summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/ffi/00_ffi.js367
-rw-r--r--ext/ffi/lib.rs1228
2 files changed, 1141 insertions, 454 deletions
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js
index fb4fcbfb8..d29e83fce 100644
--- a/ext/ffi/00_ffi.js
+++ b/ext/ffi/00_ffi.js
@@ -6,18 +6,18 @@
const __bootstrap = window.__bootstrap;
const {
ArrayBufferPrototype,
- Uint8Array,
+ ArrayPrototypePush,
+ ArrayPrototypeSome,
BigInt,
- Number,
+ NumberIsFinite,
+ NumberIsInteger,
ObjectDefineProperty,
ObjectPrototypeIsPrototypeOf,
+ PromisePrototypeThen,
TypeError,
+ Uint8Array,
} = window.__bootstrap.primordials;
- function pack64(value) {
- return [Number(value >> 32n) >>> 0, Number(value & 0xFFFFFFFFn)];
- }
-
function unpackU64([hi, lo]) {
return BigInt(hi) << 32n | BigInt(lo);
}
@@ -37,77 +37,77 @@
getUint8(offset = 0) {
return core.opSync(
"op_ffi_read_u8",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getInt8(offset = 0) {
return core.opSync(
"op_ffi_read_i8",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getUint16(offset = 0) {
return core.opSync(
"op_ffi_read_u16",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getInt16(offset = 0) {
return core.opSync(
"op_ffi_read_i16",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getUint32(offset = 0) {
return core.opSync(
"op_ffi_read_u32",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getInt32(offset = 0) {
return core.opSync(
"op_ffi_read_i32",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getBigUint64(offset = 0) {
- return unpackU64(core.opSync(
+ return core.opSync(
"op_ffi_read_u64",
- pack64(this.pointer.value + BigInt(offset)),
- ));
+ this.pointer.value + BigInt(offset),
+ );
}
getBigInt64(offset = 0) {
- return unpackI64(core.opSync(
+ return core.opSync(
"op_ffi_read_u64",
- pack64(this.pointer.value + BigInt(offset)),
- ));
+ this.pointer.value + BigInt(offset),
+ );
}
getFloat32(offset = 0) {
return core.opSync(
"op_ffi_read_f32",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getFloat64(offset = 0) {
return core.opSync(
"op_ffi_read_f64",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
getCString(offset = 0) {
return core.opSync(
"op_ffi_cstr_read",
- pack64(this.pointer.value + BigInt(offset)),
+ this.pointer.value + BigInt(offset),
);
}
@@ -118,11 +118,12 @@
}
copyInto(destination, offset = 0) {
- core.opSync("op_ffi_buf_copy_into", [
- pack64(this.pointer.value + BigInt(offset)),
+ core.opSync(
+ "op_ffi_buf_copy_into",
+ this.pointer.value + BigInt(offset),
destination,
destination.byteLength,
- ]);
+ );
}
}
@@ -130,12 +131,15 @@
value;
constructor(value) {
+ if (typeof value === "number") {
+ value = BigInt(value);
+ }
this.value = value;
}
static of(typedArray) {
return new UnsafePointer(
- unpackU64(core.opSync("op_ffi_ptr_of", typedArray)),
+ core.opSync("op_ffi_ptr_of", typedArray),
);
}
@@ -147,58 +151,118 @@
function prepareArgs(types, args) {
const parameters = [];
- const buffers = [];
+
+ if (types.length !== args.length) {
+ throw new TypeError("Invalid FFI call, parameter vs args count mismatch");
+ }
for (let i = 0; i < types.length; i++) {
const type = types[i];
const arg = args[i];
- if (type === "pointer") {
+ if (type === "u8" || type === "u16" || type === "u32") {
+ if (!NumberIsInteger(arg) || arg < 0) {
+ throw new TypeError(
+ `Expected FFI argument to be an unsigned integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "i8" || type === "i16" || type === "i32") {
+ if (!NumberIsInteger(arg)) {
+ throw new TypeError(
+ `Expected FFI argument to be a signed integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "u64" || type === "usize") {
+ if (
+ !(NumberIsInteger(arg) && arg >= 0 ||
+ typeof arg === "bigint" && 0n <= arg && arg <= 0xffffffffffffffffn)
+ ) {
+ throw new TypeError(
+ `Expected FFI argument to be an unsigned integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type == "i64" || type === "isize") {
+ if (
+ !(NumberIsInteger(arg) ||
+ typeof arg === "bigint" && -1n * 2n ** 63n <= arg &&
+ arg <= 2n ** 63n - 1n)
+ ) {
+ throw new TypeError(
+ `Expected FFI argument to be a signed integer, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "f32" || type === "f64") {
+ if (!NumberIsFinite(arg)) {
+ throw new TypeError(
+ `Expected FFI argument to be a number, but got '${arg}'`,
+ );
+ }
+ ArrayPrototypePush(parameters, arg);
+ } else if (type === "pointer") {
if (
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, arg?.buffer) &&
arg.byteLength !== undefined
) {
- parameters.push(buffers.length);
- buffers.push(arg);
+ ArrayPrototypePush(parameters, arg);
} else if (ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)) {
- parameters.push(pack64(arg.value));
- buffers.push(undefined);
+ ArrayPrototypePush(parameters, arg.value);
} else if (arg === null) {
- parameters.push(null);
- buffers.push(undefined);
+ ArrayPrototypePush(parameters, null);
} else {
throw new TypeError(
- "Invalid ffi arg value, expected TypedArray, UnsafePointer or null",
+ "Expected FFI argument to be TypedArray, UnsafePointer or null",
);
}
- } else if (typeof arg === "bigint") {
- if (arg > 0xffffffffffffffffn) {
+ } else if (
+ typeof type === "object" && type !== null && "function" in type
+ ) {
+ if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, arg)) {
+ // Own registered callback, pass the pointer value
+ ArrayPrototypePush(parameters, arg.pointer.value);
+ } else if (arg === null) {
+ // nullptr
+ ArrayPrototypePush(parameters, null);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafeFnPointerPrototype, arg)
+ ) {
+ // Foreign function, pass the pointer value
+ ArrayPrototypePush(parameters, arg.pointer.value);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, arg)
+ ) {
+ // Foreign function, pass the pointer value
+ ArrayPrototypePush(parameters, arg.value);
+ } else {
throw new TypeError(
- "Invalid ffi arg value, it needs to be less than 0xffffffffffffffff",
+ "Expected FFI argument to be UnsafeCallback, UnsafeFnPointer, UnsafePointer or null",
);
}
-
- parameters.push(pack64(arg));
} else {
- parameters.push(arg);
+ throw new TypeError(`Invalid FFI argument type '${type}'`);
}
}
- return { parameters, buffers };
+ return parameters;
}
- function unpackResult(type, result) {
+ function unpackNonblockingReturnValue(type, result) {
+ if (
+ typeof type === "object" && type !== null && "function" in type ||
+ type === "pointer"
+ ) {
+ return new UnsafePointer(unpackU64(result));
+ }
switch (type) {
- case "pointer":
- return new UnsafePointer(unpackU64(result));
- case "u64":
- return unpackU64(result);
+ case "isize":
case "i64":
return unpackI64(result);
case "usize":
+ case "u64":
return unpackU64(result);
- case "isize":
- return unpackI64(result);
default:
return result;
}
@@ -214,33 +278,39 @@
}
call(...args) {
- const { parameters, buffers } = prepareArgs(
+ const parameters = prepareArgs(
this.definition.parameters,
args,
);
+ const resultType = this.definition.result;
if (this.definition.nonblocking) {
- const promise = core.opAsync("op_ffi_call_ptr_nonblocking", {
- pointer: pack64(this.pointer.value),
- def: this.definition,
+ const promise = core.opAsync(
+ "op_ffi_call_ptr_nonblocking",
+ this.pointer.value,
+ this.definition,
parameters,
- buffers,
- });
+ );
- if (this.definition.result === "pointer") {
- return promise.then((value) => new UnsafePointer(unpackU64(value)));
+ if (
+ isReturnedAsBigInt(resultType)
+ ) {
+ return PromisePrototypeThen(
+ promise,
+ (result) => unpackNonblockingReturnValue(resultType, result),
+ );
}
return promise;
} else {
- const result = core.opSync("op_ffi_call_ptr", {
- pointer: pack64(this.pointer.value),
- def: this.definition,
+ const result = core.opSync(
+ "op_ffi_call_ptr",
+ this.pointer.value,
+ this.definition,
parameters,
- buffers,
- });
+ );
- if (this.definition.result === "pointer") {
- return new UnsafePointer(unpackU64(result));
+ if (isPointerType(resultType)) {
+ return new UnsafePointer(result);
}
return result;
@@ -248,6 +318,111 @@
}
}
+ const UnsafeFnPointerPrototype = UnsafeFnPointer.prototype;
+
+ function isPointerType(type) {
+ return type === "pointer" ||
+ typeof type === "object" && type !== null && "function" in type;
+ }
+
+ function isReturnedAsBigInt(type) {
+ return isPointerType(type) || type === "u64" || type === "i64" ||
+ type === "usize" || type === "isize";
+ }
+
+ function prepareUnsafeCallbackParameters(types, args) {
+ const parameters = [];
+ if (types.length === 0) {
+ return parameters;
+ }
+
+ for (let i = 0; i < types.length; i++) {
+ const type = types[i];
+ const arg = args[i];
+ ArrayPrototypePush(
+ parameters,
+ isPointerType(type) ? new UnsafePointer(arg) : arg,
+ );
+ }
+
+ return parameters;
+ }
+
+ function unwrapUnsafeCallbackReturnValue(result) {
+ if (
+ ObjectPrototypeIsPrototypeOf(UnsafePointerPrototype, result)
+ ) {
+ // Foreign function, return the pointer value
+ ArrayPrototypePush(parameters, result.value);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafeFnPointerPrototype, result)
+ ) {
+ // Foreign function, return the pointer value
+ ArrayPrototypePush(parameters, result.pointer.value);
+ } else if (
+ ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, result)
+ ) {
+ // Own registered callback, return the pointer value.
+ // Note that returning the ResourceId here would not work as
+ // the Rust side code cannot access OpState to get the resource.
+ ArrayPrototypePush(parameters, result.pointer.value);
+ }
+ return result;
+ }
+
+ function createInternalCallback(definition, callback) {
+ const mustUnwrap = isPointerType(definition.result);
+ return (...args) => {
+ const convertedArgs = prepareUnsafeCallbackParameters(
+ definition.parameters,
+ args,
+ );
+ const result = callback(...convertedArgs);
+ if (mustUnwrap) {
+ return unwrapUnsafeCallbackReturnValue(result);
+ }
+ return result;
+ };
+ }
+
+ class UnsafeCallback {
+ #rid;
+ #internal;
+ definition;
+ callback;
+ pointer;
+
+ constructor(definition, callback) {
+ if (definition.nonblocking) {
+ throw new TypeError(
+ "Invalid UnsafeCallback, cannot be nonblocking",
+ );
+ }
+ const needsWrapping = isPointerType(definition.result) ||
+ ArrayPrototypeSome(definition.parameters, isPointerType);
+ const internalCallback = needsWrapping
+ ? createInternalCallback(definition, callback)
+ : callback;
+
+ const [rid, pointer] = core.opSync(
+ "op_ffi_unsafe_callback_create",
+ definition,
+ internalCallback,
+ );
+ this.#rid = rid;
+ this.pointer = new UnsafePointer(pointer);
+ this.#internal = internalCallback;
+ this.definition = definition;
+ this.callback = callback;
+ }
+
+ close() {
+ core.close(this.#rid);
+ }
+ }
+
+ const UnsafeCallbackPrototype = UnsafeCallback.prototype;
+
class DynamicLibrary {
#rid;
symbols = {};
@@ -267,19 +442,12 @@
const name = symbols[symbol].name || symbol;
let value = core.opSync(
"op_ffi_get_static",
- {
- rid: this.#rid,
- name,
- type,
- },
+ this.#rid,
+ name,
+ type,
);
- if (type === "pointer" || type === "u64") {
- value = unpackU64(value);
- if (type === "pointer") {
- value = new UnsafePointer(value);
- }
- } else if (type === "i64") {
- value = unpackI64(value);
+ if (type === "pointer") {
+ value = new UnsafePointer(value);
}
ObjectDefineProperty(
this.symbols,
@@ -298,33 +466,47 @@
const types = symbols[symbol].parameters;
const resultType = symbols[symbol].result;
- const fn = (...args) => {
- const { parameters, buffers } = prepareArgs(types, args);
+ let fn;
+ if (isNonBlocking) {
+ const needsUnpacking = isReturnedAsBigInt(resultType);
+ fn = (...args) => {
+ const parameters = prepareArgs(types, args);
- if (isNonBlocking) {
- const promise = core.opAsync("op_ffi_call_nonblocking", {
- rid: this.#rid,
+ const promise = core.opAsync(
+ "op_ffi_call_nonblocking",
+ this.#rid,
symbol,
parameters,
- buffers,
- });
+ );
- if (resultType === "pointer") {
- return promise.then((result) => unpackResult(resultType, result));
+ if (needsUnpacking) {
+ return PromisePrototypeThen(
+ promise,
+ (result) => unpackNonblockingReturnValue(resultType, result),
+ );
}
return promise;
- } else {
- const result = core.opSync("op_ffi_call", {
- rid: this.#rid,
+ };
+ } else {
+ const mustWrap = isPointerType(resultType);
+ fn = (...args) => {
+ const parameters = prepareArgs(types, args);
+
+ const result = core.opSync(
+ "op_ffi_call",
+ this.#rid,
symbol,
parameters,
- buffers,
- });
+ );
- return unpackResult(resultType, result);
- }
- };
+ if (mustWrap) {
+ return new UnsafePointer(result);
+ }
+
+ return result;
+ };
+ }
ObjectDefineProperty(
this.symbols,
@@ -352,6 +534,7 @@
window.__bootstrap.ffi = {
dlopen,
+ UnsafeCallback,
UnsafePointer,
UnsafePointerView,
UnsafeFnPointer,
diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs
index 8a1a75b1d..691d44460 100644
--- a/ext/ffi/lib.rs
+++ b/ext/ffi/lib.rs
@@ -1,16 +1,19 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::bad_resource_id;
+use core::ptr::NonNull;
+use deno_core::anyhow::anyhow;
use deno_core::error::generic_error;
use deno_core::error::range_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
+use deno_core::futures::Future;
use deno_core::include_js_files;
use deno_core::op;
-use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
+use deno_core::serde_v8;
+use deno_core::v8;
use deno_core::Extension;
use deno_core::OpState;
use deno_core::Resource;
@@ -18,6 +21,8 @@ use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use dlopen::raw::Library;
use libffi::middle::Arg;
+use libffi::middle::Cif;
+use libffi::raw::*;
use serde::Deserialize;
use serde::Serialize;
use std::borrow::Cow;
@@ -31,6 +36,10 @@ use std::path::PathBuf;
use std::ptr;
use std::rc::Rc;
+thread_local! {
+ static IS_ISOLATE_THREAD: RefCell<bool> = RefCell::new(false);
+}
+
pub struct Unstable(pub bool);
fn check_unstable(state: &OpState, api_name: &str) {
@@ -66,6 +75,32 @@ struct Symbol {
unsafe impl Send for Symbol {}
unsafe impl Sync for Symbol {}
+#[derive(Clone)]
+struct PtrSymbol {
+ cif: libffi::middle::Cif,
+ ptr: libffi::middle::CodePtr,
+}
+
+impl PtrSymbol {
+ fn new(fn_ptr: u64, def: &ForeignFunction) -> Self {
+ let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
+ let cif = libffi::middle::Cif::new(
+ def
+ .parameters
+ .clone()
+ .into_iter()
+ .map(libffi::middle::Type::from),
+ def.result.into(),
+ );
+
+ Self { cif, ptr }
+ }
+}
+
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for PtrSymbol {}
+unsafe impl Sync for PtrSymbol {}
+
struct DynamicLibraryResource {
lib: Library,
symbols: HashMap<String, Symbol>,
@@ -87,6 +122,7 @@ impl DynamicLibraryResource {
name: String,
foreign_fn: ForeignFunction,
) -> Result<(), AnyError> {
+ IS_ISOLATE_THREAD.with(|s| s.replace(true));
let symbol = match &foreign_fn.name {
Some(symbol) => symbol,
None => &name,
@@ -163,6 +199,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
op_ffi_read_u64::decl::<P>(),
op_ffi_read_f32::decl::<P>(),
op_ffi_read_f64::decl::<P>(),
+ op_ffi_unsafe_callback_create::decl::<P>(),
])
.state(move |state| {
// Stolen from deno_webgpu, is there a better option?
@@ -172,6 +209,8 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
.build()
}
+/// Defines the accepted types that can be used as
+/// parameters and return values in FFI.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
enum NativeType {
@@ -189,6 +228,7 @@ enum NativeType {
F32,
F64,
Pointer,
+ Function {},
}
impl From<NativeType> for libffi::middle::Type {
@@ -208,10 +248,13 @@ impl From<NativeType> for libffi::middle::Type {
NativeType::F32 => libffi::middle::Type::f32(),
NativeType::F64 => libffi::middle::Type::f64(),
NativeType::Pointer => libffi::middle::Type::pointer(),
+ NativeType::Function {} => libffi::middle::Type::pointer(),
}
}
}
+/// Intermediate format for easy translation from NativeType + V8 value
+/// to libffi argument types.
#[repr(C)]
union NativeValue {
void_value: (),
@@ -231,68 +274,9 @@ union NativeValue {
}
impl NativeValue {
- fn new(native_type: NativeType, value: Value) -> Result<Self, AnyError> {
- let value = 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)?,
- },
- NativeType::Pointer => {
- if value.is_null() {
- Self {
- pointer: ptr::null(),
- }
- } else {
- Self {
- pointer: u64::from(serde_json::from_value::<U32x2>(value)?)
- as *const u8,
- }
- }
- }
- };
- Ok(value)
- }
-
- fn buffer(ptr: *const u8) -> Self {
- Self { pointer: ptr }
- }
-
unsafe fn as_arg(&self, native_type: NativeType) -> Arg {
match native_type {
- NativeType::Void => Arg::new(&self.void_value),
+ NativeType::Void => unreachable!(),
NativeType::U8 => Arg::new(&self.u8_value),
NativeType::I8 => Arg::new(&self.i8_value),
NativeType::U16 => Arg::new(&self.u16_value),
@@ -305,56 +289,123 @@ impl NativeValue {
NativeType::ISize => Arg::new(&self.isize_value),
NativeType::F32 => Arg::new(&self.f32_value),
NativeType::F64 => Arg::new(&self.f64_value),
- NativeType::Pointer => Arg::new(&self.pointer),
+ NativeType::Pointer | NativeType::Function {} => Arg::new(&self.pointer),
}
}
-}
-
-fn value_as_uint<T: TryFrom<u64>>(value: Value) -> Result<T, AnyError> {
- if value.is_array() {
- let value = U32x2::try_from(value)?;
- return T::try_from(u64::from(value)).map_err(|_| type_error(format!("Found U32x2 FFI argument but it could not be converted to an unsigned integer, got {:?}", value)));
- }
-
- match value.as_u64().and_then(|v| T::try_from(v).ok()) {
- Some(value) => Ok(value),
- None => Err(type_error(format!(
- "Expected FFI argument to be an unsigned integer, but got {:?}",
- value
- ))),
- }
-}
-fn value_as_int<T: TryFrom<i64>>(value: Value) -> Result<T, AnyError> {
- if value.is_array() {
- let value = U32x2::try_from(value)?;
- return T::try_from(u64::from(value) as i64).map_err(|_| type_error(format!("Found U32x2 FFI argument but it could not be converted to a signed integer, got {:?}", value)));
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ unsafe fn to_value(&self, native_type: NativeType) -> Value {
+ match native_type {
+ NativeType::Void => Value::Null,
+ NativeType::U8 => Value::from(self.u8_value),
+ NativeType::I8 => Value::from(self.i8_value),
+ NativeType::U16 => Value::from(self.u16_value),
+ NativeType::I16 => Value::from(self.i16_value),
+ NativeType::U32 => Value::from(self.u32_value),
+ NativeType::I32 => Value::from(self.i32_value),
+ NativeType::U64 => {
+ json!(U32x2::from(self.u64_value))
+ }
+ NativeType::I64 => {
+ json!(U32x2::from(self.i64_value as u64))
+ }
+ NativeType::USize => {
+ json!(U32x2::from(self.usize_value as u64))
+ }
+ NativeType::ISize => {
+ json!(U32x2::from(self.isize_value as u64))
+ }
+ NativeType::F32 => Value::from(self.f32_value),
+ NativeType::F64 => Value::from(self.f64_value),
+ NativeType::Pointer | NativeType::Function {} => {
+ json!(U32x2::from(self.pointer as u64))
+ }
+ }
}
- match value.as_i64().and_then(|v| T::try_from(v).ok()) {
- Some(value) => Ok(value),
- None => Err(type_error(format!(
- "Expected FFI argument to be a signed integer, but got {:?}",
- value
- ))),
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ unsafe fn to_v8<'scope>(
+ &self,
+ scope: &mut v8::HandleScope<'scope>,
+ native_type: NativeType,
+ ) -> serde_v8::Value<'scope> {
+ match native_type {
+ NativeType::Void => {
+ let local_value: v8::Local<v8::Value> = v8::undefined(scope).into();
+ local_value.into()
+ }
+ NativeType::U8 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u8_value as u32).into();
+ local_value.into()
+ }
+ NativeType::I8 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i8_value as i32).into();
+ local_value.into()
+ }
+ NativeType::U16 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u16_value as u32).into();
+ local_value.into()
+ }
+ NativeType::I16 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i16_value as i32).into();
+ local_value.into()
+ }
+ NativeType::U32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new_from_unsigned(scope, self.u32_value).into();
+ local_value.into()
+ }
+ NativeType::I32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Integer::new(scope, self.i32_value).into();
+ local_value.into()
+ }
+ NativeType::U64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, self.u64_value).into();
+ local_value.into()
+ }
+ NativeType::I64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_i64(scope, self.i64_value).into();
+ local_value.into()
+ }
+ NativeType::USize => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, self.usize_value as u64).into();
+ local_value.into()
+ }
+ NativeType::ISize => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_i64(scope, self.isize_value as i64).into();
+ local_value.into()
+ }
+ NativeType::F32 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Number::new(scope, self.f32_value as f64).into();
+ local_value.into()
+ }
+ NativeType::F64 => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Number::new(scope, self.f64_value).into();
+ local_value.into()
+ }
+ NativeType::Pointer | NativeType::Function {} => {
+ let local_value: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, self.pointer as u64).into();
+ local_value.into()
+ }
+ }
}
}
-fn value_as_f32(value: Value) -> Result<f32, AnyError> {
- Ok(value_as_f64(value)? as f32)
-}
+unsafe impl Send for NativeValue {}
-fn value_as_f64(value: Value) -> Result<f64, AnyError> {
- match value.as_f64() {
- Some(value) => Ok(value),
- None => Err(type_error(format!(
- "Expected FFI argument to be a double, but got {:?}",
- value
- ))),
- }
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
+#[derive(Serialize, Debug, Clone, Copy)]
struct U32x2(u32, u32);
impl From<u64> for U32x2 {
@@ -363,31 +414,6 @@ impl From<u64> for U32x2 {
}
}
-impl From<U32x2> for u64 {
- fn from(value: U32x2) -> Self {
- (value.0 as u64) << 32 | value.1 as u64
- }
-}
-
-impl TryFrom<Value> for U32x2 {
- type Error = AnyError;
-
- fn try_from(value: Value) -> Result<Self, Self::Error> {
- if let Some(value) = value.as_array() {
- if let Some(hi) = value[0].as_u64() {
- if let Some(lo) = value[1].as_u64() {
- return Ok(U32x2(hi as u32, lo as u32));
- }
- }
- }
-
- Err(type_error(format!(
- "Expected FFI argument to be a signed integer, but got {:?}",
- value
- )))
- }
-}
-
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ForeignFunction {
@@ -535,322 +561,796 @@ where
Ok(state.resource_table.add(resource))
}
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FfiCallArgs {
- rid: ResourceId,
- symbol: String,
- parameters: Vec<Value>,
- buffers: Vec<Option<ZeroCopyBuf>>,
-}
+fn ffi_parse_args<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ args: serde_v8::Value<'scope>,
+ parameter_types: &[NativeType],
+) -> Result<Vec<NativeValue>, AnyError>
+where
+ 'scope: 'scope,
+{
+ if parameter_types.is_empty() {
+ return Ok(vec![]);
+ }
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FfiCallPtrArgs {
- pointer: U32x2,
- def: ForeignFunction,
- parameters: Vec<Value>,
- buffers: Vec<Option<ZeroCopyBuf>>,
-}
+ let args = v8::Local::<v8::Array>::try_from(args.v8_value)
+ .map_err(|_| type_error("Invalid FFI parameters, expected Array"))?;
+ let mut ffi_args: Vec<NativeValue> =
+ Vec::with_capacity(parameter_types.len());
-impl From<FfiCallPtrArgs> for FfiCallArgs {
- fn from(args: FfiCallPtrArgs) -> Self {
- FfiCallArgs {
- rid: 0,
- symbol: String::new(),
- parameters: args.parameters,
- buffers: args.buffers,
+ for (index, native_type) in parameter_types.iter().enumerate() {
+ let value = args.get_index(scope, index as u32).unwrap();
+ match native_type {
+ NativeType::Void => {
+ unreachable!();
+ }
+ NativeType::U8 => {
+ let value = value
+ .uint32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI u8 type, expected number"))?
+ as u8;
+ ffi_args.push(NativeValue { u8_value: value });
+ }
+ NativeType::I8 => {
+ let value = value
+ .int32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI i8 type, expected number"))?
+ as i8;
+ ffi_args.push(NativeValue { i8_value: value });
+ }
+ NativeType::U16 => {
+ let value = value
+ .uint32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI u16 type, expected number"))?
+ as u16;
+ ffi_args.push(NativeValue { u16_value: value });
+ }
+ NativeType::I16 => {
+ let value = value
+ .int32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI i16 type, expected number"))?
+ as i16;
+ ffi_args.push(NativeValue { i16_value: value });
+ }
+ NativeType::U32 => {
+ let value = value
+ .uint32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI u32 type, expected number"))?
+ as u32;
+ ffi_args.push(NativeValue { u32_value: value });
+ }
+ NativeType::I32 => {
+ let value = value
+ .int32_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI i32 type, expected number"))?
+ as i32;
+ ffi_args.push(NativeValue { i32_value: value });
+ }
+ NativeType::U64 => {
+ let value: u64 = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.u64_value().0
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI u64 type, expected number")
+ })? as u64
+ };
+ ffi_args.push(NativeValue { u64_value: value });
+ }
+ NativeType::I64 => {
+ let value: i64 = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.i64_value().0
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI i64 type, expected number")
+ })? as i64
+ };
+ ffi_args.push(NativeValue { i64_value: value });
+ }
+ NativeType::USize => {
+ let value: usize = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.u64_value().0 as usize
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI usize type, expected number")
+ })? as usize
+ };
+ ffi_args.push(NativeValue { usize_value: value });
+ }
+ NativeType::ISize => {
+ let value: isize = if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value)?;
+ value.i64_value().0 as isize
+ } else {
+ value.integer_value(scope).ok_or_else(|| {
+ type_error("Invalid FFI isize type, expected number")
+ })? as isize
+ };
+ ffi_args.push(NativeValue { isize_value: value });
+ }
+ NativeType::F32 => {
+ let value = value
+ .number_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI f32 type, expected number"))?
+ as f32;
+ ffi_args.push(NativeValue { f32_value: value });
+ }
+ NativeType::F64 => {
+ let value = value
+ .number_value(scope)
+ .ok_or_else(|| type_error("Invalid FFI f64 type, expected number"))?
+ as f64;
+ ffi_args.push(NativeValue { f64_value: value });
+ }
+ NativeType::Pointer => {
+ if value.is_null() {
+ let value: *const u8 = ptr::null();
+ ffi_args.push(NativeValue { pointer: value })
+ } else if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ let value = value.u64_value().0 as *const u8;
+ ffi_args.push(NativeValue { pointer: value });
+ } else if value.is_array_buffer() || value.is_array_buffer_view() {
+ let value: ZeroCopyBuf = serde_v8::from_v8(scope, value)?;
+ let value: &[u8] = &value[..];
+ ffi_args.push(NativeValue {
+ pointer: value.as_ptr(),
+ });
+ } else {
+ return Err(type_error("Invalid FFI pointer type, expected null, BigInt, ArrayBuffer, or ArrayBufferView"));
+ }
+ }
+ NativeType::Function {} => {
+ if value.is_null() {
+ let value: *const u8 = ptr::null();
+ ffi_args.push(NativeValue { pointer: value })
+ } else if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ let value = value.u64_value().0 as *const u8;
+ ffi_args.push(NativeValue { pointer: value });
+ } else {
+ return Err(type_error(
+ "Invalid FFI function type, expected null, or BigInt",
+ ));
+ }
+ }
}
}
+
+ Ok(ffi_args)
}
-impl FfiCallPtrArgs {
- fn get_symbol(&self) -> Symbol {
- let fn_ptr: u64 = self.pointer.into();
- let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _);
- let cif = libffi::middle::Cif::new(
- self
- .def
- .parameters
- .clone()
- .into_iter()
- .map(libffi::middle::Type::from),
- self.def.result.into(),
- );
+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)| unsafe {
+ ffi_arg.as_arg(*parameter_types.get(index).unwrap())
+ })
+ .collect();
- Symbol {
- cif,
- ptr,
- parameter_types: self.def.parameters.clone(),
- result_type: self.def.result,
+ Ok(match result_type {
+ NativeType::Void => NativeValue {
+ void_value: unsafe { cif.call::<()>(fun_ptr, &call_args) },
+ },
+ NativeType::U8 => NativeValue {
+ u8_value: unsafe { cif.call::<u8>(fun_ptr, &call_args) },
+ },
+ NativeType::I8 => NativeValue {
+ i8_value: unsafe { cif.call::<i8>(fun_ptr, &call_args) },
+ },
+ NativeType::U16 => NativeValue {
+ u16_value: unsafe { cif.call::<u16>(fun_ptr, &call_args) },
+ },
+ NativeType::I16 => NativeValue {
+ i16_value: unsafe { cif.call::<i16>(fun_ptr, &call_args) },
+ },
+ NativeType::U32 => NativeValue {
+ u32_value: unsafe { cif.call::<u32>(fun_ptr, &call_args) },
+ },
+ NativeType::I32 => NativeValue {
+ i32_value: unsafe { cif.call::<i32>(fun_ptr, &call_args) },
+ },
+ NativeType::U64 => NativeValue {
+ u64_value: unsafe { cif.call::<u64>(fun_ptr, &call_args) },
+ },
+ NativeType::I64 => NativeValue {
+ i64_value: unsafe { cif.call::<i64>(fun_ptr, &call_args) },
+ },
+ NativeType::USize => NativeValue {
+ usize_value: unsafe { cif.call::<usize>(fun_ptr, &call_args) },
+ },
+ NativeType::ISize => NativeValue {
+ isize_value: unsafe { cif.call::<isize>(fun_ptr, &call_args) },
+ },
+ NativeType::F32 => NativeValue {
+ f32_value: unsafe { cif.call::<f32>(fun_ptr, &call_args) },
+ },
+ NativeType::F64 => NativeValue {
+ f64_value: unsafe { cif.call::<f64>(fun_ptr, &call_args) },
+ },
+ NativeType::Pointer | NativeType::Function {} => NativeValue {
+ pointer: unsafe { cif.call::<*const u8>(fun_ptr, &call_args) },
+ },
+ })
+}
+
+struct UnsafeCallbackResource {
+ // Closure is never directly touched, but it keeps the C callback alive
+ // until `close()` method is called.
+ #[allow(dead_code)]
+ closure: libffi::middle::Closure<'static>,
+ info: *const CallbackInfo,
+}
+
+impl Resource for UnsafeCallbackResource {
+ fn name(&self) -> Cow<str> {
+ "unsafecallback".into()
+ }
+
+ fn close(self: Rc<Self>) {
+ // SAFETY: This drops the closure and the callback info associated with it.
+ // Any retained function pointers to the closure become dangling pointers.
+ // It is up to the user to know that it is safe to call the `close()` on the
+ // UnsafeCallback instance.
+ unsafe {
+ let info = Box::from_raw(self.info as *mut CallbackInfo);
+ let isolate = info.isolate.as_mut().unwrap();
+ v8::Global::from_raw(isolate, info.callback);
+ v8::Global::from_raw(isolate, info.context);
}
}
}
-fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
- let buffers: Vec<Option<&[u8]>> = args
- .buffers
- .iter()
- .map(|buffer| buffer.as_ref().map(|buffer| &buffer[..]))
- .collect();
-
- let mut native_values: Vec<NativeValue> = vec![];
+struct CallbackInfo {
+ pub callback: NonNull<v8::Function>,
+ pub context: NonNull<v8::Context>,
+ pub isolate: *mut v8::Isolate,
+}
- for (&native_type, value) in symbol
- .parameter_types
- .iter()
- .zip(args.parameters.into_iter())
- {
- match native_type {
- NativeType::Pointer => match value.as_u64() {
- Some(idx) => {
- let buf = buffers
- .get(idx as usize)
- .ok_or_else(|| {
- generic_error(format!("No buffer present at index {}", idx))
- })?
- .unwrap();
- native_values.push(NativeValue::buffer(buf.as_ptr()));
- }
- _ => {
- let value = NativeValue::new(native_type, value)?;
- native_values.push(value);
- }
- },
+unsafe extern "C" fn deno_ffi_callback(
+ cif: &libffi::low::ffi_cif,
+ result: &mut c_void,
+ args: *const *const c_void,
+ info: &CallbackInfo,
+) {
+ let isolate = &mut *info.isolate;
+ let callback = v8::Global::from_raw(isolate, info.callback);
+ let context = std::mem::transmute::<
+ NonNull<v8::Context>,
+ v8::Local<v8::Context>,
+ >(info.context);
+ IS_ISOLATE_THREAD.with(|is_event_loop_thread| {
+ if !(*is_event_loop_thread.borrow()) {
+ // Call from another thread, not yet supported.
+ eprintln!(
+ "Calling Deno FFI's callbacks from other threads is not supported"
+ );
+ std::process::exit(1);
+ }
+ });
+ // Call from main thread. If this callback is being triggered due to a
+ // function call coming from Deno itself, then this callback will build
+ // ontop of that stack.
+ // If this callback is being triggered outside of Deno (for example from a
+ // signal handler) then this will either create an empty new stack if
+ // Deno currently has nothing running and is waiting for promises to resolve,
+ // or will (very incorrectly) build ontop of whatever stack exists.
+ // The callback will even be called through from a `while (true)` liveloop, but
+ // it somehow cannot change the values that the loop sees, even if they both
+ // refer the same `let bool_value`.
+ let mut cb_scope = v8::CallbackScope::new(context);
+ let mut scope = v8::HandleScope::new(&mut cb_scope);
+ let func = callback.open(&mut scope);
+ let result = result as *mut c_void;
+ let repr: &[*mut ffi_type] =
+ std::slice::from_raw_parts(cif.arg_types, cif.nargs as usize);
+ let vals: &[*const c_void] =
+ std::slice::from_raw_parts(args, cif.nargs as usize);
+
+ let mut params: Vec<v8::Local<v8::Value>> = vec![];
+ for (repr, val) in repr.iter().zip(vals) {
+ let value: v8::Local<v8::Value> = match (*(*repr)).type_ as _ {
+ FFI_TYPE_FLOAT => {
+ let value = *((*val) as *const f32);
+ v8::Number::new(&mut scope, value as f64).into()
+ }
+ FFI_TYPE_DOUBLE => {
+ let value = *((*val) as *const f64);
+ v8::Number::new(&mut scope, value).into()
+ }
+ FFI_TYPE_SINT8 => {
+ let value = *((*val) as *const i8);
+ v8::Integer::new(&mut scope, value as i32).into()
+ }
+ FFI_TYPE_UINT8 => {
+ let value = *((*val) as *const u8);
+ v8::Integer::new_from_unsigned(&mut scope, value as u32).into()
+ }
+ FFI_TYPE_SINT16 => {
+ let value = *((*val) as *const i16);
+ v8::Integer::new(&mut scope, value as i32).into()
+ }
+ FFI_TYPE_UINT16 => {
+ let value = *((*val) as *const u16);
+ v8::Integer::new_from_unsigned(&mut scope, value as u32).into()
+ }
+ FFI_TYPE_INT | FFI_TYPE_SINT32 => {
+ let value = *((*val) as *const i32);
+ v8::Integer::new(&mut scope, value).into()
+ }
+ FFI_TYPE_UINT32 => {
+ let value = *((*val) as *const u32);
+ v8::Integer::new_from_unsigned(&mut scope, value).into()
+ }
+ FFI_TYPE_SINT64 => {
+ let result = *((*val) as *const i64);
+ v8::BigInt::new_from_i64(&mut scope, result).into()
+ }
+ FFI_TYPE_POINTER | FFI_TYPE_STRUCT | FFI_TYPE_UINT64 => {
+ let result = *((*val) as *const u64);
+ v8::BigInt::new_from_u64(&mut scope, result).into()
+ }
+ FFI_TYPE_VOID => v8::undefined(&mut scope).into(),
_ => {
- let value = NativeValue::new(native_type, value)?;
- native_values.push(value);
+ unreachable!()
}
- }
+ };
+ params.push(value);
}
- 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<_>>();
+ let recv = v8::undefined(&mut scope);
+ let call_result = func.call(&mut scope, recv.into(), &params);
+ std::mem::forget(callback);
+
+ if call_result.is_none() {
+ // JS function threw an exception. Set the return value to zero and return.
+ // The exception continue propagating up the call chain when the event loop
+ // resumes.
+ match (*cif.rtype).type_ as _ {
+ FFI_TYPE_INT | FFI_TYPE_SINT32 | FFI_TYPE_UINT32 => {
+ // zero is equal for signed and unsigned alike
+ *(result as *mut u32) = 0;
+ }
+ FFI_TYPE_FLOAT => {
+ *(result as *mut f32) = 0.0;
+ }
+ FFI_TYPE_DOUBLE => {
+ *(result as *mut f64) = 0.0;
+ }
+ FFI_TYPE_SINT8 | FFI_TYPE_UINT8 => {
+ // zero is equal for signed and unsigned alike
+ *(result as *mut u8) = 0;
+ }
+ FFI_TYPE_SINT16 | FFI_TYPE_UINT16 => {
+ // zero is equal for signed and unsigned alike
+ *(result as *mut u16) = 0;
+ }
+ FFI_TYPE_POINTER | FFI_TYPE_STRUCT | FFI_TYPE_UINT64
+ | FFI_TYPE_SINT64 => {
+ *(result as *mut u64) = 0;
+ }
+ FFI_TYPE_VOID => {
+ // nop
+ }
+ _ => {
+ unreachable!();
+ }
+ };
- 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) })
+ return;
+ }
+ let value = call_result.unwrap();
+
+ match (*cif.rtype).type_ as _ {
+ FFI_TYPE_INT | FFI_TYPE_SINT32 => {
+ *(result as *mut i32) = value
+ .int32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i32;
}
- NativeType::I8 => {
- json!(unsafe { symbol.cif.call::<i8>(symbol.ptr, &call_args) })
+ FFI_TYPE_FLOAT => {
+ *(result as *mut f32) = value
+ .number_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as f32;
}
- NativeType::U16 => {
- json!(unsafe { symbol.cif.call::<u16>(symbol.ptr, &call_args) })
+ FFI_TYPE_DOUBLE => {
+ *(result as *mut f64) = value
+ .number_value(&mut scope)
+ .expect("Unable to deserialize result parameter.");
}
- NativeType::I16 => {
- json!(unsafe { symbol.cif.call::<i16>(symbol.ptr, &call_args) })
+ FFI_TYPE_POINTER | FFI_TYPE_STRUCT => {
+ if value.is_array_buffer() | value.is_array_buffer_view() {
+ let value: ZeroCopyBuf = serde_v8::from_v8(&mut scope, value)
+ .expect("Unable to deserialize result parameter.");
+ let value: &[u8] = &value[..];
+ *(result as *mut *const u8) = value.as_ptr();
+ } else if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ *(result as *mut u64) = value.u64_value().0;
+ } else if value.is_null() {
+ *(result as *mut *const c_void) = ptr::null();
+ } else {
+ // Fallthrough: Probably someone returned a number but this could
+ // also be eg. a string. This is essentially UB.
+ *(result as *mut u64) = value
+ .integer_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u64;
+ }
}
- NativeType::U32 => {
- json!(unsafe { symbol.cif.call::<u32>(symbol.ptr, &call_args) })
+ FFI_TYPE_SINT8 => {
+ *(result as *mut i8) = value
+ .int32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i8;
}
- NativeType::I32 => {
- json!(unsafe { symbol.cif.call::<i32>(symbol.ptr, &call_args) })
+ FFI_TYPE_UINT8 => {
+ *(result as *mut u8) = value
+ .uint32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u8;
}
- NativeType::U64 => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<u64>(symbol.ptr, &call_args)
- }))
+ FFI_TYPE_SINT16 => {
+ *(result as *mut i16) = value
+ .int32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i16;
}
- NativeType::I64 => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<i64>(symbol.ptr, &call_args)
- } as u64))
+ FFI_TYPE_UINT16 => {
+ *(result as *mut u16) = value
+ .uint32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u16;
}
- NativeType::USize => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<usize>(symbol.ptr, &call_args)
- } as u64))
+ FFI_TYPE_UINT32 => {
+ *(result as *mut u32) = value
+ .uint32_value(&mut scope)
+ .expect("Unable to deserialize result parameter.");
}
- NativeType::ISize => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<isize>(symbol.ptr, &call_args)
- } as u64))
+ FFI_TYPE_SINT64 => {
+ if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ *(result as *mut i64) = value.i64_value().0;
+ } else {
+ *(result as *mut i64) = value
+ .integer_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as i64;
+ }
}
- NativeType::F32 => {
- json!(unsafe { symbol.cif.call::<f32>(symbol.ptr, &call_args) })
+ FFI_TYPE_UINT64 => {
+ if value.is_big_int() {
+ let value = v8::Local::<v8::BigInt>::try_from(value).unwrap();
+ *(result as *mut u64) = value.u64_value().0;
+ } else {
+ *(result as *mut u64) = value
+ .integer_value(&mut scope)
+ .expect("Unable to deserialize result parameter.")
+ as u64;
+ }
}
- NativeType::F64 => {
- json!(unsafe { symbol.cif.call::<f64>(symbol.ptr, &call_args) })
+ FFI_TYPE_VOID => {
+ // nop
}
- NativeType::Pointer => {
- json!(U32x2::from(unsafe {
- symbol.cif.call::<*const u8>(symbol.ptr, &call_args)
- } as u64))
+ _ => {
+ unreachable!();
}
- })
+ };
}
-#[op]
-fn op_ffi_call_ptr<FP>(
+#[derive(Deserialize)]
+struct RegisterCallbackArgs {
+ parameters: Vec<NativeType>,
+ result: NativeType,
+}
+
+#[op(v8)]
+fn op_ffi_unsafe_callback_create<FP, 'scope>(
state: &mut deno_core::OpState,
- args: FfiCallPtrArgs,
-) -> Result<Value, AnyError>
+ scope: &mut v8::HandleScope<'scope>,
+ args: RegisterCallbackArgs,
+ cb: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
{
- check_unstable(state, "Deno.UnsafeFnPointer#call");
-
+ check_unstable(state, "Deno.UnsafeCallback");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- let symbol = args.get_symbol();
- ffi_call(args.into(), &symbol)
+ let v8_value = cb.v8_value;
+ let cb = v8::Local::<v8::Function>::try_from(v8_value)?;
+
+ let isolate: *mut v8::Isolate = &mut *scope as &mut v8::Isolate;
+ let callback = v8::Global::new(scope, cb).into_raw();
+ let current_context = scope.get_current_context();
+ let context = v8::Global::new(scope, current_context).into_raw();
+
+ let info = Box::leak(Box::new(CallbackInfo {
+ callback,
+ context,
+ isolate,
+ }));
+ let cif = Cif::new(
+ args.parameters.into_iter().map(libffi::middle::Type::from),
+ libffi::middle::Type::from(args.result),
+ );
+
+ let closure = libffi::middle::Closure::new(cif, deno_ffi_callback, info);
+ let ptr = *closure.code_ptr() as usize as u64;
+ let resource = UnsafeCallbackResource { closure, info };
+ let rid = state.resource_table.add(resource);
+
+ let rid_local = v8::Integer::new_from_unsigned(scope, rid);
+ let ptr_local = v8::BigInt::new_from_u64(scope, ptr);
+ let array = v8::Array::new(scope, 2);
+ array.set_index(scope, 0, rid_local.into());
+ array.set_index(scope, 1, ptr_local.into());
+ let array_value: v8::Local<v8::Value> = array.into();
+
+ Ok(array_value.into())
}
-#[op]
-async fn op_ffi_call_ptr_nonblocking<FP>(
+#[op(v8)]
+fn op_ffi_call_ptr<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: Rc<RefCell<deno_core::OpState>>,
- args: FfiCallPtrArgs,
-) -> Result<Value, AnyError>
+ pointer: u64,
+ 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 = args.get_symbol();
- tokio::task::spawn_blocking(move || ffi_call(args.into(), &symbol))
- .await
- .unwrap()
+ 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)
}
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-struct FfiGetArgs {
- rid: ResourceId,
- name: String,
- r#type: NativeType,
+#[op(v8)]
+fn op_ffi_call_ptr_nonblocking<'scope, FP>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ pointer: u64,
+ 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) })
+ })
}
-#[op]
-fn op_ffi_get_static(
+#[op(v8)]
+fn op_ffi_get_static<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
- args: FfiGetArgs,
-) -> Result<Value, AnyError> {
- let resource = state
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
+ rid: ResourceId,
+ name: String,
+ static_type: NativeType,
+) -> Result<serde_v8::Value<'scope>, AnyError> {
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
- let data_ptr = resource.get_static(args.name)? as *const u8;
+ let data_ptr = resource.get_static(name)? as *const u8;
- Ok(match args.r#type {
+ Ok(match static_type {
NativeType::Void => {
- unreachable!();
+ return Err(type_error("Invalid FFI static type 'void'"));
}
NativeType::U8 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u8) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u8) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::I8 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i8) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i8) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::U16 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u16) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u16) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::I16 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i16) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i16) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::U32 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u32) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u32) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::I32 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i32) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i32) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::U64 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const u64) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const u64) };
+ let big_int = v8::BigInt::new_from_u64(scope, result);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::I64 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const i64) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const i64) };
+ let big_int = v8::BigInt::new_from_i64(scope, result);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::USize => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const usize) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const usize) };
+ let big_int = v8::BigInt::new_from_u64(scope, result as u64);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::ISize => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const isize) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const isize) };
+ let big_int = v8::BigInt::new_from_i64(scope, result as i64);
+ serde_v8::from_v8(scope, big_int.into())?
}
NativeType::F32 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const f32) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const f32) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
NativeType::F64 => {
- json!(unsafe { ptr::read_unaligned(data_ptr as *const f64) })
+ let result = unsafe { ptr::read_unaligned(data_ptr as *const f64) };
+ let number = v8::Number::new(scope, result as f64);
+ serde_v8::from_v8(scope, number.into())?
}
- NativeType::Pointer => {
- json!(U32x2::from(data_ptr as *const u8 as u64))
+ NativeType::Pointer | NativeType::Function {} => {
+ let result = data_ptr as *const u8 as u64;
+ let big_int = v8::BigInt::new_from_u64(scope, result);
+ serde_v8::from_v8(scope, big_int.into())?
}
})
}
-#[op]
-fn op_ffi_call(
- state: &mut deno_core::OpState,
- args: FfiCallArgs,
-) -> Result<Value, AnyError> {
- let resource = state
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
-
- let symbol = resource
- .symbols
- .get(&args.symbol)
- .ok_or_else(bad_resource_id)?;
-
- ffi_call(args, symbol)
+#[op(v8)]
+fn op_ffi_call<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
+ state: Rc<RefCell<deno_core::OpState>>,
+ rid: ResourceId,
+ symbol: String,
+ parameters: serde_v8::Value<'scope>,
+) -> Result<serde_v8::Value<'scope>, AnyError> {
+ let symbol = {
+ let state = &mut state.borrow();
+ let resource = state.resource_table.get::<DynamicLibraryResource>(rid)?;
+
+ resource
+ .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 = ffi_call(
+ call_args,
+ &symbol.cif,
+ symbol.ptr,
+ &symbol.parameter_types,
+ symbol.result_type,
+ )?;
+ // SAFETY: Same return type declared to libffi; trust user to have it right beyond that.
+ let result = unsafe { result.to_v8(scope, symbol.result_type) };
+ Ok(result)
}
/// A non-blocking FFI call.
-#[op]
-async fn op_ffi_call_nonblocking(
+#[op(v8)]
+fn op_ffi_call_nonblocking<'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: Rc<RefCell<deno_core::OpState>>,
- args: FfiCallArgs,
-) -> Result<Value, AnyError> {
- let resource = state
- .borrow()
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
- let symbols = &resource.symbols;
- let symbol = symbols
- .get(&args.symbol)
- .ok_or_else(bad_resource_id)?
- .clone();
-
- tokio::task::spawn_blocking(move || ffi_call(args, &symbol))
- .await
- .unwrap()
+ 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,
+ ..
+ } = 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]
-fn op_ffi_ptr_of<FP>(
+#[op(v8)]
+fn op_ffi_ptr_of<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
buf: ZeroCopyBuf,
-) -> Result<U32x2, AnyError>
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
{
check_unstable(state, "Deno.UnsafePointer#of");
-
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(U32x2::from(buf.as_ptr() as u64))
+ let big_int: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, buf.as_ptr() as u64).into();
+ Ok(big_int.into())
}
#[op]
fn op_ffi_buf_copy_into<FP>(
state: &mut deno_core::OpState,
- (src, mut dst, len): (U32x2, ZeroCopyBuf, usize),
+ src: u64,
+ mut dst: ZeroCopyBuf,
+ len: usize,
) -> Result<(), AnyError>
where
FP: FfiPermissions + 'static,
@@ -865,7 +1365,7 @@ where
"Destination length is smaller than source length",
))
} else {
- let src = u64::from(src) as *const u8;
+ let src = src as *const u8;
unsafe { ptr::copy(src, dst.as_mut_ptr(), len) };
Ok(())
}
@@ -874,7 +1374,7 @@ where
#[op]
fn op_ffi_cstr_read<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<String, AnyError>
where
FP: FfiPermissions + 'static,
@@ -884,14 +1384,14 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- let ptr = u64::from(ptr) as *const c_char;
+ let ptr = ptr as *const c_char;
Ok(unsafe { CStr::from_ptr(ptr) }.to_str()?.to_string())
}
#[op]
fn op_ffi_read_u8<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<u8, AnyError>
where
FP: FfiPermissions + 'static,
@@ -901,13 +1401,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u8) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const u8) })
}
#[op]
fn op_ffi_read_i8<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<i8, AnyError>
where
FP: FfiPermissions + 'static,
@@ -917,13 +1417,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i8) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const i8) })
}
#[op]
fn op_ffi_read_u16<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<u16, AnyError>
where
FP: FfiPermissions + 'static,
@@ -933,13 +1433,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u16) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const u16) })
}
#[op]
fn op_ffi_read_i16<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<i16, AnyError>
where
FP: FfiPermissions + 'static,
@@ -949,13 +1449,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i16) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const i16) })
}
#[op]
fn op_ffi_read_u32<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<u32, AnyError>
where
FP: FfiPermissions + 'static,
@@ -965,13 +1465,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u32) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const u32) })
}
#[op]
fn op_ffi_read_i32<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<i32, AnyError>
where
FP: FfiPermissions + 'static,
@@ -981,31 +1481,35 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i32) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const i32) })
}
-#[op]
-fn op_ffi_read_u64<FP>(
+#[op(v8)]
+fn op_ffi_read_u64<FP, 'scope>(
+ scope: &mut v8::HandleScope<'scope>,
state: &mut deno_core::OpState,
- ptr: U32x2,
-) -> Result<U32x2, AnyError>
+ ptr: u64,
+) -> Result<serde_v8::Value<'scope>, AnyError>
where
FP: FfiPermissions + 'static,
+ 'scope: 'scope,
{
check_unstable(state, "Deno.UnsafePointerView#getBigUint64");
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(U32x2::from(unsafe {
- ptr::read_unaligned(u64::from(ptr) as *const u64)
- }))
+ let result = unsafe { ptr::read_unaligned(ptr as *const u64) };
+
+ let big_int: v8::Local<v8::Value> =
+ v8::BigInt::new_from_u64(scope, result).into();
+ Ok(big_int.into())
}
#[op]
fn op_ffi_read_f32<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<f32, AnyError>
where
FP: FfiPermissions + 'static,
@@ -1015,13 +1519,13 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f32) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const f32) })
}
#[op]
fn op_ffi_read_f64<FP>(
state: &mut deno_core::OpState,
- ptr: U32x2,
+ ptr: u64,
) -> Result<f64, AnyError>
where
FP: FfiPermissions + 'static,
@@ -1031,7 +1535,7 @@ where
let permissions = state.borrow_mut::<FP>();
permissions.check(None)?;
- Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f64) })
+ Ok(unsafe { ptr::read_unaligned(ptr as *const f64) })
}
#[cfg(test)]