Skip to content

Commit

Permalink
[feature] hyperledger-iroha#2231: Generate FFI wrapper API
Browse files Browse the repository at this point in the history
Signed-off-by: Marin Veršić <marin.versic101@gmail.com>
  • Loading branch information
mversic committed Dec 20, 2022
1 parent 2901afa commit 98818b3
Show file tree
Hide file tree
Showing 22 changed files with 1,844 additions and 609 deletions.
1 change: 1 addition & 0 deletions data_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ pub enum Value {
FfiType,
IntoSchema,
)]
#[ffi_type(opaque)]
pub enum NumericValue {
/// `u32` value
U32(u32),
Expand Down
38 changes: 31 additions & 7 deletions ffi/derive/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ fn derive_ffi_type_for_transparent_item(input: &mut syn::DeriveInput) -> TokenSt
impl<#impl_generics> iroha_ffi::ir::Ir for &#name #ty_generics #where_clause {
type Type = iroha_ffi::ir::Transparent;
}

impl iroha_ffi::ReturnTypeOf<#name #ty_generics> for #inner #where_clause {
type Type = #name #ty_generics;
}
impl iroha_ffi::option::Niche for #name #ty_generics #where_clause {
const NICHE_VALUE: <#inner as iroha_ffi::FfiType>::ReprC = <#inner as iroha_ffi::option::Niche>::NICHE_VALUE;
}
}
}
}
Expand All @@ -165,7 +172,7 @@ fn derive_ffi_type_for_fieldless_enum(

quote! {
impl iroha_ffi::option::Niche for #enum_name {
const NICHE_VALUE: Self::ReprC = Self::ReprC::MAX;
const NICHE_VALUE: <Self as FfiType>::ReprC = <Self as FfiType>::ReprC::MAX;
}

iroha_ffi::ffi_type! {
Expand All @@ -190,8 +197,8 @@ fn derive_ffi_type_for_data_carrying_enum(
) -> TokenStream {
let (repr_c_enum_name, repr_c_enum) =
gen_data_carrying_repr_c_enum(enum_name, &mut generics, enum_);
let mut non_local_where_clause = generics.make_where_clause().clone();

generics.make_where_clause();
let lifetime = quote! {'__iroha_ffi_itm};
let (impl_generics, ty_generics, where_clause) = split_for_impl(&mut generics);

Expand Down Expand Up @@ -298,6 +305,8 @@ fn derive_ffi_type_for_data_carrying_enum(
let non_locality = if local {
quote! {}
} else {
let mut non_local_where_clause = where_clause.expect_or_abort("Defined").clone();

enum_
.variants
.iter()
Expand All @@ -316,7 +325,26 @@ fn derive_ffi_type_for_data_carrying_enum(
.map(|ty| parse_quote! {#ty: iroha_ffi::repr_c::NonLocal<<#ty as iroha_ffi::ir::Ir>::Type>})
.for_each(|predicate| non_local_where_clause.predicates.push(predicate));

quote! {unsafe impl<#impl_generics> iroha_ffi::repr_c::NonLocal<Self> for #enum_name #ty_generics #non_local_where_clause {}}
quote! {
unsafe impl<#impl_generics> iroha_ffi::repr_c::NonLocal<Self> for #enum_name #ty_generics #non_local_where_clause {}

impl<#impl_generics> iroha_ffi::repr_c::CWrapperOutput<Self> for #enum_name #ty_generics #non_local_where_clause {
type ReturnType = Self;
}
impl<#impl_generics> iroha_ffi::repr_c::COutPtr<Self> for #enum_name #ty_generics #non_local_where_clause {
type OutPtr = Self::ReprC;
}
impl<#impl_generics> iroha_ffi::repr_c::COutPtrWrite<Self> for #enum_name #ty_generics #non_local_where_clause {
unsafe fn write_out(self, out_ptr: *mut Self::OutPtr) {
iroha_ffi::repr_c::write_non_local::<_, Self>(self, out_ptr);
}
}
impl<#impl_generics> iroha_ffi::repr_c::COutPtrRead<Self> for #enum_name #ty_generics #non_local_where_clause {
unsafe fn try_read_out(out_ptr: Self::OutPtr) -> iroha_ffi::Result<Self> {
iroha_ffi::repr_c::read_non_local::<Self, Self>(out_ptr)
}
}
}
};

quote! {
Expand Down Expand Up @@ -355,10 +383,6 @@ fn derive_ffi_type_for_data_carrying_enum(
}
}

impl<#impl_generics> iroha_ffi::repr_c::COutPtr<Self> for #enum_name #ty_generics #where_clause {
type OutPtr = *mut Self::ReprC;
}

#non_locality
}
}
Expand Down
23 changes: 13 additions & 10 deletions ffi/derive/src/ffi_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;

use crate::impl_visitor::{ffi_output_arg, Arg, FnDescriptor};
use crate::{
impl_visitor::{ffi_output_arg, Arg, FnDescriptor},
util::gen_resolve_type,
};

pub fn gen_declaration(fn_descriptor: &FnDescriptor) -> TokenStream {
let ffi_fn_name = gen_fn_name(fn_descriptor);
Expand Down Expand Up @@ -120,7 +123,7 @@ fn gen_input_arg(arg: &Arg) -> TokenStream {

fn gen_out_ptr_arg(arg: &Arg) -> TokenStream {
let (arg_name, arg_type) = (arg.name(), arg.src_type_resolved());
quote! { #arg_name: <#arg_type as iroha_ffi::FfiOutPtr>::OutPtr }
quote! { #arg_name: *mut <#arg_type as iroha_ffi::FfiOutPtr>::OutPtr }
}

fn gen_body(fn_descriptor: &FnDescriptor) -> syn::Block {
Expand Down Expand Up @@ -185,16 +188,16 @@ fn gen_method_call_stmt(fn_descriptor: &FnDescriptor) -> TokenStream {
}

fn gen_output_assignment_stmts(fn_descriptor: &FnDescriptor) -> TokenStream {
match &fn_descriptor.output_arg {
Some(out_arg) => {
fn_descriptor.output_arg.as_ref().map_or_else(
|| quote! {},
|out_arg| {
let (arg_name, arg_type) = (out_arg.name(), out_arg.src_type_resolved());
let output_arg_conversion = crate::util::gen_arg_src_to_ffi(out_arg, true);
let resolve_impl_trait = gen_resolve_type(out_arg, true);

quote! {
#output_arg_conversion
<<#arg_type as iroha_ffi::FfiOutPtr>::OutPtr as iroha_ffi::OutPtrOf<_>>::write(__out_ptr, #arg_name)?;
#resolve_impl_trait
<#arg_type as iroha_ffi::FfiOutPtrWrite>::write_out(#arg_name, __out_ptr);
}
}
None => quote! {},
}
},
)
}
2 changes: 1 addition & 1 deletion ffi/derive/src/impl_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Arg {
self.type_.clone()
} else {
let elem = &array.elem;
parse_quote! {&mut #elem}
parse_quote! {Box<#elem>}
}
} else {
self.type_.clone()
Expand Down
5 changes: 4 additions & 1 deletion ffi/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use proc_macro::TokenStream;
use proc_macro_error::abort;
use quote::quote;
use syn::{parse_macro_input, parse_quote, Item, NestedMeta};
use wrapper::wrap_method;

use crate::convert::derive_ffi_type;

Expand Down Expand Up @@ -238,8 +239,10 @@ pub fn ffi_import(_attr: TokenStream, item: TokenStream) -> TokenStream {

let fn_descriptor = FnDescriptor::from_fn(&item);
let ffi_fn = ffi_fn::gen_declaration(&fn_descriptor);
let wrapped_item = wrap_method(&fn_descriptor);

quote! {
#item
#wrapped_item
#ffi_fn
}
}
Expand Down
28 changes: 17 additions & 11 deletions ffi/derive/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,43 +48,49 @@ pub fn gen_arg_ffi_to_src(arg: &Arg) -> TokenStream {
}
}

#[allow(clippy::expect_used)]
pub fn gen_arg_src_to_ffi(arg: &Arg, is_output: bool) -> TokenStream {
pub fn gen_resolve_type(arg: &Arg, is_output: bool) -> TokenStream {
let (arg_name, src_type) = (arg.name(), arg.src_type());
let ffi_type = arg.ffi_type_resolved(is_output);
let store_name = gen_store_name(arg_name);

let mut resolve_impl_trait = None;
let mut resolve_impl_trait = quote! {};
if let Type::ImplTrait(type_) = &src_type {
for bound in &type_.bounds {
if let syn::TypeParamBound::Trait(trait_) = bound {
let trait_ = trait_.path.segments.last().expect_or_abort("Defined");

if trait_.ident == "IntoIterator" || trait_.ident == "ExactSizeIterator" {
resolve_impl_trait = Some(quote! {
resolve_impl_trait = quote! {
let #arg_name: Vec<_> = #arg_name.into_iter().collect();
});
};
} else if trait_.ident == "Into" {
resolve_impl_trait = Some(quote! {
resolve_impl_trait = quote! {
let #arg_name = #arg_name.into();
});
};
}
}
}
}

if is_output && unwrap_result_type(src_type).is_some() {
return quote! {
let mut #store_name = Default::default();
let #arg_name = if let Ok(ok) = #arg_name {
iroha_ffi::FfiConvert::into_ffi(ok, &mut #store_name)
ok
} else {
// TODO: Implement error handling (https://github.com/hyperledger/iroha/issues/2252)
return Err(iroha_ffi::FfiReturn::ExecutionFail);
};
};
}

resolve_impl_trait
}

#[allow(clippy::expect_used)]
pub fn gen_arg_src_to_ffi(arg: &Arg, is_output: bool) -> TokenStream {
let arg_name = arg.name();
let resolve_impl_trait = gen_resolve_type(arg, is_output);
let ffi_type = arg.ffi_type_resolved(is_output);
let store_name = gen_store_name(arg_name);

quote! {
#resolve_impl_trait
let mut #store_name = Default::default();
Expand Down
104 changes: 81 additions & 23 deletions ffi/derive/src/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use quote::quote;

use crate::{
ffi_fn,
impl_visitor::{Arg, FnDescriptor},
util::{gen_arg_ffi_to_src, gen_arg_src_to_ffi},
impl_visitor::{unwrap_result_type, FnDescriptor},
util::gen_arg_src_to_ffi,
};

pub fn wrap_as_opaque(input: &syn::DeriveInput) -> TokenStream {
Expand All @@ -26,7 +26,7 @@ pub fn wrap_as_opaque(input: &syn::DeriveInput) -> TokenStream {
#[repr(transparent)]
#vis #item_type #ident {
__opaque_ptr: *mut iroha_ffi::Extern
};
}

unsafe impl iroha_ffi::ReprC for #ident {}
}
Expand All @@ -52,12 +52,17 @@ pub fn wrap_impl_item(fns: &[FnDescriptor]) -> TokenStream {
}
}

fn wrap_method(fn_descriptor: &FnDescriptor) -> TokenStream {
let (method_doc, signature) = (&fn_descriptor.doc, &fn_descriptor.sig);
pub fn wrap_method(fn_descriptor: &FnDescriptor) -> TokenStream {
let (method_doc, mut signature) = (&fn_descriptor.doc, fn_descriptor.sig.clone());

let method_body = gen_wrapper_method_body(fn_descriptor);
if let syn::ReturnType::Type(_, output) = &mut signature.output {
// Patch the return type to facilitate returning types referencing local store
**output = syn::parse_quote! {<#output as iroha_ffi::FfiWrapperOutput>::ReturnType};
}

quote! {
#[doc = #method_doc]
#method_doc
pub #signature {
#method_body
}
Expand All @@ -67,47 +72,100 @@ fn wrap_method(fn_descriptor: &FnDescriptor) -> TokenStream {
fn gen_wrapper_method_body(fn_descriptor: &FnDescriptor) -> TokenStream {
let input_conversions = gen_input_conversion_stmts(fn_descriptor);
let ffi_fn_call_stmt = gen_ffi_fn_call_stmt(fn_descriptor);
let return_stmt = fn_descriptor.output_arg.as_ref().map(gen_return_stmt);
let return_stmt = gen_return_stmt(fn_descriptor);

quote! {
#input_conversions
#ffi_fn_call_stmt
#return_stmt

// SAFETY:
// 1. call to FFI function is safe, i.e. it's implementation is free from UBs.
// 2. out-pointer is initialized, i.e. MaybeUninit::assume_init() is not UB
unsafe {
#ffi_fn_call_stmt
#return_stmt
}
}
}

fn gen_input_conversion_stmts(fn_descriptor: &FnDescriptor) -> TokenStream {
let mut stmts = quote! {};

if let Some(arg) = &fn_descriptor.receiver {
return gen_arg_ffi_to_src(arg);
stmts.extend(gen_arg_src_to_ffi(arg, false));
}

let mut stmts = quote! {};
for arg in &fn_descriptor.input_args {
stmts.extend(gen_arg_src_to_ffi(arg, false));
}
if let Some(arg) = &fn_descriptor.output_arg {
let name = &arg.name();

stmts.extend(quote! {
let mut #name = core::mem::MaybeUninit::uninit();
});
}

stmts
}

fn gen_ffi_fn_call_stmt(fn_descriptor: &FnDescriptor) -> TokenStream {
let ffi_fn_name = ffi_fn::gen_fn_name(fn_descriptor);
let arg_names: Vec<_> = fn_descriptor.input_args.iter().map(Arg::name).collect();

quote! {
let __output = #ffi_fn_name(#(#arg_names),*);
let mut arg_names = quote! {};
if let Some(arg) = &fn_descriptor.receiver {
let arg_name = &arg.name();

arg_names.extend(quote! {
#arg_name,
});
}
}
for arg in &fn_descriptor.input_args {
let arg_name = &arg.name();

fn gen_return_stmt(arg: &Arg) -> TokenStream {
let (arg_name, output_arg_conversion) = (arg.name(), gen_arg_ffi_to_src(arg));
arg_names.extend(quote! {
#arg_name,
});
}
if let Some(arg) = &fn_descriptor.output_arg {
let arg_name = &arg.name();

arg_names.extend(quote! {
#arg_name.as_mut_ptr()
});
}

let execution_fail_arm = fn_descriptor.output_arg.as_ref().map_or_else(
|| quote! {},
|output| {
if unwrap_result_type(output.src_type()).is_some() {
quote! { iroha_ffi::FfiReturn::ExecutionFail => {return Err(());} }
} else {
quote! {}
}
},
);

quote! {
#output_arg_conversion
let __ffi_return = #ffi_fn_name(#arg_names);

if __output == iroha_ffi::FfiReturn::Ok {
TryFromReprC::try_from_repr_c(#arg_name).expect("Must not fail");
} else {
panic!("{} returned {}", ffi_fn_name, __output);
match __ffi_return {
iroha_ffi::FfiReturn::Ok => {},
#execution_fail_arm
_ => panic!(concat!(stringify!(#ffi_fn_name), " returned {:?}"), __ffi_return)
}
}
}

fn gen_return_stmt(fn_descriptor: &FnDescriptor) -> TokenStream {
fn_descriptor.output_arg.as_ref().map_or_else(|| quote! {}, |output| {
let arg_name = &output.name();

let return_stmt = unwrap_result_type(output.src_type())
.map_or_else(|| (quote! {#arg_name}), |_| (quote! { Ok(#arg_name) }));

quote! {
let #arg_name = #arg_name.assume_init();
let #arg_name = iroha_ffi::FfiOutPtrRead::try_read_out(#arg_name).expect("Invalid out-pointer value returned");
#return_stmt
}
})
}
Loading

0 comments on commit 98818b3

Please sign in to comment.