dtolnay / watt

Runtime for executing procedural macros as WebAssembly
Apache License 2.0
1.29k stars 29 forks source link

Watt

github crates.io docs.rs

Watt is a runtime for executing Rust procedural macros compiled as WebAssembly.

[dependencies]
watt = "0.5"

Compiler support: requires rustc 1.42+


Rationale


Getting started

Start by implementing and testing your proc macro as you normally would, using whatever dependencies you want (syn, quote, etc). You will end up with something that looks like:

use proc_macro::TokenStream;

#[proc_macro]
pub fn the_macro(input: TokenStream) -> TokenStream {
    /* ... */
}

#[proc_macro_derive] and #[proc_macro_attribute] are supported as well; everything is analogous to what will be shown here for #[proc_macro].

When your macro is ready, there are just a few changes we need to make to the signature and the Cargo.toml. In your lib.rs, change each of your macro entry points to a no_mangle extern "C" function, and change the TokenStream in the signature from proc_macro to proc_macro2.

It will look like:

use proc_macro2::TokenStream;

#[no_mangle]
pub extern "C" fn the_macro(input: TokenStream) -> TokenStream {
    /* same as before */
}

Now in your macro's Cargo.toml which used to contain this:

# my_macros/Cargo.toml

[lib]
proc-macro = true

change it instead to say:

[lib]
crate-type = ["cdylib"]

[patch.crates-io]
proc-macro2 = { git = "https://github.com/dtolnay/watt" }

This crate will be the binary that we compile to Wasm. Compile it by running:

$ cargo build --release --target wasm32-unknown-unknown

Next we need to make a small proc-macro shim crate to hand off the compiled Wasm bytes into the Watt runtime. It's fine to give this the same crate name as the previous crate, since the other one won't be getting published to crates.io. In a new Cargo.toml, put:

[lib]
proc-macro = true

[dependencies]
watt = "0.5"

And in its src/lib.rs, define real proc macros corresponding to each of the ones previously defined as no_mangle extern "C" functions in the other crate:

use proc_macro::TokenStream;
use watt::WasmMacro;

static MACRO: WasmMacro = WasmMacro::new(WASM);
static WASM: &[u8] = include_bytes!("my_macros.wasm");

#[proc_macro]
pub fn the_macro(input: TokenStream) -> TokenStream {
    MACRO.proc_macro("the_macro", input)
}

Finally, copy the compiled Wasm binary from target/wasm32-unknown-unknown/release/my_macros.wasm under your implementation crate, to the src directory of your shim crate, and it's ready to publish!


Remaining work


This can't be real

To assist in convincing you that this is real, here is serde_derive compiled to Wasm. It was compiled from the commit serde-rs/serde@1afae183. Feel free to try it out as:

// [dependencies]
// serde = "1.0"
// serde_json = "1.0"
// wa-serde-derive = "0.1"

use wa_serde_derive::Serialize;

#[derive(Serialize)]
struct Watt {
    msg: &'static str,
}

fn main() {
    let w = Watt { msg: "hello from wasm!" };
    println!("{}", serde_json::to_string(&w).unwrap());
}


Acknowledgements

The current underlying Wasm runtime is a fork of the Rust-WASM project by Yoann Blein and Hugo Guiroux, a simple and spec-compliant WebAssembly interpreter.


License

Everything outside of the runtime directory is licensed under either of <a href="LICENSE-APACHE">Apache License, Version 2.0 or <a href="LICENSE-MIT">MIT license at your option. The runtime directory is licensed under the ISC license.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.