1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
extern crate proc_macro;
mod once;

use ::darling::FromDeriveInput;

/// Mark a structure for use across the ffi boundary.
///
/// This procedural macro will generate a sister structure called `FFIMyStruct`. It contains the
/// same fields in the same order, except in that they are represented as ffi compatible types: a
/// [`CString`][1] instead of the original struct's [`String`][2] for example.
///
/// You can find the list of types supported by ffishim [here][7]. You can only use those types in
/// fields of structures that derive `FFIShim`, as well as other structures that themselves derive
/// `FFIShim`.
///
/// An implementation of `from` and `try_into` is provided for `FFIMyStruct`, which can be used
/// for back-and-forth "translation." These translations are executed whenever `MyStruct` is passed
/// to or returned from an [`ffishim_function`][3], the macro which generates stubs around our
/// functions. You should never have to deal with `FFIMyStruct` yourself at any time.
///
/// # C ABI Violation: embedded structs
///
/// Aside from scalar types, nothing is directly embedded in the struct, it is dereferenced
/// behind a pointer. This is because this project is originally written to work with Dart's [new
/// alpha ffi][4], and this version alpha [does not support embeded structures yet][5].
///
/// This is also a reason this ffi shim currently performs many more dynamic memory allocations
/// than would be necessary provided we could use straight-forward ffi structure embeds.
///
/// # Opaque feature
///
/// If you want to embed structures which have no ffi shim, you can mark them as `opaque` like
/// this:
///
/// ```ignore
/// #[derive(FFIShim)]
/// pub struct MyStruct {
///     #[ffishim(opaque)]
///     not_ffi_compatible: ::std::collections::HashMap<i64, i64>,
/// }
/// ```
///
/// Here, the sister structure still features the field in rust, but it is stored in its native
/// rust form. This should make it virtually unusable from across the ffi boundary, but we will not
/// loose it.
///
/// In this case, the `not_ffi_compatible` field will be carried by the corresponding ffi shim
/// `FFIMyStruct`, but it will not be formatted in any way, and will not be consumable from across
/// the ffi boundary.
///
/// If you have many of such fields in the structure, you might be interested in making the whole
/// structure opaque, in which case none of its fields are exposed:
///
/// ```ignore
/// #[derive(FFIShim)]
/// #[ffishim(opaque)]
/// pub struct MyStruct {
///     not_ffi_compatible: ::std::collections::HashMap<i64, i64>,
/// }
/// ```
///
/// [1]: https://doc.rust-lang.org/std/ffi/struct.CString.html
/// [2]: https://doc.rust-lang.org/std/string/struct.String.html
/// [3]: attr.ffishim_function.html
/// [4]: https://dart.dev/guides/libraries/c-interop
/// [5]: https://github.com/dart-lang/sdk/issues/37271
/// [6]: https://github.com/dart-lang/sdk/issues/41062
/// [7]: https://docs.rs/ffishim/0.1.2/ffishim/types/index.html
#[proc_macro_derive(FFIShim, attributes(ffishim))]
pub fn derive_ffishim(stream: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
    let derive_input = ::syn::parse_macro_input!(stream as ::syn::DeriveInput);

    let shim_data = ::ffishim::Data::from_derive_input(&derive_input).unwrap();
    let shim_from = ::ffishim::From::from(&shim_data);
    let shim_try_into = ::ffishim::TryInto::from(&shim_data);
    let shim_news = ::ffishim::News::from(&shim_data);
    let shim_free = ::ffishim::Free::from(&shim_data);
    let shim_allocator_setting = unsafe {
        crate::once::defined_once("shim_allocator_setting", ::ffishim::shim_allocator_setting())
    };

    (::quote::quote! {
        #shim_data
        #shim_from
        #shim_try_into
        #shim_news
        #shim_free
        #shim_allocator_setting
    }).into()
}

/// Mark a function for use across the ffi boundary.
///
/// This procedural macro will generate a stub called `ffi_my_function`. This function features the
/// same arguments as `my_function`, which it wraps. It will convert any arguments from the
/// [`FFIMyStruct`][1] version passed by the caller to the native `MyStruct` version. If there is
/// any return value, it will convert it from its native version back into the ffi one.
///
/// You should never have to call or manipulate `ffi_my_function` from the rust side. It will
/// systematically return our C-ABI equivalent of a `Result`, even if the original `my_function`
/// does not return `Result` itself. This is because the ffi->native type translation can fail, and
/// we need an elegant way to report that (panicking in such a setup is often inacceptable.)
///
/// Take a look at which basic/built-in types you can use [here][4]. You can also use any structure
/// which derive the [`FFIShim`][1] procedural macro.
///
/// # C ABI Violation: passing structs by value
///
/// Any structure passed by value in `my_function` will instead be hidden behind a pointer.
/// This is because [Dart's alpha ffi][2] [does not support passing structures by value][3], and
/// this project was originally written to work with it.
///
/// # Performance implications
///
/// By "type conversions", we mean calling the given structures' `from` and `try_into`
/// implementations. For scalar types for example, that's a simple `mov`. For `String`s, it most
/// likely means a malloc and a memcpy. For complex structures, it is a recursive conversion of all
/// types and sub-types.
///
/// These conversions can quickly become non-trivial, which is why we encourage the user to try to
/// reduce amount of calls and data passed through those calls to the minimum required. If you
/// don't really need a field across the ffi barrier, consider making it [opaque][4].
///
/// [1]: derive.FFIShim.html
/// [2]: https://dart.dev/guides/libraries/c-interop
/// [3]: https://github.com/dart-lang/sdk/issues/41062
/// [4]: https://docs.rs/ffishim/0.1.2/ffishim/types/index.html
#[proc_macro_attribute]
pub fn ffishim_function(
    _: ::proc_macro::TokenStream,
    stream: ::proc_macro::TokenStream,
) -> ::proc_macro::TokenStream {
    let original_function: ::proc_macro2::TokenStream = stream.clone().into();

    let item_fn = ::syn::parse_macro_input!(stream as ::syn::ItemFn);
    let shim_function = ::ffishim::Function::from_item_fn(&item_fn);
    let free_result_function = unsafe {
        crate::once::defined_once("free_result", ::ffishim::library::free_result_function())
    };

    (::quote::quote! {
        #original_function
        #shim_function
        #free_result_function
    }).into()
}