diff options
author | Elias Sjögreen <eliassjogreen1@gmail.com> | 2021-12-15 15:41:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-15 15:41:49 +0100 |
commit | ee49cce726c27cdcb372b9dba7f2deab840d9e6b (patch) | |
tree | d1a9a843eb5fba782a7cff5e50cb973ee3806225 /ext | |
parent | 4d176b7b7c11aabc584bee45423f108ea47faefe (diff) |
feat(ext/ffi): implement UnsafePointer and UnsafePointerView (#12828)
Diffstat (limited to 'ext')
-rw-r--r-- | ext/ffi/00_ffi.js | 185 | ||||
-rw-r--r-- | ext/ffi/lib.rs | 269 |
2 files changed, 426 insertions, 28 deletions
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index 25eba0233..48e2e4f92 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -6,7 +6,142 @@ const __bootstrap = window.__bootstrap; const { ArrayBuffer, + Uint8Array, + BigInt, + Number, + TypeError, } = window.__bootstrap.primordials; + + function unpackU64([hi, lo]) { + return BigInt(hi) << 32n | BigInt(lo); + } + + function packU64(value) { + return [Number(value >> 32n), Number(value & 0xFFFFFFFFn)]; + } + + function unpackI64([hi, lo]) { + const u64 = unpackU64([hi, lo]); + return u64 >> 63n ? u64 - 0x10000000000000000n : u64; + } + + class UnsafePointerView { + pointer; + + constructor(pointer) { + this.pointer = pointer; + } + + getUint8(offset = 0) { + return core.opSync( + "op_ffi_read_u8", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getInt8(offset = 0) { + return core.opSync( + "op_ffi_read_i8", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getUint16(offset = 0) { + return core.opSync( + "op_ffi_read_u16", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getInt16(offset = 0) { + return core.opSync( + "op_ffi_read_i16", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getUint32(offset = 0) { + return core.opSync( + "op_ffi_read_u32", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getInt32(offset = 0) { + return core.opSync( + "op_ffi_read_i32", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getBigUint64(offset = 0) { + return unpackU64(core.opSync( + "op_ffi_read_u64", + packU64(this.pointer.value + BigInt(offset)), + )); + } + + getBigInt64(offset = 0) { + return unpackI64(core.opSync( + "op_ffi_read_u64", + packU64(this.pointer.value + BigInt(offset)), + )); + } + + getFloat32(offset = 0) { + return core.opSync( + "op_ffi_read_f32", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getFloat64(offset = 0) { + return core.opSync( + "op_ffi_read_f64", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getCString(offset = 0) { + return core.opSync( + "op_ffi_cstr_read", + packU64(this.pointer.value + BigInt(offset)), + ); + } + + getArrayBuffer(byteLength, offset = 0) { + const uint8array = new Uint8Array(byteLength); + this.copyInto(uint8array, offset); + return uint8array.buffer; + } + + copyInto(destination, offset = 0) { + core.opSync("op_ffi_buf_copy_into", [ + packU64(this.pointer.value + BigInt(offset)), + destination, + destination.byteLength, + ]); + } + } + + class UnsafePointer { + value; + + constructor(value) { + this.value = value; + } + + static of(typedArray) { + return new UnsafePointer( + unpackU64(core.opSync("op_ffi_ptr_of", typedArray)), + ); + } + + valueOf() { + return this.value; + } + } + class DynamicLibrary { #rid; symbols = {}; @@ -16,37 +151,67 @@ for (const symbol in symbols) { const isNonBlocking = symbols[symbol].nonblocking; + const types = symbols[symbol].parameters; this.symbols[symbol] = (...args) => { const parameters = []; const buffers = []; - for (const arg of args) { - if ( - arg?.buffer instanceof ArrayBuffer && - arg.byteLength !== undefined - ) { - parameters.push(buffers.length); - buffers.push(arg); + for (let i = 0; i < types.length; i++) { + const type = types[i]; + const arg = args[i]; + + if (type === "pointer") { + if ( + arg?.buffer instanceof ArrayBuffer && + arg.byteLength !== undefined + ) { + parameters.push(buffers.length); + buffers.push(arg); + } else if (arg instanceof UnsafePointer) { + parameters.push(packU64(arg.value)); + buffers.push(undefined); + } else if (arg === null) { + parameters.push(null); + buffers.push(undefined); + } else { + throw new TypeError( + "Invalid ffi arg value, expected TypedArray, UnsafePointer or null", + ); + } } else { parameters.push(arg); } } if (isNonBlocking) { - return core.opAsync("op_ffi_call_nonblocking", { + const promise = core.opAsync("op_ffi_call_nonblocking", { rid: this.#rid, symbol, parameters, buffers, }); + + if (symbols[symbol].result === "pointer") { + return promise.then((value) => + new UnsafePointer(unpackU64(value)) + ); + } + + return promise; } else { - return core.opSync("op_ffi_call", { + const result = core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters, buffers, }); + + if (symbols[symbol].result === "pointer") { + return new UnsafePointer(unpackU64(result)); + } + + return result; } }; } @@ -63,5 +228,5 @@ return new DynamicLibrary(pathFromURL(path), symbols); } - window.__bootstrap.ffi = { dlopen }; + window.__bootstrap.ffi = { dlopen, UnsafePointer, UnsafePointerView }; })(this); diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index a21601e3b..de4ff3ef2 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -1,10 +1,12 @@ // Copyright 2021 the Deno authors. All rights reserved. MIT license. use deno_core::error::bad_resource_id; +use deno_core::error::range_error; use deno_core::error::AnyError; use deno_core::include_js_files; use deno_core::op_async; use deno_core::op_sync; +use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::Extension; @@ -15,12 +17,15 @@ use deno_core::ZeroCopyBuf; use dlopen::raw::Library; use libffi::middle::Arg; use serde::Deserialize; +use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::c_void; +use std::ffi::CStr; use std::path::Path; use std::path::PathBuf; +use std::ptr; use std::rc::Rc; pub struct Unstable(pub bool); @@ -38,7 +43,7 @@ fn check_unstable(state: &OpState, api_name: &str) { } pub trait FfiPermissions { - fn check(&mut self, path: &Path) -> Result<(), AnyError>; + fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError>; } #[derive(Clone)] @@ -108,6 +113,18 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension { ("op_ffi_load", op_sync(op_ffi_load::<P>)), ("op_ffi_call", op_sync(op_ffi_call)), ("op_ffi_call_nonblocking", op_async(op_ffi_call_nonblocking)), + ("op_ffi_ptr_of", op_sync(op_ffi_ptr_of::<P>)), + ("op_ffi_buf_copy_into", op_sync(op_ffi_buf_copy_into::<P>)), + ("op_ffi_cstr_read", op_sync(op_ffi_cstr_read::<P>)), + ("op_ffi_read_u8", op_sync(op_ffi_read_u8::<P>)), + ("op_ffi_read_i8", op_sync(op_ffi_read_i8::<P>)), + ("op_ffi_read_u16", op_sync(op_ffi_read_u16::<P>)), + ("op_ffi_read_i16", op_sync(op_ffi_read_i16::<P>)), + ("op_ffi_read_u32", op_sync(op_ffi_read_u32::<P>)), + ("op_ffi_read_i32", op_sync(op_ffi_read_i32::<P>)), + ("op_ffi_read_u64", op_sync(op_ffi_read_u64::<P>)), + ("op_ffi_read_f32", op_sync(op_ffi_read_f32::<P>)), + ("op_ffi_read_f64", op_sync(op_ffi_read_f64::<P>)), ]) .state(move |state| { // Stolen from deno_webgpu, is there a better option? @@ -133,7 +150,7 @@ enum NativeType { ISize, F32, F64, - Buffer, + Pointer, } impl From<NativeType> for libffi::middle::Type { @@ -152,7 +169,7 @@ impl From<NativeType> for libffi::middle::Type { NativeType::ISize => libffi::middle::Type::isize(), NativeType::F32 => libffi::middle::Type::f32(), NativeType::F64 => libffi::middle::Type::f64(), - NativeType::Buffer => libffi::middle::Type::pointer(), + NativeType::Pointer => libffi::middle::Type::pointer(), } } } @@ -172,7 +189,7 @@ union NativeValue { isize_value: isize, f32_value: f32, f64_value: f64, - buffer: *const u8, + pointer: *const u8, } impl NativeValue { @@ -215,12 +232,25 @@ impl NativeValue { NativeType::F64 => Self { f64_value: value_as_f64(value), }, - NativeType::Buffer => unreachable!(), + NativeType::Pointer => { + if value.is_null() { + Self { + pointer: ptr::null(), + } + } else { + Self { + pointer: u64::from( + serde_json::from_value::<U32x2>(value) + .expect("Expected ffi arg value to be a tuple of the low and high bits of a pointer address") + ) as *const u8, + } + } + } } } fn buffer(ptr: *const u8) -> Self { - Self { buffer: ptr } + Self { pointer: ptr } } unsafe fn as_arg(&self, native_type: NativeType) -> Arg { @@ -238,7 +268,7 @@ impl NativeValue { NativeType::ISize => Arg::new(&self.isize_value), NativeType::F32 => Arg::new(&self.f32_value), NativeType::F64 => Arg::new(&self.f64_value), - NativeType::Buffer => Arg::new(&self.buffer), + NativeType::Pointer => Arg::new(&self.pointer), } } } @@ -267,6 +297,21 @@ fn value_as_f64(value: Value) -> f64 { .expect("Expected ffi arg value to be a float") } +#[derive(Serialize, Deserialize, Debug)] +struct U32x2(u32, u32); + +impl From<u64> for U32x2 { + fn from(value: u64) -> Self { + Self((value >> 32) as u32, value as u32) + } +} + +impl From<U32x2> for u64 { + fn from(value: U32x2) -> Self { + (value.0 as u64) << 32 | value.1 as u64 + } +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct ForeignFunction { @@ -367,7 +412,7 @@ where check_unstable(state, "Deno.dlopen"); let permissions = state.borrow_mut::<FP>(); - permissions.check(&PathBuf::from(&path))?; + permissions.check(Some(&PathBuf::from(&path)))?; let lib = Library::open(&path).map_err(|e| { dlopen::Error::OpeningLibraryError(std::io::Error::new( @@ -394,25 +439,30 @@ struct FfiCallArgs { rid: ResourceId, symbol: String, parameters: Vec<Value>, - buffers: Vec<ZeroCopyBuf>, + buffers: Vec<Option<ZeroCopyBuf>>, } fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> { - let buffers: Vec<&[u8]> = - args.buffers.iter().map(|buffer| &buffer[..]).collect(); + let buffers: Vec<Option<&[u8]>> = args + .buffers + .iter() + .map(|buffer| buffer.as_ref().map(|buffer| &buffer[..])) + .collect(); let native_values = symbol .parameter_types .iter() .zip(args.parameters.into_iter()) .map(|(&native_type, value)| { - if let NativeType::Buffer = native_type { - let idx: usize = value_as_uint(value); - let ptr = buffers[idx].as_ptr(); - NativeValue::buffer(ptr) - } else { - NativeValue::new(native_type, value) + if let NativeType::Pointer = native_type { + if let Some(idx) = value.as_u64() { + if let Some(&Some(buf)) = buffers.get(idx as usize) { + return NativeValue::buffer(buf.as_ptr()); + } + } } + + NativeValue::new(native_type, value) }) .collect::<Vec<_>>(); @@ -465,7 +515,11 @@ fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> { NativeType::F64 => { json!(unsafe { symbol.cif.call::<f64>(symbol.ptr, &call_args) }) } - NativeType::Buffer => unreachable!(), + NativeType::Pointer => { + json!(U32x2::from(unsafe { + symbol.cif.call::<*const u8>(symbol.ptr, &call_args) + } as u64)) + } }) } @@ -507,6 +561,185 @@ async fn op_ffi_call_nonblocking( .unwrap() } +fn op_ffi_ptr_of<FP>( + state: &mut deno_core::OpState, + buf: ZeroCopyBuf, + _: (), +) -> Result<U32x2, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(U32x2::from(buf.as_ptr() as u64)) +} + +fn op_ffi_buf_copy_into<FP>( + state: &mut deno_core::OpState, + (src, mut dst, len): (U32x2, ZeroCopyBuf, usize), + _: (), +) -> Result<(), AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + if dst.len() < len { + Err(range_error( + "Destination length is smaller than source length", + )) + } else { + let src = u64::from(src) as *const u8; + unsafe { ptr::copy(src, dst.as_mut_ptr(), len) }; + Ok(()) + } +} + +fn op_ffi_cstr_read<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<String, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + let ptr = u64::from(ptr) as *const i8; + Ok(unsafe { CStr::from_ptr(ptr) }.to_str()?.to_string()) +} + +fn op_ffi_read_u8<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<u8, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u8) }) +} + +fn op_ffi_read_i8<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<i8, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i8) }) +} + +fn op_ffi_read_u16<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<u16, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u16) }) +} + +fn op_ffi_read_i16<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<i16, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i16) }) +} + +fn op_ffi_read_u32<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<u32, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const u32) }) +} + +fn op_ffi_read_i32<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<i32, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const i32) }) +} + +fn op_ffi_read_u64<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<U32x2, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(U32x2::from(unsafe { + ptr::read_unaligned(u64::from(ptr) as *const u64) + })) +} + +fn op_ffi_read_f32<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<f32, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f32) }) +} + +fn op_ffi_read_f64<FP>( + state: &mut deno_core::OpState, + ptr: U32x2, + _: (), +) -> Result<f64, AnyError> +where + FP: FfiPermissions + 'static, +{ + let permissions = state.borrow_mut::<FP>(); + permissions.check(None)?; + + Ok(unsafe { ptr::read_unaligned(u64::from(ptr) as *const f64) }) +} + #[cfg(test)] mod tests { #[cfg(target_os = "windows")] |