ark_api_macros/lib.rs
1//! Set of macros designed to help creating and implementing WebAssembly APIs and bindings,
2//! designed to run in the Ark environment.
3//!
4//! See also the [private Ark
5//! documentation](https://ark.embark.dev/internals/creating-apis/index.html) for details of how
6//! things work, motivation behind this crate, as well as thorough examples of what it can do.
7//!
8//! # The problem
9//!
10//! Implementing WebAssembly FFI (foreign function interfaces) by hand is tedious and doesn't
11//! allow using idiomatic Rust, like taking or returning non-plain types (user structs, enum, etc.
12//! but also idiomatic Rust types like `Result`/`String` and so on and so forth).
13//!
14//! # The solution
15//!
16//! This crate provides macros that are designed to work together to solve this particular
17//! problem, and allow the usage of high-level patterns in APIs.
18//!
19//! ## User-side `ark_bindgen` macro
20//!
21//! This macro applies on a `mod` defined in a Rust file that is to be compiled to WebAssembly.
22//! The output of this macro depends on whether it's running on the host or not:
23//!
24//! - On the host, this will create an `HostShim` trait, that contains a few static internal
25//! methods, as well as methods mirroring the extern C function declarations, using high-level,
26//! idiomatic Rust patterns. This macro can be partially implemented automatically with the
27//! `host_exports` macro, described below.
28//! - In user code, this will also create the trait as well as a `safe` Rust `mod` containing
29//! functions with the same signatures as in the declaration.
30//!
31//! Here are the different kind of declarations one can do with it:
32//!
33//! // TODO reinclude, see also #4516
34//! //```rust,ignore
35//! //#![doc = include_str!("../../api-ffi/src/ffi/example_automatic.rs")]
36//! //```
37//!
38//! ## Host-side `host_export` macro
39//!
40//! This macro applied on impl blocks for the `HostShim` trait generated by the above macro. An end
41//! user only needs to implement the `_shim` functions in the trait, and get the rest implemented
42//! for free by this macro.
43//!
44//! // TODO reinclude, see also #4516
45//! //```rust,ignore
46//! //#![doc = include_str!("../../../components/module-host/src/host_api/examples/automatic_macro.rs")]
47//! //```
48//!
49//! ## `test` macro
50//!
51//! This macro is designed to replicate the behavior of `#[test]` in Rust modules compiled to
52//! WebAssembly.
53
54#![allow(clippy::wildcard_enum_match_arm)] // not very practical with `syn` crate in here
55#![warn(clippy::trivially_copy_pass_by_ref)]
56
57use proc_macro::TokenStream;
58use quote::quote;
59
60mod bindgen;
61mod ffi_union;
62mod host_exports;
63mod test;
64mod utils;
65
66/// Replaces an impl block of an `HostShim` trait (generated by the `ark_bindgen` macro) by an
67/// augmented one that automatically implements some functions, making it more ergonomic and
68/// pleasant to use.
69///
70/// Note this macro is designed to be used on the **host**.
71///
72/// See the crate description for examples of what you can do with it.
73#[proc_macro_attribute]
74pub fn host_exports(_args: TokenStream, input: TokenStream) -> TokenStream {
75 let mut item = syn::parse_macro_input!(input as host_exports::Item);
76 match host_exports::expand(&mut item) {
77 Ok(_) => TokenStream::from(quote!(#item)),
78 Err(e) => e.to_compile_error().into(),
79 }
80}
81
82/// Replaces a `mod` block by another one that augments type/struct/external function declarations
83/// to make them more concise, ergonomic and pleasant to use.
84///
85/// In particular, generates an `HostShim` trait that is implemented partially automatically thanks
86/// to the `host_exports` macro above.
87///
88/// Note this macro is designed to be used in WebAssembly code (creating a `safe` Rust module
89/// that can used directly) *and* on the host (creating the trait that will be implemented by the
90/// above macro).
91///
92/// See the crate description for examples of what you can do with it.
93///
94/// This unfortunately can't be applied at module scope yet; see also
95/// <https://github.com/rust-lang/rust/issues/54726>.
96#[proc_macro_attribute]
97pub fn ark_bindgen(args: TokenStream, input: TokenStream) -> TokenStream {
98 let args = syn::parse_macro_input!(args as bindgen::Args);
99 let mut item = syn::parse_macro_input!(input as bindgen::Item);
100
101 match bindgen::expand(&mut item, args) {
102 Ok(_) => TokenStream::from(quote!(#item)),
103 Err(e) => e.to_compile_error().into(),
104 }
105}
106
107/// Macro emulating the behavior of the standard `#[test]` macro, but for module code compiled to
108/// WebAssembly. Such a test can then be executed with ark using `ark module test`.
109///
110/// ```rust,ignore
111/// #[test]
112/// fn makes_a_sad() {
113/// assert_eq!(1, 2, "me am good in nummers");
114/// }
115/// ```
116#[proc_macro_attribute]
117pub fn test(
118 attr: proc_macro::TokenStream,
119 body: proc_macro::TokenStream,
120) -> proc_macro::TokenStream {
121 match test::try_ark_test(attr, body) {
122 Ok(result) => result.into(),
123 Err(err) => err.to_compile_error().into(),
124 }
125}
126
127/// Performs modification necessary to make a union ffi-safe.
128///
129/// Ultimately this checks for invariants and implements `bytemuck::NoUninit` and `bytemuck::AnyBitPattern`. It is
130/// technically possible to make the `bytemuck::NoUninit` implementation invalid with *sound* code,
131/// but only through using unsafe code that writes uninit memory into the union. Therefore you have
132/// to promise not to do this... which ultimately shouldn't be an issue as this isn't really something
133/// that should be a want to do.
134///
135/// This macro wraps each field in a union into an `ark_api_ffi::TransparentPad<T, N>`
136/// such that the total size of the new type is equal to the size specified in the
137/// `ffi_union` macro. The size is specified in bytes.
138///
139/// This macro automatically derives `bytemuck::NoUninit` and `bytemuck::AnyBitPattern`
140/// for the union it is applied to, which is necessary so that those derives use the
141/// modified version of the union definition.
142///
143/// # Expected form
144///
145/// ```ignore
146/// #[ark_api_macros::ffi_union(size = <size>, <pod_accessors | checked_accessors>)]
147/// ```
148///
149/// # Example
150///
151/// ```ignore
152/// #[ark_api_macros::ffi_union(size = 8, pod_accessors)]
153/// union AutoPadExample {
154/// field1: u64,
155/// field2: u32,
156/// }
157/// ```
158///
159/// Usually, this would result in a union of size 8 but with padding for bytes 4-8
160/// when the used field is `field2`. This macro will expand this to (basically):
161///
162/// ```ignore
163/// #[derive(NoUninit, AnyBitPattern)]
164/// union AutoPadExample {
165/// field1: TransparentPad<u64, 0>,
166/// field2: TransparentPad<u32, 4>,
167/// }
168/// ```
169///
170/// which adds "explicit"/"manual" padding bytes to the end of `field2` -- making the
171/// compiler not treat them as *real* padding.
172///
173/// This macro will also derive accessor methods on the union for each field in the form
174/// `as_{field_name}` (and `try_as_{field_name}`, if using `checked_accessors`) that return references
175/// to the field as the field's original type rather than as the wrapped `TransparentPad`.
176///
177/// These accessors will either use `bytemuck`'s raw or checked casting functions based on
178/// the second argument to the proc macro, which can be either `pod_accessors` or `checked_accessors`.
179///
180/// All fields must implement `Pod` for `pod_accessors`, while all fields must implement
181/// `CheckedBitPattern` for `checked_accessors`.
182#[proc_macro_attribute]
183pub fn ffi_union(
184 args: proc_macro::TokenStream,
185 body: proc_macro::TokenStream,
186) -> proc_macro::TokenStream {
187 let args = syn::parse_macro_input!(args as ffi_union::Args);
188 let mut item = syn::parse_macro_input!(body as syn::ItemUnion);
189
190 let item_ident = item.ident.clone();
191
192 match ffi_union::expand(&mut item, args) {
193 Ok(ffi_union::ExpansionExtras { extras, accessors }) => TokenStream::from(quote!(
194 #(#extras)*
195 #item
196 impl #item_ident {
197 #(#accessors)*
198 }
199 )),
200 Err(e) => e.to_compile_error().into(),
201 }
202}