mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 09:10:40 -05:00
a2db70a8d0
- [x] `dlfcn.rs` - `dlopen()`-related code. - [x] `turbocall.rs` - Call trampoline JIT compiler. - [x] `repr.rs` - Pointer representation. Home of the UnsafePointerView ops. - [x] `symbol.rs` - Function symbol related code. - [x] `callback.rs` - Home of `Deno.UnsafeCallback` ops. - [x] `ir.rs` - Intermediate representation for values. Home of the `NativeValue` type. - [x] `call.rs` - Generic call ops. Home to everything related to calling FFI symbols. - [x] `static.rs` - static symbol support I find easier to work with this setup, I eventually want to expand TurboCall to unroll type conversion loop in generic calls, generate code for individual symbols (lazy function pointers), etc.
335 lines
9.8 KiB
Rust
335 lines
9.8 KiB
Rust
// 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, ¶meter_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)
|
|
}
|