summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/ops.rs35
-rw-r--r--core/runtime/ops.rs184
-rw-r--r--ops/op2/dispatch_fast.rs293
-rw-r--r--ops/op2/dispatch_slow.rs100
-rw-r--r--ops/op2/generator_state.rs4
-rw-r--r--ops/op2/mod.rs11
-rw-r--r--ops/op2/test_cases/sync/add.out7
-rw-r--r--ops/op2/test_cases/sync/doc_comment.out5
-rw-r--r--ops/op2/test_cases/sync/result_primitive.out59
-rw-r--r--ops/op2/test_cases/sync/result_primitive.rs2
-rw-r--r--ops/op2/test_cases/sync/result_void.out59
-rw-r--r--ops/op2/test_cases/sync/result_void.rs2
-rw-r--r--ops/op2/test_cases/sync/smi.out7
13 files changed, 549 insertions, 219 deletions
diff --git a/core/ops.rs b/core/ops.rs
index a5c76e412..361fc3cb7 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -14,6 +14,7 @@ use futures::Future;
use pin_project::pin_project;
use serde::Serialize;
use std::cell::RefCell;
+use std::cell::UnsafeCell;
use std::ops::Deref;
use std::ops::DerefMut;
use std::ptr::NonNull;
@@ -106,7 +107,10 @@ pub fn to_op_result<R: Serialize + 'static>(
}
}
-// TODO(@AaronO): optimize OpCtx(s) mem usage ?
+/// Per-op context.
+///
+// Note: We don't worry too much about the size of this struct because it's allocated once per realm, and is
+// stored in a contiguous array.
pub struct OpCtx {
pub id: OpId,
pub state: Rc<RefCell<OpState>>,
@@ -114,6 +118,8 @@ pub struct OpCtx {
pub fast_fn_c_info: Option<NonNull<v8::fast_api::CFunctionInfo>>,
pub runtime_state: Weak<RefCell<JsRuntimeState>>,
pub(crate) context_state: Rc<RefCell<ContextState>>,
+ /// If the last fast op failed, stores the error to be picked up by the slow op.
+ pub(crate) last_fast_error: UnsafeCell<Option<AnyError>>,
}
impl OpCtx {
@@ -145,8 +151,35 @@ impl OpCtx {
decl,
context_state,
fast_fn_c_info,
+ last_fast_error: UnsafeCell::new(None),
}
}
+
+ /// This takes the last error from an [`OpCtx`], assuming that no other code anywhere
+ /// can hold a `&mut` to the last_fast_error field.
+ ///
+ /// # Safety
+ ///
+ /// Must only be called from op implementations.
+ #[inline(always)]
+ pub unsafe fn unsafely_take_last_error_for_ops_only(
+ &self,
+ ) -> Option<AnyError> {
+ let opt_mut = &mut *self.last_fast_error.get();
+ opt_mut.take()
+ }
+
+ /// This set the last error for an [`OpCtx`], assuming that no other code anywhere
+ /// can hold a `&mut` to the last_fast_error field.
+ ///
+ /// # Safety
+ ///
+ /// Must only be called from op implementations.
+ #[inline(always)]
+ pub unsafe fn unsafely_set_last_error_for_ops_only(&self, error: AnyError) {
+ let opt_mut = &mut *self.last_fast_error.get();
+ *opt_mut = Some(error);
+ }
}
/// Maintains the resources and ops inside a JS runtime.
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs
index 76a29c5c3..84b578aeb 100644
--- a/core/runtime/ops.rs
+++ b/core/runtime/ops.rs
@@ -215,12 +215,15 @@ mod tests {
use crate::JsRuntime;
use crate::RuntimeOptions;
use deno_ops::op2;
+ use std::cell::Cell;
crate::extension!(
testing,
ops = [
+ op_test_fail,
op_test_add,
op_test_add_option,
+ op_test_result_void_switch,
op_test_result_void_ok,
op_test_result_void_err,
op_test_result_primitive_ok,
@@ -228,47 +231,84 @@ mod tests {
]
);
+ thread_local! {
+ static FAIL: Cell<bool> = Cell::new(false)
+ }
+
+ #[op2(core, fast)]
+ pub fn op_test_fail() {
+ FAIL.with(|b| {
+ println!("fail");
+ b.set(true)
+ })
+ }
+
/// Run a test for a single op.
- fn run_test(
+ fn run_test2(
+ repeat: usize,
op: &'static str,
test: &'static str,
- f: impl FnOnce(Result<&v8::Value, anyhow::Error>, &mut v8::HandleScope),
- ) {
+ ) -> Result<(), AnyError> {
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![testing::init_ext()],
..Default::default()
});
- let value: Result<v8::Global<v8::Value>, anyhow::Error> = runtime
+ runtime
.execute_script(
"",
FastString::Owned(
- format!("const {{ {op} }} = Deno.core.ensureFastOps(); {test}")
- .into(),
+ format!(
+ r"
+ const {{ op_test_fail, {op} }} = Deno.core.ensureFastOps();
+ function assert(b) {{
+ if (!b) {{
+ op_test_fail();
+ }}
+ }}
+ "
+ )
+ .into(),
),
- );
- let mut scope: v8::HandleScope =
- // SAFETY: transmute for test (this lifetime should be safe for this purpose)
- unsafe { std::mem::transmute(runtime.handle_scope()) };
- match value {
- Ok(value) => {
- let value = value.open(&mut scope);
- f(Ok(value), &mut scope)
- }
- Err(err) => f(Err(err), &mut scope),
+ )
+ .unwrap();
+ FAIL.with(|b| b.set(false));
+ runtime.execute_script(
+ "",
+ FastString::Owned(
+ format!(
+ r"
+ for (let __index__ = 0; __index__ < {repeat}; __index__++) {{
+ {test}
+ }}
+ "
+ )
+ .into(),
+ ),
+ )?;
+ if FAIL.with(|b| b.get()) {
+ Err(generic_error("test failed"))
+ } else {
+ Ok(())
}
}
+ #[tokio::test(flavor = "current_thread")]
+ pub async fn test_op_fail() {
+ assert!(run_test2(1, "", "assert(false)").is_err());
+ }
+
#[op2(core, fast)]
pub fn op_test_add(a: u32, b: u32) -> u32 {
a + b
}
- #[tokio::test]
+ #[tokio::test(flavor = "current_thread")]
pub async fn test_op_add() -> Result<(), Box<dyn std::error::Error>> {
- run_test("op_test_add", "op_test_add(1, 11)", |value, scope| {
- assert_eq!(value.unwrap().int32_value(scope), Some(12));
- });
- Ok(())
+ Ok(run_test2(
+ 10000,
+ "op_test_add",
+ "assert(op_test_add(1, 11) == 12)",
+ )?)
}
#[op2(core)]
@@ -276,59 +316,84 @@ mod tests {
a + b.unwrap_or(100)
}
- #[tokio::test]
+ #[tokio::test(flavor = "current_thread")]
pub async fn test_op_add_option() -> Result<(), Box<dyn std::error::Error>> {
- run_test(
+ // This isn't fast, so we don't repeat it
+ run_test2(
+ 1,
"op_test_add_option",
- "op_test_add_option(1, 11)",
- |value, scope| {
- assert_eq!(value.unwrap().int32_value(scope), Some(12));
- },
- );
- run_test(
+ "assert(op_test_add_option(1, 11) == 12)",
+ )?;
+ run_test2(
+ 1,
"op_test_add_option",
- "op_test_add_option(1, null)",
- |value, scope| {
- assert_eq!(value.unwrap().int32_value(scope), Some(101));
- },
- );
+ "assert(op_test_add_option(1, null) == 101)",
+ )?;
Ok(())
}
- #[op2(core)]
+ thread_local! {
+ static RETURN_COUNT: Cell<usize> = Cell::new(0);
+ }
+
+ #[op2(core, fast)]
+ pub fn op_test_result_void_switch() -> Result<(), AnyError> {
+ let count = RETURN_COUNT.with(|count| {
+ let new = count.get() + 1;
+ count.set(new);
+ new
+ });
+ if count > 5000 {
+ Err(generic_error("failed!!!"))
+ } else {
+ Ok(())
+ }
+ }
+
+ #[op2(core, fast)]
pub fn op_test_result_void_err() -> Result<(), AnyError> {
Err(generic_error("failed!!!"))
}
- #[op2(core)]
+ #[op2(core, fast)]
pub fn op_test_result_void_ok() -> Result<(), AnyError> {
Ok(())
}
- #[tokio::test]
+ #[tokio::test(flavor = "current_thread")]
pub async fn test_op_result_void() -> Result<(), Box<dyn std::error::Error>> {
- run_test(
+ // Test the non-switching kinds
+ run_test2(
+ 10000,
"op_test_result_void_err",
- "op_test_result_void_err()",
- |value, _scope| {
- let js_error = value.err().unwrap().downcast::<JsError>().unwrap();
- assert_eq!(js_error.message, Some("failed!!!".to_owned()));
- },
- );
- run_test(
- "op_test_result_void_ok",
- "op_test_result_void_ok()",
- |value, _scope| assert!(value.unwrap().is_null_or_undefined()),
- );
+ "try { op_test_result_void_err(); assert(false) } catch (e) {}",
+ )?;
+ run_test2(10000, "op_test_result_void_ok", "op_test_result_void_ok()")?;
Ok(())
}
- #[op2(core)]
+ #[tokio::test(flavor = "current_thread")]
+ pub async fn test_op_result_void_switch(
+ ) -> Result<(), Box<dyn std::error::Error>> {
+ RETURN_COUNT.with(|count| count.set(0));
+ let err = run_test2(
+ 10000,
+ "op_test_result_void_switch",
+ "op_test_result_void_switch();",
+ )
+ .expect_err("Expected this to fail");
+ let js_err = err.downcast::<JsError>().unwrap();
+ assert_eq!(js_err.message, Some("failed!!!".into()));
+ assert_eq!(RETURN_COUNT.with(|count| count.get()), 5001);
+ Ok(())
+ }
+
+ #[op2(core, fast)]
pub fn op_test_result_primitive_err() -> Result<u32, AnyError> {
Err(generic_error("failed!!!"))
}
- #[op2(core)]
+ #[op2(core, fast)]
pub fn op_test_result_primitive_ok() -> Result<u32, AnyError> {
Ok(123)
}
@@ -336,19 +401,16 @@ mod tests {
#[tokio::test]
pub async fn test_op_result_primitive(
) -> Result<(), Box<dyn std::error::Error>> {
- run_test(
+ run_test2(
+ 10000,
"op_test_result_primitive_err",
- "op_test_result_primitive_err()",
- |value, _scope| {
- let js_error = value.err().unwrap().downcast::<JsError>().unwrap();
- assert_eq!(js_error.message, Some("failed!!!".to_owned()));
- },
- );
- run_test(
+ "try { op_test_result_primitive_err(); assert(false) } catch (e) {}",
+ )?;
+ run_test2(
+ 10000,
"op_test_result_primitive_ok",
"op_test_result_primitive_ok()",
- |value, scope| assert_eq!(value.unwrap().int32_value(scope), Some(123)),
- );
+ )?;
Ok(())
}
}
diff --git a/ops/op2/dispatch_fast.rs b/ops/op2/dispatch_fast.rs
index 94140dbf6..5262196f4 100644
--- a/ops/op2/dispatch_fast.rs
+++ b/ops/op2/dispatch_fast.rs
@@ -11,7 +11,7 @@ use quote::quote;
#[allow(unused)]
#[derive(Debug, Default, PartialEq, Clone)]
-pub(crate) enum FastValue {
+pub(crate) enum V8FastCallType {
#[default]
Void,
Bool,
@@ -27,66 +27,74 @@ pub(crate) enum FastValue {
Uint32Array,
Float64Array,
SeqOneByteString,
+ CallbackOptions,
}
-impl FastValue {
+impl V8FastCallType {
/// Quote fast value type.
- fn quote_rust_type(&self) -> TokenStream {
+ fn quote_rust_type(&self, deno_core: &TokenStream) -> TokenStream {
match self {
- FastValue::Void => quote!(()),
- FastValue::Bool => quote!(bool),
- FastValue::U32 => quote!(u32),
- FastValue::I32 => quote!(i32),
- FastValue::U64 => quote!(u64),
- FastValue::I64 => quote!(i64),
- FastValue::F32 => quote!(f32),
- FastValue::F64 => quote!(f64),
- FastValue::Pointer => quote!(*mut ::std::ffi::c_void),
- FastValue::V8Value => unimplemented!("v8::Local<v8::Value>"),
- FastValue::Uint8Array
- | FastValue::Uint32Array
- | FastValue::Float64Array
- | FastValue::SeqOneByteString => unreachable!(),
+ V8FastCallType::Void => quote!(()),
+ V8FastCallType::Bool => quote!(bool),
+ V8FastCallType::U32 => quote!(u32),
+ V8FastCallType::I32 => quote!(i32),
+ V8FastCallType::U64 => quote!(u64),
+ V8FastCallType::I64 => quote!(i64),
+ V8FastCallType::F32 => quote!(f32),
+ V8FastCallType::F64 => quote!(f64),
+ V8FastCallType::Pointer => quote!(*mut ::std::ffi::c_void),
+ V8FastCallType::V8Value => {
+ quote!(#deno_core::v8::Local<#deno_core::v8::Value>)
+ }
+ V8FastCallType::CallbackOptions => {
+ quote!(*mut #deno_core::v8::fast_api::FastApiCallbackOptions)
+ }
+ V8FastCallType::Uint8Array
+ | V8FastCallType::Uint32Array
+ | V8FastCallType::Float64Array
+ | V8FastCallType::SeqOneByteString => unreachable!(),
}
}
/// Quote fast value type's variant.
fn quote_ctype(&self) -> TokenStream {
match &self {
- FastValue::Void => quote!(CType::Void),
- FastValue::Bool => quote!(CType::Bool),
- FastValue::U32 => quote!(CType::Uint32),
- FastValue::I32 => quote!(CType::Int32),
- FastValue::U64 => quote!(CType::Uint64),
- FastValue::I64 => quote!(CType::Int64),
- FastValue::F32 => quote!(CType::Float32),
- FastValue::F64 => quote!(CType::Float64),
- FastValue::Pointer => quote!(CType::Pointer),
- FastValue::V8Value => quote!(CType::V8Value),
- FastValue::Uint8Array => unreachable!(),
- FastValue::Uint32Array => unreachable!(),
- FastValue::Float64Array => unreachable!(),
- FastValue::SeqOneByteString => quote!(CType::SeqOneByteString),
+ V8FastCallType::Void => quote!(CType::Void),
+ V8FastCallType::Bool => quote!(CType::Bool),
+ V8FastCallType::U32 => quote!(CType::Uint32),
+ V8FastCallType::I32 => quote!(CType::Int32),
+ V8FastCallType::U64 => quote!(CType::Uint64),
+ V8FastCallType::I64 => quote!(CType::Int64),
+ V8FastCallType::F32 => quote!(CType::Float32),
+ V8FastCallType::F64 => quote!(CType::Float64),
+ V8FastCallType::Pointer => quote!(CType::Pointer),
+ V8FastCallType::V8Value => quote!(CType::V8Value),
+ V8FastCallType::CallbackOptions => quote!(CType::CallbackOptions),
+ V8FastCallType::Uint8Array => unreachable!(),
+ V8FastCallType::Uint32Array => unreachable!(),
+ V8FastCallType::Float64Array => unreachable!(),
+ V8FastCallType::SeqOneByteString => quote!(CType::SeqOneByteString),
}
}
/// Quote fast value type's variant.
fn quote_type(&self) -> TokenStream {
match &self {
- FastValue::Void => quote!(Type::Void),
- FastValue::Bool => quote!(Type::Bool),
- FastValue::U32 => quote!(Type::Uint32),
- FastValue::I32 => quote!(Type::Int32),
- FastValue::U64 => quote!(Type::Uint64),
- FastValue::I64 => quote!(Type::Int64),
- FastValue::F32 => quote!(Type::Float32),
- FastValue::F64 => quote!(Type::Float64),
- FastValue::Pointer => quote!(Type::Pointer),
- FastValue::V8Value => quote!(Type::V8Value),
- FastValue::Uint8Array => quote!(Type::TypedArray(CType::Uint8)),
- FastValue::Uint32Array => quote!(Type::TypedArray(CType::Uint32)),
- FastValue::Float64Array => quote!(Type::TypedArray(CType::Float64)),
- FastValue::SeqOneByteString => quote!(Type::SeqOneByteString),
+ V8FastCallType::Void => quote!(Type::Void),
+ V8FastCallType::Bool => quote!(Type::Bool),
+ V8FastCallType::U32 => quote!(Type::Uint32),
+ V8FastCallType::I32 => quote!(Type::Int32),
+ V8FastCallType::U64 => quote!(Type::Uint64),
+ V8FastCallType::I64 => quote!(Type::Int64),
+ V8FastCallType::F32 => quote!(Type::Float32),
+ V8FastCallType::F64 => quote!(Type::Float64),
+ V8FastCallType::Pointer => quote!(Type::Pointer),
+ V8FastCallType::V8Value => quote!(Type::V8Value),
+ V8FastCallType::CallbackOptions => quote!(Type::CallbackOptions),
+ V8FastCallType::Uint8Array => quote!(Type::TypedArray(CType::Uint8)),
+ V8FastCallType::Uint32Array => quote!(Type::TypedArray(CType::Uint32)),
+ V8FastCallType::Float64Array => quote!(Type::TypedArray(CType::Float64)),
+ V8FastCallType::SeqOneByteString => quote!(Type::SeqOneByteString),
}
}
}
@@ -95,104 +103,181 @@ pub fn generate_dispatch_fast(
generator_state: &mut GeneratorState,
signature: &ParsedSignature,
) -> Result<Option<(TokenStream, TokenStream)>, V8MappingError> {
- // Result not fast-call compatible (yet)
- if matches!(signature.ret_val, RetVal::Result(..)) {
- return Ok(None);
- }
-
let mut inputs = vec![];
for arg in &signature.args {
- let fv = match arg {
- Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None),
- Arg::Numeric(NumericArg::bool) => FastValue::Bool,
- Arg::Numeric(NumericArg::u32)
- | Arg::Numeric(NumericArg::u16)
- | Arg::Numeric(NumericArg::u8) => FastValue::U32,
- Arg::Numeric(NumericArg::i32)
- | Arg::Numeric(NumericArg::i16)
- | Arg::Numeric(NumericArg::i8)
- | Arg::Numeric(NumericArg::__SMI__) => FastValue::I32,
- Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
- FastValue::U64
- }
- Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
- FastValue::I64
- }
- _ => {
- return Err(V8MappingError::NoMapping("a fast argument", arg.clone()))
- }
+ let Some(fv) = map_arg_to_v8_fastcall_type(arg)? else {
+ return Ok(None);
};
inputs.push(fv);
}
+ let mut names = inputs
+ .iter()
+ .enumerate()
+ .map(|(i, _)| format_ident!("arg{i}"))
+ .collect::<Vec<_>>();
let ret_val = match &signature.ret_val {
RetVal::Infallible(arg) => arg,
RetVal::Result(arg) => arg,
};
- let output = match ret_val {
- Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None),
- Arg::Void => FastValue::Void,
- Arg::Numeric(NumericArg::bool) => FastValue::Bool,
- Arg::Numeric(NumericArg::u32)
- | Arg::Numeric(NumericArg::u16)
- | Arg::Numeric(NumericArg::u8) => FastValue::U32,
- Arg::Numeric(NumericArg::i32)
- | Arg::Numeric(NumericArg::i16)
- | Arg::Numeric(NumericArg::i8) => FastValue::I32,
- Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
- FastValue::U64
- }
- Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
- FastValue::I64
- }
- Arg::Special(_) => return Ok(None),
- _ => {
- return Err(V8MappingError::NoMapping(
- "a fast return value",
- ret_val.clone(),
- ))
- }
+ let output = match map_retval_to_v8_fastcall_type(ret_val)? {
+ None => return Ok(None),
+ Some(rv) => rv,
};
let GeneratorState {
fast_function,
deno_core,
+ result,
+ opctx,
+ fast_api_callback_options,
+ needs_fast_api_callback_options,
+ needs_fast_opctx,
..
- } = &generator_state;
+ } = generator_state;
+
+ let handle_error = match signature.ret_val {
+ RetVal::Infallible(_) => quote!(),
+ RetVal::Result(_) => {
+ *needs_fast_api_callback_options = true;
+ *needs_fast_opctx = true;
+ inputs.push(V8FastCallType::CallbackOptions);
+ quote! {
+ let #result = match #result {
+ Ok(#result) => #result,
+ Err(err) => {
+ // FASTCALL FALLBACK: This is where we set the errors for the slow-call error pickup path. There
+ // is no code running between this and the other FASTCALL FALLBACK comment, except some V8 code
+ // required to perform the fallback process. This is why the below call is safe.
- let input_types = inputs.iter().map(|fv| fv.quote_type());
+ // The reason we need to do this is because V8 does not allow exceptions to be thrown from the
+ // fast call. Instead, you are required to set the fallback flag, which indicates to V8 that it
+ // should re-call the slow version of the function. Technically the slow call should perform the
+ // same operation and then throw the same error (because it should be idempotent), but in our
+ // case we stash the error and pick it up on the slow path before doing any work.
+
+ // TODO(mmastrac): We should allow an #[op] flag to re-perform slow calls without the error path when
+ // the method is performance sensitive.
+
+ // SAFETY: We guarantee that OpCtx has no mutable references once ops are live and being called,
+ // allowing us to perform this one little bit of mutable magic.
+ unsafe { #opctx.unsafely_set_last_error_for_ops_only(err); }
+ #fast_api_callback_options.fallback = true;
+ return ::std::default::Default::default();
+ }
+ };
+ }
+ }
+ };
+
+ let input_types = inputs.iter().map(|fv| fv.quote_type()).collect::<Vec<_>>();
let output_type = output.quote_ctype();
let fast_definition = quote! {
use #deno_core::v8::fast_api::Type;
use #deno_core::v8::fast_api::CType;
#deno_core::v8::fast_api::FastFunction::new(
- &[ #( #input_types ),* ],
+ &[ Type::V8Value, #( #input_types ),* ],
#output_type,
Self::#fast_function as *const ::std::ffi::c_void
)
};
- let output_type = output.quote_rust_type();
- let names = &inputs
+ let output_type = output.quote_rust_type(deno_core);
+ let mut types = inputs
.iter()
- .enumerate()
- .map(|(i, _)| format_ident!("arg{i}"))
+ .map(|rv| rv.quote_rust_type(deno_core))
.collect::<Vec<_>>();
- let types = inputs.iter().map(|rv| rv.quote_rust_type());
+
+ let call_args = names.clone();
+
+ let with_fast_api_callback_options = if *needs_fast_api_callback_options {
+ types.push(V8FastCallType::CallbackOptions.quote_rust_type(deno_core));
+ names.push(fast_api_callback_options.clone());
+ quote! {
+ let #fast_api_callback_options = unsafe { &mut *#fast_api_callback_options };
+ }
+ } else {
+ quote!()
+ };
+ let with_opctx = if *needs_fast_opctx {
+ quote!(
+ let #opctx = unsafe {
+ &*(#deno_core::v8::Local::<v8::External>::cast(unsafe { #fast_api_callback_options.data.data }).value()
+ as *const #deno_core::_ops::OpCtx)
+ };
+ )
+ } else {
+ quote!()
+ };
let fast_fn = quote!(
fn #fast_function(
_: #deno_core::v8::Local<#deno_core::v8::Object>,
#( #names: #types, )*
) -> #output_type {
- #(
- let #names = #names as _;
- )*
- Self::call(#(#names),*)
+ #with_fast_api_callback_options
+ #with_opctx
+ let #result = Self::call(#(#call_args as _),*);
+ #handle_error
+ #result
}
);
Ok(Some((fast_definition, fast_fn)))
}
+
+fn map_arg_to_v8_fastcall_type(
+ arg: &Arg,
+) -> Result<Option<V8FastCallType>, V8MappingError> {
+ let rv = match arg {
+ Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None),
+ Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool,
+ Arg::Numeric(NumericArg::u32)
+ | Arg::Numeric(NumericArg::u16)
+ | Arg::Numeric(NumericArg::u8) => V8FastCallType::U32,
+ Arg::Numeric(NumericArg::i32)
+ | Arg::Numeric(NumericArg::i16)
+ | Arg::Numeric(NumericArg::i8)
+ | Arg::Numeric(NumericArg::__SMI__) => V8FastCallType::I32,
+ Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
+ V8FastCallType::U64
+ }
+ Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
+ V8FastCallType::I64
+ }
+ _ => return Err(V8MappingError::NoMapping("a fast argument", arg.clone())),
+ };
+ Ok(Some(rv))
+}
+
+fn map_retval_to_v8_fastcall_type(
+ arg: &Arg,
+) -> Result<Option<V8FastCallType>, V8MappingError> {
+ let rv = match arg {
+ Arg::OptionNumeric(_) | Arg::SerdeV8(_) => return Ok(None),
+ Arg::Void => V8FastCallType::Void,
+ Arg::Numeric(NumericArg::bool) => V8FastCallType::Bool,
+ Arg::Numeric(NumericArg::u32)
+ | Arg::Numeric(NumericArg::u16)
+ | Arg::Numeric(NumericArg::u8) => V8FastCallType::U32,
+ Arg::Numeric(NumericArg::i32)
+ | Arg::Numeric(NumericArg::i16)
+ | Arg::Numeric(NumericArg::i8) => V8FastCallType::I32,
+ Arg::Numeric(NumericArg::u64) | Arg::Numeric(NumericArg::usize) => {
+ V8FastCallType::U64
+ }
+ Arg::Numeric(NumericArg::i64) | Arg::Numeric(NumericArg::isize) => {
+ V8FastCallType::I64
+ }
+ Arg::Special(_) => return Ok(None),
+ _ => {
+ return Err(V8MappingError::NoMapping(
+ "a fast return value",
+ arg.clone(),
+ ))
+ }
+ };
+ Ok(Some(rv))
+}
diff --git a/ops/op2/dispatch_slow.rs b/ops/op2/dispatch_slow.rs
index f54a28f1c..f10217a2d 100644
--- a/ops/op2/dispatch_slow.rs
+++ b/ops/op2/dispatch_slow.rs
@@ -1,3 +1,4 @@
+use super::MacroConfig;
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use super::generator_state::GeneratorState;
use super::signature::Arg;
@@ -9,11 +10,31 @@ use super::V8MappingError;
use proc_macro2::TokenStream;
use quote::quote;
-pub fn generate_dispatch_slow(
+pub(crate) fn generate_dispatch_slow(
+ config: &MacroConfig,
generator_state: &mut GeneratorState,
signature: &ParsedSignature,
) -> Result<TokenStream, V8MappingError> {
let mut output = TokenStream::new();
+
+ // Fast ops require the slow op to check op_ctx for the last error
+ if config.fast && matches!(signature.ret_val, RetVal::Result(_)) {
+ generator_state.needs_opctx = true;
+ let throw_exception = throw_exception(generator_state)?;
+ // If the fast op returned an error, we must throw it rather than doing work.
+ output.extend(quote!{
+ // FASTCALL FALLBACK: This is where we pick up the errors for the slow-call error pickup
+ // path. There is no code running between this and the other FASTCALL FALLBACK comment,
+ // except some V8 code required to perform the fallback process. This is why the below call is safe.
+
+ // SAFETY: We guarantee that OpCtx has no mutable references once ops are live and being called,
+ // allowing us to perform this one little bit of mutable magic.
+ if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } {
+ #throw_exception
+ }
+ });
+ }
+
for (index, arg) in signature.args.iter().enumerate() {
output.extend(extract_arg(generator_state, index)?);
output.extend(from_arg(generator_state, index, arg)?);
@@ -27,6 +48,12 @@ pub fn generate_dispatch_slow(
quote!()
};
+ let with_opctx = if generator_state.needs_opctx {
+ with_opctx(generator_state)
+ } else {
+ quote!()
+ };
+
let with_retval = if generator_state.needs_retval {
with_retval(generator_state)
} else {
@@ -51,6 +78,7 @@ pub fn generate_dispatch_slow(
#with_scope
#with_retval
#with_args
+ #with_opctx
#output
}})
@@ -94,9 +122,11 @@ fn with_opctx(generator_state: &mut GeneratorState) -> TokenStream {
deno_core,
opctx,
fn_args,
+ needs_args,
..
- } = &generator_state;
+ } = generator_state;
+ *needs_args = true;
quote!(let #opctx = unsafe {
&*(#deno_core::v8::Local::<#deno_core::v8::External>::cast(#fn_args.data()).value()
as *const #deno_core::_ops::OpCtx)
@@ -246,56 +276,68 @@ pub fn return_value_infallible(
Ok(res)
}
-pub fn return_value_result(
+fn return_value_result(
generator_state: &mut GeneratorState,
ret_type: &Arg,
) -> Result<TokenStream, V8MappingError> {
let infallible = return_value_infallible(generator_state, ret_type)?;
+ let exception = throw_exception(generator_state)?;
+
+ let GeneratorState { result, .. } = &generator_state;
+
+ let tokens = quote!(
+ match #result {
+ Ok(#result) => {
+ #infallible
+ }
+ Err(err) => {
+ #exception
+ }
+ };
+ );
+ Ok(tokens)
+}
+
+/// Generates code to throw an exception, adding required additional dependencies as needed.
+fn throw_exception(
+ generator_state: &mut GeneratorState,
+) -> Result<TokenStream, V8MappingError> {
let maybe_scope = if generator_state.needs_scope {
quote!()
} else {
with_scope(generator_state)
};
- let maybe_args = if generator_state.needs_args {
+ let maybe_opctx = if generator_state.needs_opctx {
quote!()
} else {
- with_fn_args(generator_state)
+ with_opctx(generator_state)
};
- let maybe_opctx = if generator_state.needs_opctx {
+ let maybe_args = if generator_state.needs_args {
quote!()
} else {
- with_opctx(generator_state)
+ with_fn_args(generator_state)
};
let GeneratorState {
deno_core,
- result,
scope,
opctx,
..
} = &generator_state;
- let tokens = quote!(
- match #result {
- Ok(#result) => {
- #infallible
- }
- Err(err) => {
- #maybe_scope
- #maybe_args
- #maybe_opctx
- let opstate = ::std::cell::RefCell::borrow(&*#opctx.state);
- let exception = #deno_core::error::to_v8_error(
- #scope,
- opstate.get_error_class_fn,
- &err,
- );
- scope.throw_exception(exception);
- return;
- }
- };
- );
- Ok(tokens)
+ Ok(quote! {
+ #maybe_scope
+ #maybe_args
+ #maybe_opctx
+ let opstate = ::std::cell::RefCell::borrow(&*#opctx.state);
+ let exception = #deno_core::error::to_v8_error(
+ #scope,
+ opstate.get_error_class_fn,
+ &err,
+ );
+ scope.throw_exception(exception);
+ return;
+ })
}
diff --git a/ops/op2/generator_state.rs b/ops/op2/generator_state.rs
index 16249c217..e437ea47c 100644
--- a/ops/op2/generator_state.rs
+++ b/ops/op2/generator_state.rs
@@ -21,6 +21,8 @@ pub struct GeneratorState {
pub fn_args: Ident,
/// The `OpCtx` used for various information required for some ops.
pub opctx: Ident,
+ /// The `FastApiCallbackOptions` used in fast calls for fallback returns.
+ pub fast_api_callback_options: Ident,
/// The `v8::ReturnValue` used in the slow function
pub retval: Ident,
/// The "slow" function (ie: the one that isn't a fastcall)
@@ -33,4 +35,6 @@ pub struct GeneratorState {
pub needs_scope: bool,
pub needs_opstate: bool,
pub needs_opctx: bool,
+ pub needs_fast_opctx: bool,
+ pub needs_fast_api_callback_options: bool,
}
diff --git a/ops/op2/mod.rs b/ops/op2/mod.rs
index 558d7c7dc..67a92d450 100644
--- a/ops/op2/mod.rs
+++ b/ops/op2/mod.rs
@@ -50,7 +50,7 @@ pub enum V8MappingError {
}
#[derive(Default)]
-struct MacroConfig {
+pub(crate) struct MacroConfig {
pub core: bool,
pub fast: bool,
}
@@ -135,6 +135,8 @@ fn generate_op2(
let opctx = Ident::new("opctx", Span::call_site());
let slow_function = Ident::new("slow_function", Span::call_site());
let fast_function = Ident::new("fast_function", Span::call_site());
+ let fast_api_callback_options =
+ Ident::new("fast_api_callback_options", Span::call_site());
let deno_core = if config.core {
syn2::parse_str::<Path>("crate")
@@ -151,6 +153,7 @@ fn generate_op2(
scope,
info,
opctx,
+ fast_api_callback_options,
deno_core,
result,
retval,
@@ -161,10 +164,14 @@ fn generate_op2(
needs_scope: false,
needs_opctx: false,
needs_opstate: false,
+ needs_fast_opctx: false,
+ needs_fast_api_callback_options: false,
};
let name = func.sig.ident;
- let slow_fn = generate_dispatch_slow(&mut generator_state, &signature)?;
+
+ let slow_fn =
+ generate_dispatch_slow(&config, &mut generator_state, &signature)?;
let (fast_definition, fast_fn) =
match generate_dispatch_fast(&mut generator_state, &signature)? {
Some((fast_definition, fast_fn)) => {
diff --git a/ops/op2/test_cases/sync/add.out b/ops/op2/test_cases/sync/add.out
index a7269c5cf..7d97a7161 100644
--- a/ops/op2/test_cases/sync/add.out
+++ b/ops/op2/test_cases/sync/add.out
@@ -13,7 +13,7 @@ impl op_add {
use deno_core::v8::fast_api::Type;
use deno_core::v8::fast_api::CType;
deno_core::v8::fast_api::FastFunction::new(
- &[Type::Uint32, Type::Uint32],
+ &[Type::V8Value, Type::Uint32, Type::Uint32],
CType::Uint32,
Self::fast_function as *const ::std::ffi::c_void,
)
@@ -43,9 +43,8 @@ impl op_add {
arg0: u32,
arg1: u32,
) -> u32 {
- let arg0 = arg0 as _;
- let arg1 = arg1 as _;
- Self::call(arg0, arg1)
+ let result = Self::call(arg0 as _, arg1 as _);
+ result
}
#[inline(always)]
fn call(a: u32, b: u32) -> u32 {
diff --git a/ops/op2/test_cases/sync/doc_comment.out b/ops/op2/test_cases/sync/doc_comment.out
index bd0d0b21f..e9f063102 100644
--- a/ops/op2/test_cases/sync/doc_comment.out
+++ b/ops/op2/test_cases/sync/doc_comment.out
@@ -13,7 +13,7 @@ impl op_has_doc_comment {
use deno_core::v8::fast_api::Type;
use deno_core::v8::fast_api::CType;
deno_core::v8::fast_api::FastFunction::new(
- &[],
+ &[Type::V8Value],
CType::Void,
Self::fast_function as *const ::std::ffi::c_void,
)
@@ -28,7 +28,8 @@ impl op_has_doc_comment {
let result = Self::call();
}
fn fast_function(_: deno_core::v8::Local<deno_core::v8::Object>) -> () {
- Self::call()
+ let result = Self::call();
+ result
}
#[inline(always)]
pub fn call() -> () {}
diff --git a/ops/op2/test_cases/sync/result_primitive.out b/ops/op2/test_cases/sync/result_primitive.out
index a8ac50174..151e8e730 100644
--- a/ops/op2/test_cases/sync/result_primitive.out
+++ b/ops/op2/test_cases/sync/result_primitive.out
@@ -9,7 +9,15 @@ impl op_u32_with_result {
name: stringify!(op_u32_with_result),
v8_fn_ptr: Self::slow_function as _,
enabled: true,
- fast_fn: None,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::CallbackOptions],
+ CType::Uint32,
+ Self::fast_function as *const ::std::ffi::c_void,
+ )
+ }),
is_async: false,
is_unstable: false,
is_v8: false,
@@ -20,6 +28,27 @@ impl op_u32_with_result {
let mut rv = deno_core::v8::ReturnValue::from_function_callback_info(unsafe {
&*info
});
+ let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+ &*info
+ });
+ let opctx = unsafe {
+ &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+ as *const deno_core::_ops::OpCtx)
+ };
+ if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+ &*info
+ });
+ let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
+ let exception = deno_core::error::to_v8_error(
+ scope,
+ opstate.get_error_class_fn,
+ &err,
+ );
+ scope.throw_exception(exception);
+ return;
+ }
let result = Self::call();
match result {
Ok(result) => {
@@ -30,10 +59,6 @@ impl op_u32_with_result {
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
&*info
});
- let opctx = unsafe {
- &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data())
- .value() as *const deno_core::_ops::OpCtx)
- };
let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
let exception = deno_core::error::to_v8_error(
scope,
@@ -45,6 +70,30 @@ impl op_u32_with_result {
}
};
}
+ fn fast_function(
+ _: deno_core::v8::Local<deno_core::v8::Object>,
+ fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+ ) -> u32 {
+ let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+ let opctx = unsafe {
+ &*(deno_core::v8::Local::<
+ v8::External,
+ >::cast(unsafe { fast_api_callback_options.data.data })
+ .value() as *const deno_core::_ops::OpCtx)
+ };
+ let result = Self::call();
+ let result = match result {
+ Ok(result) => result,
+ Err(err) => {
+ unsafe {
+ opctx.unsafely_set_last_error_for_ops_only(err);
+ }
+ fast_api_callback_options.fallback = true;
+ return ::std::default::Default::default();
+ }
+ };
+ result
+ }
#[inline(always)]
pub fn call() -> Result<u32, AnyError> {}
}
diff --git a/ops/op2/test_cases/sync/result_primitive.rs b/ops/op2/test_cases/sync/result_primitive.rs
index 6f68fa228..df89c2432 100644
--- a/ops/op2/test_cases/sync/result_primitive.rs
+++ b/ops/op2/test_cases/sync/result_primitive.rs
@@ -1,4 +1,4 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-#[op2]
+#[op2(fast)]
pub fn op_u32_with_result() -> Result<u32, AnyError> {}
diff --git a/ops/op2/test_cases/sync/result_void.out b/ops/op2/test_cases/sync/result_void.out
index 74c0c66a6..afc10582b 100644
--- a/ops/op2/test_cases/sync/result_void.out
+++ b/ops/op2/test_cases/sync/result_void.out
@@ -9,7 +9,15 @@ impl op_void_with_result {
name: stringify!(op_void_with_result),
v8_fn_ptr: Self::slow_function as _,
enabled: true,
- fast_fn: None,
+ fast_fn: Some({
+ use deno_core::v8::fast_api::Type;
+ use deno_core::v8::fast_api::CType;
+ deno_core::v8::fast_api::FastFunction::new(
+ &[Type::V8Value, Type::CallbackOptions],
+ CType::Void,
+ Self::fast_function as *const ::std::ffi::c_void,
+ )
+ }),
is_async: false,
is_unstable: false,
is_v8: false,
@@ -17,6 +25,27 @@ impl op_void_with_result {
}
}
pub extern "C" fn slow_function(info: *const deno_core::v8::FunctionCallbackInfo) {
+ let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+ &*info
+ });
+ let opctx = unsafe {
+ &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data()).value()
+ as *const deno_core::_ops::OpCtx)
+ };
+ if let Some(err) = unsafe { opctx.unsafely_take_last_error_for_ops_only() } {
+ let scope = &mut unsafe { deno_core::v8::CallbackScope::new(&*info) };
+ let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
+ &*info
+ });
+ let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
+ let exception = deno_core::error::to_v8_error(
+ scope,
+ opstate.get_error_class_fn,
+ &err,
+ );
+ scope.throw_exception(exception);
+ return;
+ }
let result = Self::call();
match result {
Ok(result) => {}
@@ -25,10 +54,6 @@ impl op_void_with_result {
let args = deno_core::v8::FunctionCallbackArguments::from_function_callback_info(unsafe {
&*info
});
- let opctx = unsafe {
- &*(deno_core::v8::Local::<deno_core::v8::External>::cast(args.data())
- .value() as *const deno_core::_ops::OpCtx)
- };
let opstate = ::std::cell::RefCell::borrow(&*opctx.state);
let exception = deno_core::error::to_v8_error(
scope,
@@ -40,6 +65,30 @@ impl op_void_with_result {
}
};
}
+ fn fast_function(
+ _: deno_core::v8::Local<deno_core::v8::Object>,
+ fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions,
+ ) -> () {
+ let fast_api_callback_options = unsafe { &mut *fast_api_callback_options };
+ let opctx = unsafe {
+ &*(deno_core::v8::Local::<
+ v8::External,
+ >::cast(unsafe { fast_api_callback_options.data.data })
+ .value() as *const deno_core::_ops::OpCtx)
+ };
+ let result = Self::call();
+ let result = match result {
+ Ok(result) => result,
+ Err(err) => {
+ unsafe {
+ opctx.unsafely_set_last_error_for_ops_only(err);
+ }
+ fast_api_callback_options.fallback = true;
+ return ::std::default::Default::default();
+ }
+ };
+ result
+ }
#[inline(always)]
pub fn call() -> Result<(), AnyError> {}
}
diff --git a/ops/op2/test_cases/sync/result_void.rs b/ops/op2/test_cases/sync/result_void.rs
index 41256e8c4..ef3aa7b32 100644
--- a/ops/op2/test_cases/sync/result_void.rs
+++ b/ops/op2/test_cases/sync/result_void.rs
@@ -1,4 +1,4 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-#[op2]
+#[op2(fast)]
pub fn op_void_with_result() -> Result<(), AnyError> {}
diff --git a/ops/op2/test_cases/sync/smi.out b/ops/op2/test_cases/sync/smi.out
index e6c1bc1e3..7210e0572 100644
--- a/ops/op2/test_cases/sync/smi.out
+++ b/ops/op2/test_cases/sync/smi.out
@@ -13,7 +13,7 @@ impl op_add {
use deno_core::v8::fast_api::Type;
use deno_core::v8::fast_api::CType;
deno_core::v8::fast_api::FastFunction::new(
- &[Type::Int32, Type::Uint32],
+ &[Type::V8Value, Type::Int32, Type::Uint32],
CType::Uint32,
Self::fast_function as *const ::std::ffi::c_void,
)
@@ -43,9 +43,8 @@ impl op_add {
arg0: i32,
arg1: u32,
) -> u32 {
- let arg0 = arg0 as _;
- let arg1 = arg1 as _;
- Self::call(arg0, arg1)
+ let result = Self::call(arg0 as _, arg1 as _);
+ result
}
#[inline(always)]
fn call(id: ResourceId, extra: u16) -> u32 {}