mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
9845361153
Partially supersedes #19016. This migrates `spawn` and `spawn_blocking` to `deno_core`, and removes the requirement for `spawn` tasks to be `Send` given our single-threaded executor. While we don't need to technically do anything w/`spawn_blocking`, this allows us to have a single `JoinHandle` type that works for both cases, and allows us to more easily experiment with alternative `spawn_blocking` implementations that do not require tokio (ie: rayon). Async ops (+~35%): Before: ``` time 1310 ms rate 763358 time 1267 ms rate 789265 time 1259 ms rate 794281 time 1266 ms rate 789889 ``` After: ``` time 956 ms rate 1046025 time 954 ms rate 1048218 time 924 ms rate 1082251 time 920 ms rate 1086956 ``` HTTP serve (+~4.4%): Before: ``` Running 10s test @ http://localhost:4500 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 68.78us 19.77us 1.43ms 86.84% Req/Sec 68.78k 5.00k 73.84k 91.58% 1381833 requests in 10.10s, 167.36MB read Requests/sec: 136823.29 Transfer/sec: 16.57MB ``` After: ``` Running 10s test @ http://localhost:4500 2 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 63.12us 17.43us 1.11ms 85.13% Req/Sec 71.82k 3.71k 77.02k 79.21% 1443195 requests in 10.10s, 174.79MB read Requests/sec: 142921.99 Transfer/sec: 17.31MB ``` Suggested-By: alice@ryhl.io Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
412 lines
12 KiB
Rust
412 lines
12 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use crate::callback::PtrSymbol;
|
|
use crate::check_unstable2;
|
|
use crate::dlfcn::DynamicLibraryResource;
|
|
use crate::ir::*;
|
|
use crate::symbol::NativeType;
|
|
use crate::symbol::Symbol;
|
|
use crate::FfiPermissions;
|
|
use crate::ForeignFunction;
|
|
use deno_core::anyhow::anyhow;
|
|
use deno_core::error::type_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::op;
|
|
use deno_core::serde_json::Value;
|
|
use deno_core::serde_v8;
|
|
use deno_core::serde_v8::ExternalPointer;
|
|
use deno_core::task::spawn_blocking;
|
|
use deno_core::v8;
|
|
use deno_core::OpState;
|
|
use deno_core::ResourceId;
|
|
use libffi::middle::Arg;
|
|
use serde::Serialize;
|
|
use std::cell::RefCell;
|
|
use std::ffi::c_void;
|
|
use std::future::Future;
|
|
use std::rc::Rc;
|
|
|
|
// SAFETY: Makes an FFI call
|
|
unsafe fn ffi_call_rtype_struct(
|
|
cif: &libffi::middle::Cif,
|
|
fn_ptr: &libffi::middle::CodePtr,
|
|
call_args: Vec<Arg>,
|
|
out_buffer: *mut u8,
|
|
) {
|
|
libffi::raw::ffi_call(
|
|
cif.as_raw_ptr(),
|
|
Some(*fn_ptr.as_safe_fun()),
|
|
out_buffer as *mut c_void,
|
|
call_args.as_ptr() as *mut *mut c_void,
|
|
);
|
|
}
|
|
|
|
// A one-off synchronous FFI call.
|
|
pub(crate) fn ffi_call_sync<'scope>(
|
|
scope: &mut v8::HandleScope<'scope>,
|
|
args: v8::FunctionCallbackArguments,
|
|
symbol: &Symbol,
|
|
out_buffer: Option<OutBuffer>,
|
|
) -> Result<NativeValue, AnyError>
|
|
where
|
|
'scope: 'scope,
|
|
{
|
|
let Symbol {
|
|
parameter_types,
|
|
result_type,
|
|
cif,
|
|
ptr: fun_ptr,
|
|
..
|
|
} = symbol;
|
|
let mut ffi_args: Vec<NativeValue> =
|
|
Vec::with_capacity(parameter_types.len());
|
|
|
|
for (index, native_type) in parameter_types.iter().enumerate() {
|
|
let value = args.get(index as i32);
|
|
match native_type {
|
|
NativeType::Bool => {
|
|
ffi_args.push(ffi_parse_bool_arg(value)?);
|
|
}
|
|
NativeType::U8 => {
|
|
ffi_args.push(ffi_parse_u8_arg(value)?);
|
|
}
|
|
NativeType::I8 => {
|
|
ffi_args.push(ffi_parse_i8_arg(value)?);
|
|
}
|
|
NativeType::U16 => {
|
|
ffi_args.push(ffi_parse_u16_arg(value)?);
|
|
}
|
|
NativeType::I16 => {
|
|
ffi_args.push(ffi_parse_i16_arg(value)?);
|
|
}
|
|
NativeType::U32 => {
|
|
ffi_args.push(ffi_parse_u32_arg(value)?);
|
|
}
|
|
NativeType::I32 => {
|
|
ffi_args.push(ffi_parse_i32_arg(value)?);
|
|
}
|
|
NativeType::U64 => {
|
|
ffi_args.push(ffi_parse_u64_arg(scope, value)?);
|
|
}
|
|
NativeType::I64 => {
|
|
ffi_args.push(ffi_parse_i64_arg(scope, value)?);
|
|
}
|
|
NativeType::USize => {
|
|
ffi_args.push(ffi_parse_usize_arg(scope, value)?);
|
|
}
|
|
NativeType::ISize => {
|
|
ffi_args.push(ffi_parse_isize_arg(scope, value)?);
|
|
}
|
|
NativeType::F32 => {
|
|
ffi_args.push(ffi_parse_f32_arg(value)?);
|
|
}
|
|
NativeType::F64 => {
|
|
ffi_args.push(ffi_parse_f64_arg(value)?);
|
|
}
|
|
NativeType::Buffer => {
|
|
ffi_args.push(ffi_parse_buffer_arg(scope, value)?);
|
|
}
|
|
NativeType::Struct(_) => {
|
|
ffi_args.push(ffi_parse_struct_arg(scope, value)?);
|
|
}
|
|
NativeType::Pointer => {
|
|
ffi_args.push(ffi_parse_pointer_arg(scope, value)?);
|
|
}
|
|
NativeType::Function => {
|
|
ffi_args.push(ffi_parse_function_arg(scope, value)?);
|
|
}
|
|
NativeType::Void => {
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
let call_args: Vec<Arg> = ffi_args
|
|
.iter()
|
|
.enumerate()
|
|
// SAFETY: Creating a `Arg` from a `NativeValue` is pretty safe.
|
|
.map(|(i, v)| unsafe { v.as_arg(parameter_types.get(i).unwrap()) })
|
|
.collect();
|
|
// SAFETY: types in the `Cif` match the actual calling convention and
|
|
// types of symbol.
|
|
unsafe {
|
|
Ok(match result_type {
|
|
NativeType::Void => NativeValue {
|
|
void_value: cif.call::<()>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::Bool => NativeValue {
|
|
bool_value: cif.call::<bool>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::U8 => NativeValue {
|
|
u8_value: cif.call::<u8>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::I8 => NativeValue {
|
|
i8_value: cif.call::<i8>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::U16 => NativeValue {
|
|
u16_value: cif.call::<u16>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::I16 => NativeValue {
|
|
i16_value: cif.call::<i16>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::U32 => NativeValue {
|
|
u32_value: cif.call::<u32>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::I32 => NativeValue {
|
|
i32_value: cif.call::<i32>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::U64 => NativeValue {
|
|
u64_value: cif.call::<u64>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::I64 => NativeValue {
|
|
i64_value: cif.call::<i64>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::USize => NativeValue {
|
|
usize_value: cif.call::<usize>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::ISize => NativeValue {
|
|
isize_value: cif.call::<isize>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::F32 => NativeValue {
|
|
f32_value: cif.call::<f32>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::F64 => NativeValue {
|
|
f64_value: cif.call::<f64>(*fun_ptr, &call_args),
|
|
},
|
|
NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
|
|
NativeValue {
|
|
pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args),
|
|
}
|
|
}
|
|
NativeType::Struct(_) => NativeValue {
|
|
void_value: ffi_call_rtype_struct(
|
|
&symbol.cif,
|
|
&symbol.ptr,
|
|
call_args,
|
|
out_buffer.unwrap().0,
|
|
),
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(untagged)]
|
|
pub enum FfiValue {
|
|
Value(Value),
|
|
External(ExternalPointer),
|
|
}
|
|
|
|
fn ffi_call(
|
|
call_args: Vec<NativeValue>,
|
|
cif: &libffi::middle::Cif,
|
|
fun_ptr: libffi::middle::CodePtr,
|
|
parameter_types: &[NativeType],
|
|
result_type: NativeType,
|
|
out_buffer: Option<OutBuffer>,
|
|
) -> Result<FfiValue, AnyError> {
|
|
let call_args: Vec<Arg> = call_args
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, ffi_arg)| {
|
|
// SAFETY: the union field is initialized
|
|
unsafe { ffi_arg.as_arg(parameter_types.get(index).unwrap()) }
|
|
})
|
|
.collect();
|
|
|
|
// SAFETY: types in the `Cif` match the actual calling convention and
|
|
// types of symbol.
|
|
unsafe {
|
|
Ok(match result_type {
|
|
NativeType::Void => {
|
|
cif.call::<()>(fun_ptr, &call_args);
|
|
FfiValue::Value(Value::from(()))
|
|
}
|
|
NativeType::Bool => {
|
|
FfiValue::Value(Value::from(cif.call::<bool>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U8 => {
|
|
FfiValue::Value(Value::from(cif.call::<u8>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I8 => {
|
|
FfiValue::Value(Value::from(cif.call::<i8>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U16 => {
|
|
FfiValue::Value(Value::from(cif.call::<u16>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I16 => {
|
|
FfiValue::Value(Value::from(cif.call::<i16>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U32 => {
|
|
FfiValue::Value(Value::from(cif.call::<u32>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I32 => {
|
|
FfiValue::Value(Value::from(cif.call::<i32>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::U64 => {
|
|
FfiValue::Value(Value::from(cif.call::<u64>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::I64 => {
|
|
FfiValue::Value(Value::from(cif.call::<i64>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::USize => {
|
|
FfiValue::Value(Value::from(cif.call::<usize>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::ISize => {
|
|
FfiValue::Value(Value::from(cif.call::<isize>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::F32 => {
|
|
FfiValue::Value(Value::from(cif.call::<f32>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::F64 => {
|
|
FfiValue::Value(Value::from(cif.call::<f64>(fun_ptr, &call_args)))
|
|
}
|
|
NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
|
|
FfiValue::External(ExternalPointer::from(
|
|
cif.call::<*mut c_void>(fun_ptr, &call_args),
|
|
))
|
|
}
|
|
NativeType::Struct(_) => {
|
|
ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0);
|
|
FfiValue::Value(Value::Null)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
#[op(v8)]
|
|
pub fn op_ffi_call_ptr_nonblocking<'scope, FP>(
|
|
scope: &mut v8::HandleScope<'scope>,
|
|
state: Rc<RefCell<OpState>>,
|
|
pointer: *mut c_void,
|
|
def: ForeignFunction,
|
|
parameters: serde_v8::Value<'scope>,
|
|
out_buffer: Option<serde_v8::Value<'scope>>,
|
|
) -> Result<impl Future<Output = Result<FfiValue, AnyError>>, AnyError>
|
|
where
|
|
FP: FfiPermissions + 'static,
|
|
{
|
|
check_unstable2(&state, "Deno.UnsafeFnPointer#call");
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<FP>();
|
|
permissions.check(None)?;
|
|
};
|
|
|
|
let symbol = PtrSymbol::new(pointer, &def)?;
|
|
let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
|
|
|
|
let out_buffer = out_buffer
|
|
.map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap());
|
|
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
|
|
|
|
let join_handle = spawn_blocking(move || {
|
|
let PtrSymbol { cif, ptr } = symbol.clone();
|
|
ffi_call(
|
|
call_args,
|
|
&cif,
|
|
ptr,
|
|
&def.parameters,
|
|
def.result,
|
|
out_buffer_ptr,
|
|
)
|
|
});
|
|
|
|
Ok(async move {
|
|
let result = join_handle
|
|
.await
|
|
.map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
|
|
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
|
|
Ok(result)
|
|
})
|
|
}
|
|
|
|
/// A non-blocking FFI call.
|
|
#[op(v8)]
|
|
pub fn op_ffi_call_nonblocking<'scope>(
|
|
scope: &mut v8::HandleScope<'scope>,
|
|
state: Rc<RefCell<OpState>>,
|
|
rid: ResourceId,
|
|
symbol: String,
|
|
parameters: serde_v8::Value<'scope>,
|
|
out_buffer: Option<serde_v8::Value<'scope>>,
|
|
) -> Result<impl Future<Output = Result<FfiValue, AnyError>> + 'static, AnyError>
|
|
{
|
|
let symbol = {
|
|
let state = state.borrow();
|
|
let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
|
|
let symbols = &resource.symbols;
|
|
*symbols
|
|
.get(&symbol)
|
|
.ok_or_else(|| type_error("Invalid FFI symbol name"))?
|
|
.clone()
|
|
};
|
|
|
|
let call_args = ffi_parse_args(scope, parameters, &symbol.parameter_types)?;
|
|
let out_buffer = out_buffer
|
|
.map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap());
|
|
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
|
|
|
|
let join_handle = spawn_blocking(move || {
|
|
let Symbol {
|
|
cif,
|
|
ptr,
|
|
parameter_types,
|
|
result_type,
|
|
..
|
|
} = symbol.clone();
|
|
ffi_call(
|
|
call_args,
|
|
&cif,
|
|
ptr,
|
|
¶meter_types,
|
|
result_type,
|
|
out_buffer_ptr,
|
|
)
|
|
});
|
|
|
|
Ok(async move {
|
|
let result = join_handle
|
|
.await
|
|
.map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??;
|
|
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
|
|
Ok(result)
|
|
})
|
|
}
|
|
|
|
#[op(v8)]
|
|
pub fn op_ffi_call_ptr<FP, 'scope>(
|
|
scope: &mut v8::HandleScope<'scope>,
|
|
state: Rc<RefCell<OpState>>,
|
|
pointer: *mut c_void,
|
|
def: ForeignFunction,
|
|
parameters: serde_v8::Value<'scope>,
|
|
out_buffer: Option<serde_v8::Value<'scope>>,
|
|
) -> Result<FfiValue, AnyError>
|
|
where
|
|
FP: FfiPermissions + 'static,
|
|
{
|
|
check_unstable2(&state, "Deno.UnsafeFnPointer#call");
|
|
{
|
|
let mut state = state.borrow_mut();
|
|
let permissions = state.borrow_mut::<FP>();
|
|
permissions.check(None)?;
|
|
};
|
|
|
|
let symbol = PtrSymbol::new(pointer, &def)?;
|
|
let call_args = ffi_parse_args(scope, parameters, &def.parameters)?;
|
|
|
|
let out_buffer = out_buffer
|
|
.map(|v| v8::Local::<v8::TypedArray>::try_from(v.v8_value).unwrap());
|
|
let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer);
|
|
|
|
let result = ffi_call(
|
|
call_args,
|
|
&symbol.cif,
|
|
symbol.ptr,
|
|
&def.parameters,
|
|
def.result.clone(),
|
|
out_buffer_ptr,
|
|
)?;
|
|
// SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
|
|
Ok(result)
|
|
}
|