diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index f55c7ed380c..09a0627f05c 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -4,6 +4,9 @@ version = "2.0.0-pre-rc.7" authors = ["Iroha 2 team "] edition = "2021" +[features] +wasm = [] + [dependencies] iroha_ffi_derive = { version = "=2.0.0-pre-rc.7", path = "derive" } diff --git a/ffi/derive/src/convert.rs b/ffi/derive/src/convert.rs index bd2cea01722..d442f7b8038 100644 --- a/ffi/derive/src/convert.rs +++ b/ffi/derive/src/convert.rs @@ -86,7 +86,9 @@ fn variant_discriminants(enum_: &syn::DataEnum) -> Vec { } fn derive_try_from_repr_c_for_opaque_item_wrapper(name: &Ident) -> TokenStream2 { - let opaque_item_slice_into_ffi_derive = derive_try_from_repr_c_for_opaque_item_slice(name); + let opaque_item_slice_try_from_repr_c_derive = + derive_try_from_repr_c_for_opaque_item_slice(name); + let opaque_item_vec_try_from_repr_c_derive = derive_try_from_repr_c_for_opaque_item_vec(name); quote! { impl<'itm> iroha_ffi::TryFromReprC<'itm> for #name { @@ -139,12 +141,15 @@ fn derive_try_from_repr_c_for_opaque_item_wrapper(name: &Ident) -> TokenStream2 } } - #opaque_item_slice_into_ffi_derive + #opaque_item_slice_try_from_repr_c_derive + #opaque_item_vec_try_from_repr_c_derive } } fn derive_try_from_repr_c_for_opaque_item(name: &Ident) -> TokenStream2 { - let opaque_item_slice_into_ffi_derive = derive_try_from_repr_c_for_opaque_item_slice(name); + let opaque_item_slice_try_from_repr_c_derive = + derive_try_from_repr_c_for_opaque_item_slice(name); + let opaque_item_vec_try_from_repr_c_derive = derive_try_from_repr_c_for_opaque_item_vec(name); quote! { impl<'itm> iroha_ffi::TryFromReprC<'itm> for #name { @@ -187,7 +192,8 @@ fn derive_try_from_repr_c_for_opaque_item(name: &Ident) -> TokenStream2 { } } - #opaque_item_slice_into_ffi_derive + #opaque_item_slice_try_from_repr_c_derive + #opaque_item_vec_try_from_repr_c_derive } } @@ -213,6 +219,29 @@ fn derive_try_from_repr_c_for_opaque_item_slice(name: &Ident) -> TokenStream2 { } } +fn derive_try_from_repr_c_for_opaque_item_vec(name: &Ident) -> TokenStream2 { + quote! { + impl<'itm> iroha_ffi::owned::TryFromReprCVec<'itm> for #name { + type Source = iroha_ffi::slice::SliceRef<'itm, >::Source>; + type Store = (); + + unsafe fn try_from_repr_c( + source: Self::Source, + _: &'itm mut >::Store, + ) -> iroha_ffi::Result> { + let slice = source.into_rust().ok_or(iroha_ffi::FfiReturn::ArgIsNull)?; + let mut res = Vec::with_capacity(slice.len()); + + for elem in slice { + res.push(iroha_ffi::TryFromReprC::try_from_repr_c(*elem, &mut ())?); + } + + Ok(res) + } + } + } +} + fn derive_try_from_repr_c_for_item(_: &Ident) -> TokenStream2 { quote! { // TODO: @@ -248,15 +277,17 @@ fn derive_try_from_repr_c_for_fieldless_enum( quote! { impl<'itm> iroha_ffi::TryFromReprC<'itm> for #enum_name { - type Source = #ffi_type; + type Source = <#ffi_type as iroha_ffi::TryFromReprC<'itm>>::Source; type Store = (); unsafe fn try_from_repr_c( source: >::Source, - _: &mut >::Store + store: &mut >::Store ) -> iroha_ffi::Result { #( #discriminants )* + let source: #ffi_type = iroha_ffi::TryFromReprC::try_from_repr_c(source, store)?; + match source { #( #discriminant_names => Ok(#enum_name::#variant_names), )* _ => Err(iroha_ffi::FfiReturn::TrapRepresentation), @@ -323,6 +354,7 @@ fn derive_try_from_repr_c_for_fieldless_enum( fn derive_into_ffi_for_opaque_item_wrapper(name: &Ident) -> TokenStream2 { let opaque_item_slice_into_ffi_derive = derive_into_ffi_for_opaque_item_slice(name); + let opaque_item_vec_into_ffi_derive = derive_into_ffi_for_opaque_item_vec(name); quote! { impl iroha_ffi::IntoFfi for #name { @@ -350,11 +382,13 @@ fn derive_into_ffi_for_opaque_item_wrapper(name: &Ident) -> TokenStream2 { } #opaque_item_slice_into_ffi_derive + #opaque_item_vec_into_ffi_derive } } fn derive_into_ffi_for_opaque_item(name: &Ident) -> TokenStream2 { let opaque_item_slice_into_ffi_derive = derive_into_ffi_for_opaque_item_slice(name); + let opaque_item_vec_into_ffi_derive = derive_into_ffi_for_opaque_item_vec(name); quote! { impl iroha_ffi::IntoFfi for #name { @@ -388,6 +422,7 @@ fn derive_into_ffi_for_opaque_item(name: &Ident) -> TokenStream2 { } #opaque_item_slice_into_ffi_derive + #opaque_item_vec_into_ffi_derive } } @@ -403,6 +438,18 @@ fn derive_into_ffi_for_opaque_item_slice(name: &Ident) -> TokenStream2 { } } +fn derive_into_ffi_for_opaque_item_vec(name: &Ident) -> TokenStream2 { + quote! { + impl iroha_ffi::owned::IntoFfiVec for #name { + type Target = iroha_ffi::owned::LocalSlice<<#name as iroha_ffi::IntoFfi>::Target>; + + fn into_ffi(source: Vec) -> Self::Target { + source.into_iter().map(IntoFfi::into_ffi).collect() + } + } + } +} + fn derive_into_ffi_for_item(_: &Ident) -> TokenStream2 { quote! { // TODO: @@ -414,10 +461,10 @@ fn derive_into_ffi_for_fieldless_enum(enum_name: &Ident, repr: &[syn::NestedMeta quote! { impl iroha_ffi::IntoFfi for #enum_name { - type Target = #ffi_type; + type Target = <#ffi_type as iroha_ffi::IntoFfi>::Target; fn into_ffi(self) -> Self::Target { - self as #ffi_type + (self as #ffi_type).into_ffi() } } diff --git a/ffi/src/handle.rs b/ffi/src/handle.rs index 28ad428d1bd..4a262eec450 100644 --- a/ffi/src/handle.rs +++ b/ffi/src/handle.rs @@ -113,7 +113,8 @@ macro_rules! def_ffi_fn { handle_id: $crate::handle::Id, left_handle_ptr: *const core::ffi::c_void, right_handle_ptr: *const core::ffi::c_void, - output_ptr: *mut u8, + // Pointer to FFI-safe representation of `bool` (u8 or u32 if `wasm` feature is active) + output_ptr: *mut ::Target, ) -> $crate::FfiReturn { $crate::def_ffi_fn!(@catch_unwind { use core::borrow::Borrow; @@ -152,7 +153,8 @@ macro_rules! def_ffi_fn { handle_id: $crate::handle::Id, left_handle_ptr: *const core::ffi::c_void, right_handle_ptr: *const core::ffi::c_void, - output_ptr: *mut i8, + // Pointer to FFI-safe representation of `Ordering` (i8 or i32 if `wasm` feature is active) + output_ptr: *mut ::Target, ) -> $crate::FfiReturn { $crate::def_ffi_fn!(@catch_unwind { use core::borrow::Borrow; diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 091371f6efb..d13a7f7a2f6 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -7,6 +7,8 @@ extern crate alloc; +use alloc::vec::Vec; + pub use iroha_ffi_derive::*; use owned::Local; @@ -107,6 +109,8 @@ pub trait Output: Sized { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i8)] pub enum FfiReturn { + /// The input argument provided to FFI function can't be converted into inner rust representation. + ConversionFailed = -7, /// The input argument provided to FFI function has a trap representation. TrapRepresentation = -6, /// FFI function execution panicked. @@ -315,6 +319,40 @@ macro_rules! impl_tuple { Local::new($ffi_ty($( <$ty as IntoFfi>::into_ffi($ty),)+)) } } + + impl<$($ty: IntoFfi),+> $crate::owned::IntoFfiVec for ($( $ty, )+) { + type Target = $crate::owned::LocalSlice<::Target>; + + fn into_ffi(source: Vec) -> Self::Target { + source.into_iter().map(IntoFfi::into_ffi).collect() + } + } + + impl<'itm, $($ty: TryFromReprC<'itm>),+> $crate::owned::TryFromReprCVec<'itm> for ($( $ty, )+) { + type Source = $crate::slice::SliceRef<'itm, >::Source>; + type Store = Vec<>::Store>; + + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'itm mut Self::Store, + ) -> Result> { + let prev_store_len = store.len(); + let slice = source.into_rust().ok_or(FfiReturn::ArgIsNull)?; + store.extend(core::iter::repeat_with(Default::default).take(slice.len())); + + let mut substore = &mut store[prev_store_len..]; + let mut res = Vec::with_capacity(slice.len()); + + let mut i = 0; + while let Some((first, rest)) = substore.split_first_mut() { + res.push(TryFromReprC::try_from_repr_c(slice[i], first)?); + substore = rest; + i += 1; + } + + Ok(res) + } + } }; // NOTE: This is a trick to index tuples diff --git a/ffi/src/owned.rs b/ffi/src/owned.rs index be63a77c5ef..2d939bd6b1c 100644 --- a/ffi/src/owned.rs +++ b/ffi/src/owned.rs @@ -5,9 +5,45 @@ use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec}; use crate::{ slice::{OutBoxedSlice, SliceRef}, - AsReprCRef, FfiReturn, IntoFfi, Output, ReprC, TryFromReprC, + AsReprCRef, FfiReturn, IntoFfi, Output, ReprC, Result, TryFromReprC, }; +/// Trait that facilitates the implementation of [`IntoFfi`] for vectors of foreign types +pub trait IntoFfiVec: Sized { + /// Immutable vec equivalent of [`IntoFfi::Target`] + type Target: ReprC; + + /// Convert from `&[Self]` into [`Self::Target`] + fn into_ffi(source: Vec) -> Self::Target; +} + +/// Trait that facilitates the implementation of [`TryFromReprC`] for vector of foreign types +pub trait TryFromReprCVec<'slice>: Sized { + /// Vec equivalent of [`TryFromReprC::Source`] + type Source: ReprC + Copy; + + /// Type into which state can be stored during conversion. Useful for returning + /// non-owning types but performing some conversion which requires allocation. + /// Serves similar purpose as does context in a closure + type Store: Default; + + /// Convert from [`Self::Source`] into `&[Self]` + /// + /// # Errors + /// + /// * [`FfiReturn::ArgIsNull`] - given pointer is null + /// * [`FfiReturn::UnknownHandle`] - given id doesn't identify any known handle + /// * [`FfiReturn::TrapRepresentation`] - given value contains trap representation + /// + /// # Safety + /// + /// All conversions from a pointer must ensure pointer validity beforehand + unsafe fn try_from_repr_c( + source: Self::Source, + store: &'slice mut Self::Store, + ) -> Result>; +} + /// Wrapper around `T` that is local to the conversion site. This structure carries /// ownership and care must be taken not to let it transfer ownership into an FFI function // NOTE: It's not possible to mutably reference local context @@ -121,40 +157,34 @@ impl LocalSlice { } } -impl IntoFfi for Vec +impl<'itm, T> IntoFfiVec for &'itm T where - T::Target: ReprC, + &'itm T: IntoFfi, { - type Target = LocalSlice; + type Target = LocalSlice<::Target>; + + fn into_ffi(source: Vec) -> Self::Target { + source.into_iter().map(IntoFfi::into_ffi).collect() + } +} + +impl IntoFfi for Vec { + type Target = T::Target; fn into_ffi(self) -> Self::Target { - self.into_iter().map(IntoFfi::into_ffi).collect() + ::into_ffi(self) } } -impl<'itm, T: TryFromReprC<'itm>> TryFromReprC<'itm> for Vec { - type Source = SliceRef<'itm, T::Source>; - type Store = Vec; +impl<'slice, T: TryFromReprCVec<'slice> + 'slice> TryFromReprC<'slice> for Vec { + type Source = T::Source; + type Store = T::Store; unsafe fn try_from_repr_c( source: Self::Source, - store: &'itm mut Self::Store, - ) -> Result { - let prev_store_len = store.len(); - let slice = source.into_rust().ok_or(FfiReturn::ArgIsNull)?; - store.extend(core::iter::repeat_with(Default::default).take(slice.len())); - - let mut substore = &mut store[prev_store_len..]; - let mut res = Vec::with_capacity(slice.len()); - - let mut i = 0; - while let Some((first, rest)) = substore.split_first_mut() { - res.push(>::try_from_repr_c(slice[i], first)?); - substore = rest; - i += 1; - } - - Ok(res) + store: &'slice mut Self::Store, + ) -> Result { + ::try_from_repr_c(source, store) } } @@ -162,10 +192,7 @@ impl<'itm> TryFromReprC<'itm> for String { type Source = as TryFromReprC<'itm>>::Source; type Store = (); - unsafe fn try_from_repr_c( - source: Self::Source, - _: &mut Self::Store, - ) -> Result { + unsafe fn try_from_repr_c(source: Self::Source, _: &mut Self::Store) -> Result { String::from_utf8(source.into_rust().ok_or(FfiReturn::ArgIsNull)?.to_owned()) .map_err(|_e| FfiReturn::Utf8Error) } @@ -174,10 +201,7 @@ impl<'itm> TryFromReprC<'itm> for &'itm str { type Source = <&'itm [u8] as TryFromReprC<'itm>>::Source; type Store = (); - unsafe fn try_from_repr_c( - source: Self::Source, - _: &mut Self::Store, - ) -> Result { + unsafe fn try_from_repr_c(source: Self::Source, _: &mut Self::Store) -> Result { core::str::from_utf8(source.into_rust().ok_or(FfiReturn::ArgIsNull)?) .map_err(|_e| FfiReturn::Utf8Error) } diff --git a/ffi/src/primitives.rs b/ffi/src/primitives.rs index bd8f1614e1c..a1954315df1 100644 --- a/ffi/src/primitives.rs +++ b/ffi/src/primitives.rs @@ -1,6 +1,10 @@ +//! Logic related to the conversion of primitive types. #![allow(trivial_casts)] +use alloc::vec::Vec; + use crate::{ + owned::{IntoFfiVec, LocalSlice, TryFromReprCVec}, slice::{ IntoFfiSliceMut, IntoFfiSliceRef, SliceMut, SliceRef, TryFromReprCSliceMut, TryFromReprCSliceRef, @@ -64,7 +68,7 @@ impl<'slice> TryFromReprCSliceRef<'slice> for bool { } impl IntoFfi for bool { - type Target = u8; + type Target = ::Target; fn into_ffi(self) -> Self::Target { u8::from(self).into_ffi() @@ -139,10 +143,10 @@ impl<'slice> TryFromReprCSliceRef<'slice> for core::cmp::Ordering { } impl IntoFfi for core::cmp::Ordering { - type Target = i8; + type Target = ::Target; fn into_ffi(self) -> Self::Target { - self as i8 + (self as i8).into_ffi() } } impl IntoFfi for &core::cmp::Ordering { @@ -169,16 +173,51 @@ impl<'itm> IntoFfiSliceRef<'itm> for core::cmp::Ordering { } } +/// Trait for replacing unsupported types with supported when crossing WASM FFI-boundary +#[cfg(feature = "wasm")] +pub trait WasmRepr { + /// Type used to represent [`Self`] when crossing FFI-boundry + type Repr: ReprC; +} + +#[cfg(feature = "wasm")] +macro_rules! wasm_repr_impls { + ($src:ty = $dst:ty, $($tail:tt)+) => { + wasm_repr_impls! { $src = $dst } + wasm_repr_impls! { $($tail)+ } + }; + ($src:ty, $($tail:tt)+) => { + wasm_repr_impls! { $src } + wasm_repr_impls! { $($tail)+ } + }; + ($src:ty = $dst:ty) => { + impl WasmRepr for $src { + type Repr = $dst; + } + }; + ($src:ty) => { + impl WasmRepr for $src { + type Repr = Self; + } + }; +} + +#[cfg(feature = "wasm")] +wasm_repr_impls! {u8 = u32, u16 = u32, i8 = i32, i16 = i32, u32, u64, i32, i64} + macro_rules! primitive_impls { ( $( $ty:ty ),+ $(,)? ) => { $( unsafe impl ReprC for $ty {} impl TryFromReprC<'_> for $ty { + #[cfg(feature = "wasm")] + type Source = <$ty as WasmRepr>::Repr; + #[cfg(not(feature = "wasm"))] type Source = Self; type Store = (); unsafe fn try_from_repr_c(source: Self::Source, _: &mut Self::Store) -> Result { - Ok(source) + source.try_into().map_err(|_| FfiReturn::ConversionFailed) } } @@ -201,10 +240,13 @@ macro_rules! primitive_impls { } impl IntoFfi for $ty { + #[cfg(feature = "wasm")] + type Target = <$ty as WasmRepr>::Repr; + #[cfg(not(feature = "wasm"))] type Target = Self; fn into_ffi(self) -> Self::Target { - self + self.into() } } @@ -221,8 +263,29 @@ macro_rules! primitive_impls { fn into_ffi(source: &mut [Self]) -> Self::Target { SliceMut::from_slice(source) } - } )+ - }; + } + + impl IntoFfiVec for $ty + { + type Target = LocalSlice<$ty>; + + fn into_ffi(source: Vec<$ty>) -> Self::Target { + source.into_iter().collect() + } + } + + impl<'itm> TryFromReprCVec<'itm> for $ty { + type Source = SliceRef<'itm, $ty>; + type Store = (); + + unsafe fn try_from_repr_c( + source: Self::Source, + _: &'itm mut Self::Store, + ) -> Result> { + source.into_rust().ok_or(FfiReturn::ArgIsNull).map(alloc::borrow::ToOwned::to_owned) + } + } + )+}; } -primitive_impls! {u8, u16, u32, u64, u128, i8, i16, i32, i64} +primitive_impls! {u8, u16, u32, u64, i8, i16, i32, i64} diff --git a/ffi/tests/ffi_export.rs b/ffi/tests/ffi_export.rs index 6cef3f43ddb..4e4a70908a3 100644 --- a/ffi/tests/ffi_export.rs +++ b/ffi/tests/ffi_export.rs @@ -89,6 +89,12 @@ impl FfiStruct { } } +#[ffi_export] +/// Return byte +pub fn simple(byte: u8) -> u8 { + byte +} + fn get_new_struct() -> FfiStruct { let name = Name(String::from("X")); @@ -286,13 +292,27 @@ fn return_result() { unsafe { assert_eq!( FfiReturn::ExecutionFail, - FfiStruct__fallible_int_output(u8::from(false), output.as_mut_ptr()) + FfiStruct__fallible_int_output(From::from(false), output.as_mut_ptr()) ); assert_eq!(0, output.assume_init()); assert_eq!( FfiReturn::Ok, - FfiStruct__fallible_int_output(u8::from(true), output.as_mut_ptr()) + FfiStruct__fallible_int_output(From::from(true), output.as_mut_ptr()) ); assert_eq!(42, output.assume_init()); } } + +#[cfg(feature = "wasm")] +#[test] +fn conversion_failed() { + let byte: u32 = u32::MAX; + let mut output = MaybeUninit::new(0); + + unsafe { + assert_eq!( + FfiReturn::ConversionFailed, + __simple(byte, output.as_mut_ptr()) + ) + } +} diff --git a/ffi/tests/gen_shared_fns.rs b/ffi/tests/gen_shared_fns.rs index ea0e4dba5cd..bbdfe4c0592 100644 --- a/ffi/tests/gen_shared_fns.rs +++ b/ffi/tests/gen_shared_fns.rs @@ -59,7 +59,7 @@ fn gen_shared_fns() { cloned }; - let mut is_equal = MaybeUninit::::new(1); + let mut is_equal = MaybeUninit::new(1); let cloned_ptr = IntoFfi::into_ffi(&cloned); __eq( @@ -72,8 +72,7 @@ fn gen_shared_fns() { TryFromReprC::try_from_repr_c(is_equal.assume_init(), &mut ()).unwrap(); assert!(is_equal); - // TODO: Fix - let mut ordering = MaybeUninit::::new(1); + let mut ordering = MaybeUninit::new(1); __ord( FfiStruct1::ID, ffi_struct1.cast(),