summaryrefslogtreecommitdiff
path: root/ext/ffi/ir.rs
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2022-12-12 06:14:20 -0800
committerGitHub <noreply@github.com>2022-12-12 14:14:20 +0000
commita2db70a8d0820722695e9094c8dbc888bde1ffa3 (patch)
tree6885e2aee424ab0264a8b01df9b0bafbcfe85792 /ext/ffi/ir.rs
parent890b0653104388bfef60ca7ebd0af758caf5f0be (diff)
refactor(ext/ffi): split into multiple parts (#16950)
- [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.
Diffstat (limited to 'ext/ffi/ir.rs')
-rw-r--r--ext/ffi/ir.rs527
1 files changed, 527 insertions, 0 deletions
diff --git a/ext/ffi/ir.rs b/ext/ffi/ir.rs
new file mode 100644
index 000000000..67c65b5b5
--- /dev/null
+++ b/ext/ffi/ir.rs
@@ -0,0 +1,527 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+
+use crate::symbol::NativeType;
+use crate::MAX_SAFE_INTEGER;
+use crate::MIN_SAFE_INTEGER;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::serde_json::Value;
+use deno_core::serde_v8;
+use deno_core::v8;
+use libffi::middle::Arg;
+use std::ffi::c_void;
+use std::ptr;
+
+/// Intermediate format for easy translation from NativeType + V8 value
+/// to libffi argument types.
+#[repr(C)]
+pub union NativeValue {
+ pub void_value: (),
+ pub bool_value: bool,
+ pub u8_value: u8,
+ pub i8_value: i8,
+ pub u16_value: u16,
+ pub i16_value: i16,
+ pub u32_value: u32,
+ pub i32_value: i32,
+ pub u64_value: u64,
+ pub i64_value: i64,
+ pub usize_value: usize,
+ pub isize_value: isize,
+ pub f32_value: f32,
+ pub f64_value: f64,
+ pub pointer: *mut c_void,
+}
+
+impl NativeValue {
+ pub unsafe fn as_arg(&self, native_type: NativeType) -> Arg {
+ match native_type {
+ NativeType::Void => unreachable!(),
+ NativeType::Bool => Arg::new(&self.bool_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),
+ NativeType::Pointer | NativeType::Buffer | NativeType::Function => {
+ Arg::new(&self.pointer)
+ }
+ }
+ }
+
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ pub unsafe fn to_value(&self, native_type: NativeType) -> Value {
+ match native_type {
+ NativeType::Void => Value::Null,
+ NativeType::Bool => Value::from(self.bool_value),
+ 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 => Value::from(self.u64_value),
+ NativeType::I64 => Value::from(self.i64_value),
+ NativeType::USize => Value::from(self.usize_value),
+ NativeType::ISize => Value::from(self.isize_value),
+ NativeType::F32 => Value::from(self.f32_value),
+ NativeType::F64 => Value::from(self.f64_value),
+ NativeType::Pointer | NativeType::Function | NativeType::Buffer => {
+ Value::from(self.pointer as usize)
+ }
+ }
+ }
+
+ // SAFETY: native_type must correspond to the type of value represented by the union field
+ #[inline]
+ pub 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::Bool => {
+ let local_value: v8::Local<v8::Value> =
+ v8::Boolean::new(scope, self.bool_value).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 value = self.u64_value;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as u64 {
+ v8::BigInt::new_from_u64(scope, value).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::I64 => {
+ let value = self.i64_value;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as i64 || value < MIN_SAFE_INTEGER as i64
+ {
+ v8::BigInt::new_from_i64(scope, self.i64_value).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::USize => {
+ let value = self.usize_value;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as usize {
+ v8::BigInt::new_from_u64(scope, value as u64).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ NativeType::ISize => {
+ let value = self.isize_value;
+ let local_value: v8::Local<v8::Value> =
+ if !(MIN_SAFE_INTEGER..=MAX_SAFE_INTEGER).contains(&value) {
+ v8::BigInt::new_from_i64(scope, self.isize_value as i64).into()
+ } else {
+ v8::Number::new(scope, value as f64).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::Buffer | NativeType::Function => {
+ let value = self.pointer as u64;
+ let local_value: v8::Local<v8::Value> =
+ if value > MAX_SAFE_INTEGER as u64 {
+ v8::BigInt::new_from_u64(scope, value).into()
+ } else {
+ v8::Number::new(scope, value as f64).into()
+ };
+ local_value.into()
+ }
+ }
+ }
+}
+
+// SAFETY: unsafe trait must have unsafe implementation
+unsafe impl Send for NativeValue {}
+
+#[inline]
+pub fn ffi_parse_bool_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let bool_value = v8::Local::<v8::Boolean>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u8 type, expected boolean"))?
+ .is_true();
+ Ok(NativeValue { bool_value })
+}
+
+#[inline]
+pub fn ffi_parse_u8_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let u8_value = v8::Local::<v8::Uint32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u8 type, expected unsigned integer"))?
+ .value() as u8;
+ Ok(NativeValue { u8_value })
+}
+
+#[inline]
+pub fn ffi_parse_i8_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let i8_value = v8::Local::<v8::Int32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI i8 type, expected integer"))?
+ .value() as i8;
+ Ok(NativeValue { i8_value })
+}
+
+#[inline]
+pub fn ffi_parse_u16_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let u16_value = v8::Local::<v8::Uint32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u16 type, expected unsigned integer"))?
+ .value() as u16;
+ Ok(NativeValue { u16_value })
+}
+
+#[inline]
+pub fn ffi_parse_i16_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let i16_value = v8::Local::<v8::Int32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI i16 type, expected integer"))?
+ .value() as i16;
+ Ok(NativeValue { i16_value })
+}
+
+#[inline]
+pub fn ffi_parse_u32_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let u32_value = v8::Local::<v8::Uint32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI u32 type, expected unsigned integer"))?
+ .value() as u32;
+ Ok(NativeValue { u32_value })
+}
+
+#[inline]
+pub fn ffi_parse_i32_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let i32_value = v8::Local::<v8::Int32>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI i32 type, expected integer"))?
+ .value() as i32;
+ Ok(NativeValue { i32_value })
+}
+
+#[inline]
+pub fn ffi_parse_u64_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let u64_value: u64 = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg)
+ {
+ value.u64_value().0
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as u64
+ } else {
+ return Err(type_error(
+ "Invalid FFI u64 type, expected unsigned integer",
+ ));
+ };
+ Ok(NativeValue { u64_value })
+}
+
+#[inline]
+pub fn ffi_parse_i64_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let i64_value: i64 = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg)
+ {
+ value.i64_value().0
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as i64
+ } else {
+ return Err(type_error("Invalid FFI i64 type, expected integer"));
+ };
+ Ok(NativeValue { i64_value })
+}
+
+#[inline]
+pub fn ffi_parse_usize_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let usize_value: usize =
+ if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.u64_value().0 as usize
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as usize
+ } else {
+ return Err(type_error("Invalid FFI usize type, expected integer"));
+ };
+ Ok(NativeValue { usize_value })
+}
+
+#[inline]
+pub fn ffi_parse_isize_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, so optimise slow call for this case.
+ // 2. Number: Common, supported by Fast API, so let that be the optimal case.
+ let isize_value: isize =
+ if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.i64_value().0 as isize
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as isize
+ } else {
+ return Err(type_error("Invalid FFI isize type, expected integer"));
+ };
+ Ok(NativeValue { isize_value })
+}
+
+#[inline]
+pub fn ffi_parse_f32_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let f32_value = v8::Local::<v8::Number>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI f32 type, expected number"))?
+ .value() as f32;
+ Ok(NativeValue { f32_value })
+}
+
+#[inline]
+pub fn ffi_parse_f64_arg(
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ let f64_value = v8::Local::<v8::Number>::try_from(arg)
+ .map_err(|_| type_error("Invalid FFI f64 type, expected number"))?
+ .value() as f64;
+ Ok(NativeValue { f64_value })
+}
+
+#[inline]
+pub fn ffi_parse_pointer_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
+ // 2. Number: Common and supported by Fast API.
+ // 3. Null: Very uncommon / can be represented by a 0.
+ let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.u64_value().0 as usize as *mut c_void
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as usize as *mut c_void
+ } else if arg.is_null() {
+ ptr::null_mut()
+ } else {
+ return Err(type_error(
+ "Invalid FFI pointer type, expected null, integer or BigInt",
+ ));
+ };
+ Ok(NativeValue { pointer })
+}
+
+#[inline]
+pub fn ffi_parse_buffer_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. ArrayBuffer: Fairly common and not supported by Fast API, optimise this case.
+ // 2. ArrayBufferView: Common and supported by Fast API
+ // 5. Null: Very uncommon / can be represented by a 0.
+
+ let pointer = if let Ok(value) = v8::Local::<v8::ArrayBuffer>::try_from(arg) {
+ if let Some(non_null) = value.data() {
+ non_null.as_ptr()
+ } else {
+ ptr::null_mut()
+ }
+ } else if let Ok(value) = v8::Local::<v8::ArrayBufferView>::try_from(arg) {
+ let byte_offset = value.byte_offset();
+ let pointer = value
+ .buffer(scope)
+ .ok_or_else(|| {
+ type_error("Invalid FFI ArrayBufferView, expected data in the buffer")
+ })?
+ .data();
+ if let Some(non_null) = pointer {
+ // SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset
+ // is within the buffer backing store.
+ unsafe { non_null.as_ptr().add(byte_offset) }
+ } else {
+ ptr::null_mut()
+ }
+ } else if arg.is_null() {
+ ptr::null_mut()
+ } else {
+ return Err(type_error(
+ "Invalid FFI buffer type, expected null, ArrayBuffer, or ArrayBufferView",
+ ));
+ };
+ Ok(NativeValue { pointer })
+}
+
+#[inline]
+pub fn ffi_parse_function_arg(
+ scope: &mut v8::HandleScope,
+ arg: v8::Local<v8::Value>,
+) -> Result<NativeValue, AnyError> {
+ // Order of checking:
+ // 1. BigInt: Uncommon and not supported by Fast API, optimise this case.
+ // 2. Number: Common and supported by Fast API, optimise this case as second.
+ // 3. Null: Very uncommon / can be represented by a 0.
+ let pointer = if let Ok(value) = v8::Local::<v8::BigInt>::try_from(arg) {
+ value.u64_value().0 as usize as *mut c_void
+ } else if let Ok(value) = v8::Local::<v8::Number>::try_from(arg) {
+ value.integer_value(scope).unwrap() as usize as *mut c_void
+ } else if arg.is_null() {
+ ptr::null_mut()
+ } else {
+ return Err(type_error(
+ "Invalid FFI function type, expected null, integer, or BigInt",
+ ));
+ };
+ Ok(NativeValue { pointer })
+}
+
+pub 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![]);
+ }
+
+ 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());
+
+ for (index, native_type) in parameter_types.iter().enumerate() {
+ let value = args.get_index(scope, index as u32).unwrap();
+ 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!();
+ }
+ }
+ }
+
+ Ok(ffi_args)
+}