xref: /wasmtime-44.0.1/crates/wiggle/macro/src/lib.rs (revision b2498a29)
1 use proc_macro::TokenStream;
2 use quote::quote;
3 use syn::parse_macro_input;
4 
5 /// This macro expands to a set of `pub` Rust modules:
6 ///
7 /// * The `types` module contains definitions for each `typename` declared in
8 ///   the witx document. Type names are translated to the Rust-idiomatic
9 ///   CamelCase.
10 ///
11 /// * For each `module` defined in the witx document, a Rust module is defined
12 ///   containing definitions for that module. Module names are translated to the
13 ///   Rust-idiomatic snake\_case.
14 ///
15 ///     * For each `@interface func` defined in a witx module, an abi-level
16 ///       function is generated which takes ABI-level arguments, along with
17 ///       a ref that impls the module trait, and a `GuestMemory` implementation.
18 ///       Users typically won't use these abi-level functions: Either the
19 ///       `wasmtime_integration` macro or the `lucet-wiggle` crates adapt these
20 ///       to work with a particular WebAssembly engine.
21 ///
22 ///     * A public "module trait" is defined (called the module name, in
23 ///       SnakeCase) which has a `&self` method for each function in the
24 ///       module. These methods takes idiomatic Rust types for each argument
25 ///       and return `Result<($return_types),$error_type>`
26 ///
27 ///     * When the `wiggle` crate is built with the `wasmtime_integration`
28 ///       feature, each module contains an `add_to_linker` function to add it to
29 ///       a `wasmtime::Linker`.
30 ///
31 /// Arguments are provided using Rust struct value syntax.
32 ///
33 /// * `witx` takes a list of string literal paths. Paths are relative to the
34 ///   CARGO_MANIFEST_DIR of the crate where the macro is invoked. Alternatively,
35 ///   `witx_literal` takes a string containing a complete witx document.
36 /// * Optional: `errors` takes a mapping of witx identifiers to types, e.g
37 ///   `{ errno => YourErrnoType }`. This allows you to use the `UserErrorConversion`
38 ///   trait to map these rich errors into the flat witx type, or to terminate
39 ///   WebAssembly execution by trapping.
40 ///     * Instead of requiring the user to define an error type, wiggle can
41 ///       generate an error type for the user which has conversions to/from
42 ///       the base type, and permits trapping, using the syntax
43 ///       `errno => trappable AnErrorType`.
44 /// * Optional: `async` takes a set of witx modules and functions which are
45 ///   made Rust `async` functions in the module trait.
46 ///
47 /// ## Example
48 ///
49 /// ```
50 /// use wiggle::GuestPtr;
51 /// wiggle::from_witx!({
52 ///     witx_literal: "
53 ///         (typename $errno
54 ///           (enum (@witx tag u32)
55 ///             $ok
56 ///             $invalid_arg
57 ///             $io
58 ///             $overflow))
59 ///          (typename $alias_to_float f32)
60 ///          (module $example
61 ///            (@interface func (export \"int_float_args\")
62 ///              (param $an_int u32)
63 ///              (param $some_floats (list f32))
64 ///              (result $r (expected (error $errno))))
65 ///            (@interface func (export \"double_int_return_float\")
66 ///              (param $an_int u32)
67 ///              (result $r (expected $alias_to_float (error $errno)))))
68 ///     ",
69 ///     errors: { errno => YourRichError },
70 ///     async: { example::double_int_return_float },
71 /// });
72 ///
73 /// /// Witx generates a set of traits, which the user must impl on a
74 /// /// type they define. We call this the ctx type. It stores any context
75 /// /// these functions need to execute.
76 /// pub struct YourCtxType {}
77 ///
78 /// /// Witx provides a hook to translate "rich" (arbitrary Rust type) errors
79 /// /// into the flat error enums used at the WebAssembly interface. You will
80 /// /// need to impl the `types::UserErrorConversion` trait to provide a translation
81 /// /// from this rich type.
82 /// #[derive(Debug)]
83 /// pub enum YourRichError {
84 ///     InvalidArg(String),
85 ///     Io(std::io::Error),
86 ///     Overflow,
87 ///     Trap(String),
88 /// }
89 ///
90 /// /// The above witx text contains one module called `$example`. So, we must
91 /// /// implement this one method trait for our ctx type.
92 /// impl example::Example for YourCtxType {
93 ///     /// The arrays module has two methods, shown here.
94 ///     /// Note that the `GuestPtr` type comes from `wiggle`,
95 ///     /// whereas the witx-defined types like `Excuse` and `Errno` come
96 ///     /// from the `pub mod types` emitted by the `wiggle::from_witx!`
97 ///     /// invocation above.
98 ///     fn int_float_args(&mut self, _int: u32, _floats: &GuestPtr<[f32]>)
99 ///         -> Result<(), YourRichError> {
100 ///         unimplemented!()
101 ///     }
102 ///     async fn double_int_return_float(&mut self, int: u32)
103 ///         -> Result<f32, YourRichError> {
104 ///         Ok(int.checked_mul(2).ok_or(YourRichError::Overflow)? as f32)
105 ///     }
106 /// }
107 ///
108 /// /// For all types used in the `error` an `expected` in the witx document,
109 /// /// you must implement `GuestErrorType` which tells wiggle-generated
110 /// /// code what value to return when the method returns Ok(...).
111 /// impl wiggle::GuestErrorType for types::Errno {
112 ///     fn success() -> Self {
113 ///         unimplemented!()
114 ///     }
115 /// }
116 ///
117 /// /// If you specify a `error` mapping to the macro, you must implement the
118 /// /// `types::UserErrorConversion` for your ctx type as well. This trait gives
119 /// /// you an opportunity to store or log your rich error type, while returning
120 /// /// a basic witx enum to the WebAssembly caller. It also gives you the ability
121 /// /// to terminate WebAssembly execution by trapping.
122 ///
123 /// impl types::UserErrorConversion for YourCtxType {
124 ///     fn errno_from_your_rich_error(&mut self, e: YourRichError)
125 ///         -> Result<types::Errno, wiggle::wasmtime_crate::Error>
126 ///     {
127 ///         println!("Rich error: {:?}", e);
128 ///         match e {
129 ///             YourRichError::InvalidArg{..} => Ok(types::Errno::InvalidArg),
130 ///             YourRichError::Io{..} => Ok(types::Errno::Io),
131 ///             YourRichError::Overflow => Ok(types::Errno::Overflow),
132 ///             YourRichError::Trap(s) => Err(wiggle::wasmtime_crate::Error::msg(s)),
133 ///         }
134 ///     }
135 /// }
136 ///
137 /// # fn main() { println!("this fools doc tests into compiling the above outside a function body")
138 /// # }
139 /// ```
140 #[proc_macro]
from_witx(args: TokenStream) -> TokenStream141 pub fn from_witx(args: TokenStream) -> TokenStream {
142     let config = parse_macro_input!(args as wiggle_generate::Config);
143 
144     let doc = config.load_document();
145 
146     let settings = wiggle_generate::CodegenSettings::new(
147         &config.errors,
148         &config.async_,
149         &doc,
150         config.wasmtime,
151         &config.tracing,
152         config.mutable,
153     )
154     .expect("validating codegen settings");
155 
156     let code = wiggle_generate::generate(&doc, &settings);
157     let metadata = if cfg!(feature = "wiggle_metadata") {
158         wiggle_generate::generate_metadata(&doc)
159     } else {
160         quote!()
161     };
162 
163     let mut ret = quote! { #code #metadata };
164 
165     if std::env::var("WIGGLE_DEBUG_BINDGEN").is_ok() {
166         use std::path::Path;
167         use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
168         static INVOCATION: AtomicUsize = AtomicUsize::new(0);
169         let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
170         let n = INVOCATION.fetch_add(1, Relaxed);
171         let path = root.join(format!("wiggle{n}.rs"));
172 
173         std::fs::write(&path, ret.to_string()).unwrap();
174 
175         // optimistically format the code but don't require success
176         drop(
177             std::process::Command::new("rustfmt")
178                 .arg(&path)
179                 .arg("--edition=2021")
180                 .output(),
181         );
182 
183         let path = path.to_str().unwrap();
184         ret = quote!(include!(#path););
185     }
186     TokenStream::from(ret)
187 }
188 
189 /// Define the structs required to integrate a Wiggle implementation with Wasmtime.
190 ///
191 /// ## Arguments
192 ///
193 /// Arguments are provided using struct syntax e.g. `{ arg_name: value }`.
194 ///
195 /// * `target`: The path of the module where the Wiggle implementation is defined.
196 #[proc_macro]
wasmtime_integration(args: TokenStream) -> TokenStream197 pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
198     let config = parse_macro_input!(args as wiggle_generate::WasmtimeConfig);
199     let doc = config.c.load_document();
200 
201     let settings = wiggle_generate::CodegenSettings::new(
202         &config.c.errors,
203         &config.c.async_,
204         &doc,
205         true,
206         &config.c.tracing,
207         config.c.mutable,
208     )
209     .expect("validating codegen settings");
210 
211     let modules = doc.modules().map(|module| {
212         wiggle_generate::wasmtime::link_module(&module, Some(&config.target), &settings)
213     });
214     quote!( #(#modules)* ).into()
215 }
216