summaryrefslogtreecommitdiff
path: root/ext/ffi/call.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/ffi/call.rs')
-rw-r--r--ext/ffi/call.rs335
1 files changed, 335 insertions, 0 deletions
diff --git a/ext/ffi/call.rs b/ext/ffi/call.rs
new file mode 100644
index 000000000..4a913c0e0
--- /dev/null
+++ b/ext/ffi/call.rs
@@ -0,0 +1,335 @@
+// Copyright 2018-2022 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::v8;
+use deno_core::ResourceId;
+use libffi::middle::Arg;
+use std::cell::RefCell;
+use std::ffi::c_void;
+use std::future::Future;
+use std::rc::Rc;
+
+// A one-off synchronous FFI call.
+pub(crate) fn ffi_call_sync<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ args: v8::FunctionCallbackArguments,
+ symbol: &Symbol,
+) -> 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::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().map(Arg::new).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),
+ }
+ }
+ })
+ }
+}
+
+fn ffi_call(
+ call_args: Vec<NativeValue>,
+ cif: &libffi::middle::Cif,
+ fun_ptr: libffi::middle::CodePtr,
+ parameter_types: &[NativeType],
+ result_type: NativeType,
+) -> Result<NativeValue, 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 => 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),
+ }
+ }
+ })
+ }
+}
+
+#[op(v8)]
+pub fn op_ffi_call_ptr_nonblocking<'scope, FP>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ pointer: usize,
+ def: ForeignFunction,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<impl Future<Output = Result<Value, 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 join_handle = tokio::task::spawn_blocking(move || {
+ let PtrSymbol { cif, ptr } = symbol.clone();
+ ffi_call(call_args, &cif, ptr, &def.parameters, def.result)
+ });
+
+ 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(unsafe { result.to_value(def.result) })
+ })
+}
+
+/// A non-blocking FFI call.
+#[op(v8)]
+pub fn op_ffi_call_nonblocking<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ rid: ResourceId,
+ symbol: String,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<impl Future<Output = Result<Value, 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 result_type = symbol.result_type;
+ let join_handle = tokio::task::spawn_blocking(move || {
+ let Symbol {
+ cif,
+ ptr,
+ parameter_types,
+ result_type,
+ ..
+ } = symbol.clone();
+ ffi_call(call_args, &cif, ptr, &parameter_types, result_type)
+ });
+
+ 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(unsafe { result.to_value(result_type) })
+ })
+}
+
+#[op(v8)]
+pub fn op_ffi_call_ptr<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ pointer: usize,
+ def: ForeignFunction,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, 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 result = ffi_call(
+ call_args,
+ &symbol.cif,
+ symbol.ptr,
+ &def.parameters,
+ def.result,
+ )?;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ let result = unsafe { result.to_v8(scope, def.result) };
+ Ok(result)
+}