wiggle_macro/lib.rs
1use proc_macro::TokenStream;
2use quote::quote;
3use 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]
141pub 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]
197pub 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}