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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! Set of macros designed to help creating and implementing WebAssembly APIs and bindings,
//! designed to run in the Ark environment.
//!
//! See also the [private Ark
//! documentation](https://ark.embark.dev/internals/creating-apis/index.html) for details of how
//! things work, motivation behind this crate, as well as thorough examples of what it can do.
//!
//! # The problem
//!
//! Implementing WebAssembly FFI (foreign function interfaces) by hand is tedious and doesn't
//! allow using idiomatic Rust, like taking or returning non-plain types (user structs, enum, etc.
//! but also idiomatic Rust types like `Result`/`String` and so on and so forth).
//!
//! # The solution
//!
//! This crate provides macros that are designed to work together to solve this particular
//! problem, and allow the usage of high-level patterns in APIs.
//!
//! ## User-side `ark_bindgen` macro
//!
//! This macro applies on a `mod` defined in a Rust file that is to be compiled to WebAssembly.
//! The output of this macro depends on whether it's running on the host or not:
//!
//! - On the host, this will create an `HostShim` trait, that contains a few static internal
//! methods, as well as methods mirroring the extern C function declarations, using high-level,
//! idiomatic Rust patterns. This macro can be partially implemented automatically with the
//! `host_exports` macro, described below.
//! - In user code, this will also create the trait as well as a `safe` Rust `mod` containing
//! functions with the same signatures as in the declaration.
//!
//! Here are the different kind of declarations one can do with it:
//!
//! // TODO reinclude, see also #4516
//! //```rust,ignore
//! //#![doc = include_str!("../../api-ffi/src/ffi/example_automatic.rs")]
//! //```
//!
//! ## Host-side `host_export` macro
//!
//! This macro applied on impl blocks for the `HostShim` trait generated by the above macro. An end
//! user only needs to implement the `_shim` functions in the trait, and get the rest implemented
//! for free by this macro.
//!
//! // TODO reinclude, see also #4516
//! //```rust,ignore
//! //#![doc = include_str!("../../../components/module-host/src/host_api/examples/automatic_macro.rs")]
//! //```
//!
//! ## `ark_test` macro
//!
//! This macro is designed to replicate the behavior of `#[test]` in Rust modules compiled to
//! WebAssembly.

use proc_macro::TokenStream;
use quote::quote;

mod bindgen;
mod ffi_union;
mod host_exports;
mod test;
mod utils;

/// Replaces an impl block of an `HostShim` trait (generated by the `ark_bindgen` macro) by an
/// augmented one that automatically implements some functions, making it more ergonomic and
/// pleasant to use.
///
/// Note this macro is designed to be used on the **host**.
///
/// See the crate description for examples of what you can do with it.
#[proc_macro_attribute]
pub fn host_exports(_args: TokenStream, input: TokenStream) -> TokenStream {
    let mut item = syn::parse_macro_input!(input as host_exports::Item);
    match host_exports::expand(&mut item) {
        Ok(_) => TokenStream::from(quote!(#item)),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Replaces a `mod` block by another one that augments type/struct/external function declarations
/// to make them more concise, ergonomic and pleasant to use.
///
/// In particular, generates an `HostShim` trait that is implemented partially automatically thanks
/// to the `host_exports` macro above.
///
/// Note this macro is designed to be used in WebAssembly code (creating a `safe` Rust module
/// that can used directly) *and* on the host (creating the trait that will be implemented by the
/// above macro).
///
/// See the crate description for examples of what you can do with it.
///
/// This unfortunately can't be applied at module scope yet; see also
/// <https://github.com/rust-lang/rust/issues/54726>.
#[proc_macro_attribute]
pub fn ark_bindgen(args: TokenStream, input: TokenStream) -> TokenStream {
    let args = syn::parse_macro_input!(args as bindgen::Args);
    let mut item = syn::parse_macro_input!(input as bindgen::Item);

    match bindgen::expand(&mut item, args) {
        Ok(_) => TokenStream::from(quote!(#item)),
        Err(e) => e.to_compile_error().into(),
    }
}

/// Macro emulating the behavior of the standard `#[test]` macro, but for module code compiled to
/// WebAssembly. Such a test can then be executed with ark using `ark module test`.
///
/// ```rust,ignore
/// #[ark_test]
/// fn makes_a_sad() {
///     assert_eq!(1, 2, "me am gud in nummers");
/// }
/// ```
#[proc_macro_attribute]
pub fn ark_test(
    attr: proc_macro::TokenStream,
    body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    match test::try_ark_test(attr, body) {
        Ok(result) => result.into(),
        Err(err) => err.to_compile_error().into(),
    }
}

/// Performs modification necessary to make a union ffi-safe.
///
/// Ultimately this checks for invariants and implements `bytemuck::NoUninit` and `bytemuck::AnyBitPattern`. It is
/// technically possible to make the `bytemuck::NoUninit` implementation invalid with *sound* code,
/// but only through using unsafe code that writes uninit memory into the union. Therefore you have
/// to promise not to do this... which ultimately shouldn't be an issue as this isn't really something
/// that should be a want to do.
///
/// This macro wraps each field in a union into an `ark_api_ffi::TransparentPad<T, N>`
/// such that the total size of the new type is equal to the size specified in the
/// `ffi_union` macro. The size is specified in bytes.
///
/// This macro automatically derives `bytemuck::NoUninit` and `bytemuck::AnyBitPattern`
/// for the union it is applied to, which is necessary so that those derives use the
/// modified version of the union definition.
///
/// # Expected form
///
/// ```ignore
/// #[ark_api_macros::ffi_union(size = <size>, <pod_accessors | checked_accessors>)]
/// ```
///
/// # Example
///
/// ```ignore
/// #[ark_api_macros::ffi_union(size = 8, pod_accessors)]
/// union AutoPadExample {
///     field1: u64,
///     field2: u32,
/// }
/// ```
///
/// Usually, this would result in a union of size 8 but with padding for bytes 4-8
/// when the used field is `field2`. This macro will expand this to (basically):
///
/// ```ignore
/// #[derive(NoUninit, AnyBitPattern)]
/// union AutoPadExample {
///     field1: TransparentPad<u64, 0>,
///     field2: TransparentPad<u32, 4>,
/// }
/// ```
///
/// which adds "explicit"/"manual" padding bytes to the end of `field2` -- making the
/// compiler not treat them as *real* padding.
///
/// This macro will also derive accessor methods on the union for each field in the form
/// `as_{field_name}` (and `try_as_{field_name}`, if using `checked_accessors`) that return references
/// to the field as the field's original type rather than as the wrapped `TransparentPad`.
///
/// These accessors will either use `bytemuck`'s raw or checked casting functions based on
/// the second argument to the proc macro, which can be either `pod_accessors` or `checked_accessors`.
///
/// All fields must implement `Pod` for `pod_accessors`, while all fields must implement
/// `CheckedBitPattern` for `checked_accessors`.
#[proc_macro_attribute]
pub fn ffi_union(
    args: proc_macro::TokenStream,
    body: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let args = syn::parse_macro_input!(args as ffi_union::Args);
    let mut item = syn::parse_macro_input!(body as syn::ItemUnion);

    let item_ident = item.ident.clone();

    match ffi_union::expand(&mut item, args) {
        Ok(ffi_union::ExpansionExtras { extras, accessors }) => TokenStream::from(quote!(
            #(#extras)*
            #item
            impl #item_ident {
                #(#accessors)*
            }
        )),
        Err(e) => e.to_compile_error().into(),
    }
}