summaryrefslogtreecommitdiff
path: root/extensions/ffi/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/ffi/lib.rs')
-rw-r--r--extensions/ffi/lib.rs397
1 files changed, 397 insertions, 0 deletions
diff --git a/extensions/ffi/lib.rs b/extensions/ffi/lib.rs
new file mode 100644
index 000000000..125e6da99
--- /dev/null
+++ b/extensions/ffi/lib.rs
@@ -0,0 +1,397 @@
+// Copyright 2021 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::bad_resource_id;
+use deno_core::error::AnyError;
+use deno_core::include_js_files;
+use deno_core::op_sync;
+use deno_core::serde_json::json;
+use deno_core::serde_json::Value;
+use deno_core::Extension;
+use deno_core::OpState;
+use deno_core::Resource;
+use deno_core::ResourceId;
+use dlopen::raw::Library;
+use libffi::middle::Arg;
+use serde::Deserialize;
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::ffi::c_void;
+use std::rc::Rc;
+
+pub struct Unstable(pub bool);
+
+fn check_unstable(state: &OpState, api_name: &str) {
+ let unstable = state.borrow::<Unstable>();
+
+ if !unstable.0 {
+ eprintln!(
+ "Unstable API '{}'. The --unstable flag must be provided.",
+ api_name
+ );
+ std::process::exit(70);
+ }
+}
+
+pub trait FfiPermissions {
+ fn check(&mut self, path: &str) -> Result<(), AnyError>;
+}
+
+pub struct NoFfiPermissions;
+
+impl FfiPermissions for NoFfiPermissions {
+ fn check(&mut self, _path: &str) -> Result<(), AnyError> {
+ Ok(())
+ }
+}
+
+struct Symbol {
+ cif: libffi::middle::Cif,
+ ptr: libffi::middle::CodePtr,
+ parameter_types: Vec<NativeType>,
+ result_type: NativeType,
+}
+
+struct DynamicLibraryResource {
+ lib: Library,
+ symbols: HashMap<String, Symbol>,
+}
+
+impl Resource for DynamicLibraryResource {
+ fn name(&self) -> Cow<str> {
+ "dynamicLibrary".into()
+ }
+
+ fn close(self: Rc<Self>) {
+ drop(self)
+ }
+}
+
+impl DynamicLibraryResource {
+ fn register(
+ &mut self,
+ symbol: String,
+ foreign_fn: ForeignFunction,
+ ) -> Result<(), AnyError> {
+ let fn_ptr = unsafe { self.lib.symbol::<*const c_void>(&symbol) }?;
+ let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
+ let parameter_types =
+ foreign_fn.parameters.into_iter().map(NativeType::from);
+ let result_type = NativeType::from(foreign_fn.result);
+ let cif = libffi::middle::Cif::new(
+ parameter_types.clone().map(libffi::middle::Type::from),
+ result_type.into(),
+ );
+
+ self.symbols.insert(
+ symbol,
+ Symbol {
+ cif,
+ ptr,
+ parameter_types: parameter_types.collect(),
+ result_type,
+ },
+ );
+
+ Ok(())
+ }
+}
+
+pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
+ Extension::builder()
+ .js(include_js_files!(
+ prefix "deno:extensions/ffi",
+ "00_ffi.js",
+ ))
+ .ops(vec![
+ ("op_ffi_load", op_sync(op_ffi_load::<P>)),
+ ("op_ffi_call", op_sync(op_ffi_call)),
+ ])
+ .state(move |state| {
+ // Stolen from deno_webgpu, is there a better option?
+ state.put(Unstable(unstable));
+ Ok(())
+ })
+ .build()
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
+#[serde(rename_all = "lowercase")]
+enum NativeType {
+ Void,
+ U8,
+ I8,
+ U16,
+ I16,
+ U32,
+ I32,
+ U64,
+ I64,
+ USize,
+ ISize,
+ F32,
+ F64,
+}
+
+impl From<NativeType> for libffi::middle::Type {
+ fn from(native_type: NativeType) -> Self {
+ match native_type {
+ NativeType::Void => libffi::middle::Type::void(),
+ NativeType::U8 => libffi::middle::Type::u8(),
+ NativeType::I8 => libffi::middle::Type::i8(),
+ NativeType::U16 => libffi::middle::Type::u16(),
+ NativeType::I16 => libffi::middle::Type::i16(),
+ NativeType::U32 => libffi::middle::Type::u32(),
+ NativeType::I32 => libffi::middle::Type::i32(),
+ NativeType::U64 => libffi::middle::Type::u64(),
+ NativeType::I64 => libffi::middle::Type::i64(),
+ NativeType::USize => libffi::middle::Type::usize(),
+ NativeType::ISize => libffi::middle::Type::isize(),
+ NativeType::F32 => libffi::middle::Type::f32(),
+ NativeType::F64 => libffi::middle::Type::f64(),
+ }
+ }
+}
+
+impl From<String> for NativeType {
+ fn from(string: String) -> Self {
+ match string.as_str() {
+ "void" => NativeType::Void,
+ "u8" => NativeType::U8,
+ "i8" => NativeType::I8,
+ "u16" => NativeType::U16,
+ "i16" => NativeType::I16,
+ "u32" => NativeType::U32,
+ "i32" => NativeType::I32,
+ "u64" => NativeType::U64,
+ "i64" => NativeType::I64,
+ "usize" => NativeType::USize,
+ "isize" => NativeType::ISize,
+ "f32" => NativeType::F32,
+ "f64" => NativeType::F64,
+ _ => unimplemented!(),
+ }
+ }
+}
+
+#[repr(C)]
+union NativeValue {
+ void_value: (),
+ u8_value: u8,
+ i8_value: i8,
+ u16_value: u16,
+ i16_value: i16,
+ u32_value: u32,
+ i32_value: i32,
+ u64_value: u64,
+ i64_value: i64,
+ usize_value: usize,
+ isize_value: isize,
+ f32_value: f32,
+ f64_value: f64,
+}
+
+impl NativeValue {
+ fn new(native_type: NativeType, value: Value) -> Self {
+ match native_type {
+ NativeType::Void => Self { void_value: () },
+ NativeType::U8 => Self {
+ u8_value: value_as_uint::<u8>(value),
+ },
+ NativeType::I8 => Self {
+ i8_value: value_as_int::<i8>(value),
+ },
+ NativeType::U16 => Self {
+ u16_value: value_as_uint::<u16>(value),
+ },
+ NativeType::I16 => Self {
+ i16_value: value_as_int::<i16>(value),
+ },
+ NativeType::U32 => Self {
+ u32_value: value_as_uint::<u32>(value),
+ },
+ NativeType::I32 => Self {
+ i32_value: value_as_int::<i32>(value),
+ },
+ NativeType::U64 => Self {
+ u64_value: value_as_uint::<u64>(value),
+ },
+ NativeType::I64 => Self {
+ i64_value: value_as_int::<i64>(value),
+ },
+ NativeType::USize => Self {
+ usize_value: value_as_uint::<usize>(value),
+ },
+ NativeType::ISize => Self {
+ isize_value: value_as_int::<isize>(value),
+ },
+ NativeType::F32 => Self {
+ f32_value: value_as_f32(value),
+ },
+ NativeType::F64 => Self {
+ f64_value: value_as_f64(value),
+ },
+ }
+ }
+
+ unsafe fn as_arg(&self, native_type: NativeType) -> Arg {
+ match native_type {
+ NativeType::Void => Arg::new(&self.void_value),
+ NativeType::U8 => Arg::new(&self.u8_value),
+ NativeType::I8 => Arg::new(&self.i8_value),
+ NativeType::U16 => Arg::new(&self.u16_value),
+ NativeType::I16 => Arg::new(&self.i16_value),
+ NativeType::U32 => Arg::new(&self.u32_value),
+ NativeType::I32 => Arg::new(&self.i32_value),
+ NativeType::U64 => Arg::new(&self.u64_value),
+ NativeType::I64 => Arg::new(&self.i64_value),
+ NativeType::USize => Arg::new(&self.usize_value),
+ NativeType::ISize => Arg::new(&self.isize_value),
+ NativeType::F32 => Arg::new(&self.f32_value),
+ NativeType::F64 => Arg::new(&self.f64_value),
+ }
+ }
+}
+
+fn value_as_uint<T: TryFrom<u64>>(value: Value) -> T {
+ value
+ .as_u64()
+ .and_then(|v| T::try_from(v).ok())
+ .expect("Expected ffi arg value to be an unsigned integer")
+}
+
+fn value_as_int<T: TryFrom<i64>>(value: Value) -> T {
+ value
+ .as_i64()
+ .and_then(|v| T::try_from(v).ok())
+ .expect("Expected ffi arg value to be a signed integer")
+}
+
+fn value_as_f32(value: Value) -> f32 {
+ value_as_f64(value) as f32
+}
+
+fn value_as_f64(value: Value) -> f64 {
+ value
+ .as_f64()
+ .expect("Expected ffi arg value to be a float")
+}
+
+#[derive(Deserialize, Debug)]
+struct ForeignFunction {
+ parameters: Vec<String>,
+ result: String,
+}
+
+#[derive(Deserialize, Debug)]
+struct FfiLoadArgs {
+ path: String,
+ symbols: HashMap<String, ForeignFunction>,
+}
+
+fn op_ffi_load<FP>(
+ state: &mut deno_core::OpState,
+ args: FfiLoadArgs,
+ _: (),
+) -> Result<ResourceId, AnyError>
+where
+ FP: FfiPermissions + 'static,
+{
+ check_unstable(state, "Deno.dlopen");
+ let permissions = state.borrow_mut::<FP>();
+ permissions.check(&args.path)?;
+
+ let lib = Library::open(args.path)?;
+ let mut resource = DynamicLibraryResource {
+ lib,
+ symbols: HashMap::new(),
+ };
+
+ for (symbol, foreign_fn) in args.symbols {
+ resource.register(symbol, foreign_fn)?;
+ }
+
+ Ok(state.resource_table.add(resource))
+}
+
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct FfiCallArgs {
+ rid: ResourceId,
+ symbol: String,
+ parameters: Vec<Value>,
+}
+
+fn op_ffi_call(
+ state: &mut deno_core::OpState,
+ args: FfiCallArgs,
+ _: (),
+) -> Result<Value, AnyError> {
+ let resource = state
+ .resource_table
+ .get::<DynamicLibraryResource>(args.rid)
+ .ok_or_else(bad_resource_id)?;
+
+ let symbol = resource
+ .symbols
+ .get(&args.symbol)
+ .ok_or_else(bad_resource_id)?;
+
+ let native_values = symbol
+ .parameter_types
+ .iter()
+ .zip(args.parameters.into_iter())
+ .map(|(&native_type, value)| NativeValue::new(native_type, value))
+ .collect::<Vec<_>>();
+
+ let call_args = symbol
+ .parameter_types
+ .iter()
+ .zip(native_values.iter())
+ .map(|(&native_type, native_value)| unsafe {
+ native_value.as_arg(native_type)
+ })
+ .collect::<Vec<_>>();
+
+ Ok(match symbol.result_type {
+ NativeType::Void => {
+ json!(unsafe { symbol.cif.call::<()>(symbol.ptr, &call_args) })
+ }
+ NativeType::U8 => {
+ json!(unsafe { symbol.cif.call::<u8>(symbol.ptr, &call_args) })
+ }
+ NativeType::I8 => {
+ json!(unsafe { symbol.cif.call::<i8>(symbol.ptr, &call_args) })
+ }
+ NativeType::U16 => {
+ json!(unsafe { symbol.cif.call::<u16>(symbol.ptr, &call_args) })
+ }
+ NativeType::I16 => {
+ json!(unsafe { symbol.cif.call::<i16>(symbol.ptr, &call_args) })
+ }
+ NativeType::U32 => {
+ json!(unsafe { symbol.cif.call::<u32>(symbol.ptr, &call_args) })
+ }
+ NativeType::I32 => {
+ json!(unsafe { symbol.cif.call::<i32>(symbol.ptr, &call_args) })
+ }
+ NativeType::U64 => {
+ json!(unsafe { symbol.cif.call::<u64>(symbol.ptr, &call_args) })
+ }
+ NativeType::I64 => {
+ json!(unsafe { symbol.cif.call::<i64>(symbol.ptr, &call_args) })
+ }
+ NativeType::USize => {
+ json!(unsafe { symbol.cif.call::<usize>(symbol.ptr, &call_args) })
+ }
+ NativeType::ISize => {
+ json!(unsafe { symbol.cif.call::<isize>(symbol.ptr, &call_args) })
+ }
+ NativeType::F32 => {
+ json!(unsafe { symbol.cif.call::<f32>(symbol.ptr, &call_args) })
+ }
+ NativeType::F64 => {
+ json!(unsafe { symbol.cif.call::<f64>(symbol.ptr, &call_args) })
+ }
+ })
+}