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 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
//! [![github]](https://github.com/dtolnay/watt) [![crates-io]](https://crates.io/crates/watt) [![docs-rs]](https://docs.rs/watt) //! //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K //! //! <br> //! //! # Watt //! //! Watt is a runtime for executing Rust procedural macros compiled as //! WebAssembly. //! //! <br> //! //! # Rationale //! //! - **Faster compilation.** By compiling macros ahead-of-time to Wasm, we //! save all downstream users of the macro from having to compile the macro //! logic or its dependencies themselves. //! //! Instead, what they compile is a small self-contained Wasm runtime (~3 //! seconds, shared by all macros) and a tiny proc macro shim for each macro //! crate to hand off Wasm bytecode into the Watt runtime (~0.3 seconds per //! proc-macro crate you depend on). This is much less than the 20+ seconds it //! can take to compile complex procedural macros and their dependencies. //! //! - **Isolation.** The Watt runtime is 100% safe code with zero //! dependencies. While running in this environment, a macro's *only possible //! interaction with the world* is limited to consuming tokens and producing //! tokens. This is true regardless of how much unsafe code the macro itself //! might contain! Modulo bugs in the Rust compiler or standard library, it is //! impossible for a macro to do anything other than shuffle tokens around. //! //! - **Determinism.** From a build system point of view, a macro backed by //! Wasm has the advantage that it can be treated as a purely deterministic //! function from input to output. There is no possibility of implicit //! dependencies, such as via the filesystem, which aren't visible to or taken //! into account by the build system. //! //! <br> //! //! # 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: //! //! ``` //! # const IGNORE: &str = stringify! { //! 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: //! //! ``` //! # const IGNORE: &str = stringify! { //! 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: //! //! ```toml //! [lib] //! proc-macro = true //! ``` //! //! change it instead to say: //! //! ```toml //! [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: //! //! ```console //! $ 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: //! //! ```toml //! [lib] //! proc-macro = true //! //! [dependencies] //! watt = "0.4" //! ``` //! //! 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: //! //! ``` //! # const IGNORE: &str = stringify! { //! 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! //! //! <br> //! //! # Remaining work //! //! - **Performance.** Watt compiles pretty fast, but so far I have not put //! any effort toward optimizing the runtime. That means macro expansion can //! potentially take longer than with a natively compiled proc macro. //! //! Note that the performance overhead of the Wasm environment is partially //! offset by the fact that our proc macros are compiled to Wasm in release //! mode, so downstream `cargo build` will be running a release-mode macro //! when it would have been running debug-mode for a traditional proc macro. //! //! A neat approach would be to provide some kind of `cargo install //! watt-runtime` which installs an optimized Wasm runtime locally, which the //! Watt crate can detect and hand off code to if available. That way we avoid //! running things in a debug-mode runtime altogether. The experimental //! beginnings of this can be found under the [jit/] directory. //! //! - **Tooling.** The getting started section shows there are a lot of //! steps to building a macro for Watt, and a pretty hacky patching in of //! proc-macro2. Ideally this would all be more straightforward, including //! easy tooling for doing reproducible builds of the Wasm artifact for //! confirming that it was indeed compiled from the publicly available //! sources. //! //! - **RFCs.** The advantages of fast compile time, isolation, and //! determinism may make it worthwhile to build first-class support for Wasm //! proc macros into rustc and Cargo. The toolchain could ship its own high //! performance Wasm runtime, which is an even better outcome than Watt //! because that runtime can be heavily optimized and consumers of macros //! don't need to compile it. //! //! [jit/]: https://github.com/dtolnay/watt/tree/master/jit //! //! <br> //! //! # 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. //! //! [Rust-WASM]: https://github.com/yblein/rust-wasm #![deny(rust_2018_idioms)] extern crate proc_macro; #[cfg(not(jit))] #[path = "interpret.rs"] mod exec; #[cfg(not(jit))] #[path = "../runtime/src/lib.rs"] mod runtime; #[cfg(jit)] #[path = "jit.rs"] mod exec; #[cfg(jit)] #[path = "../jit/src/lib.rs"] mod runtime; mod data; mod decode; mod encode; mod import; mod sym; use proc_macro::TokenStream; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// An instantiation of a WebAssembly module used to invoke procedural macro /// methods on the wasm module. /// /// /// # Examples /// /// ``` /// # const IGNORE: &str = stringify! { /// static MACRO: WasmMacro = WasmMacro::new(WASM); /// static WASM: &[u8] = include_bytes!("my_macros.wasm"); /// # }; /// ``` pub struct WasmMacro<'bytecode> { wasm: &'bytecode [u8], id: AtomicUsize, } impl<'bytecode> WasmMacro<'bytecode> { /// Creates a new `WasmMacro` from the statically included blob of wasm bytes. /// /// # Examples /// /// ``` /// # const IGNORE: &str = stringify! { /// static MACRO: WasmMacro = WasmMacro::new(WASM); /// static WASM: &[u8] = include_bytes!("my_macros.wasm"); /// # }; /// ``` pub const fn new(wasm: &'bytecode [u8]) -> Self { WasmMacro { wasm, id: AtomicUsize::new(0), } } /// A #\[proc_macro\] implemented in wasm! /// /// # Canonical macro implementation: /// /// ``` /// # const IGNORE: &str = stringify! { /// use proc_macro2::TokenStream; /// /// #[no_mangle] /// pub extern "C" fn the_macro(input: TokenStream) -> TokenStream { /// ... /// } /// # }; /// ``` /// /// # Canonical entry point: /// /// ``` /// # const IGNORE: &str = stringify! { /// 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) /// } /// # }; /// ``` pub fn proc_macro(&self, fun: &str, input: TokenStream) -> TokenStream { exec::proc_macro(fun, vec![input], self) } /// A #\[proc_macro_derive\] implemented in wasm! /// /// # Canonical macro implementation: /// /// ``` /// # const IGNORE: &str = stringify! { /// use proc_macro2::TokenStream; /// /// #[no_mangle] /// pub extern "C" fn the_macro(input: TokenStream) -> TokenStream { /// ... /// } /// # }; /// ``` /// /// # Canonical entry point: /// /// ``` /// # const IGNORE: &str = stringify! { /// use proc_macro::TokenStream; /// use watt::WasmMacro; /// /// static MACRO: WasmMacro = WasmMacro::new(WASM); /// static WASM: &[u8] = include_bytes!("my_macros.wasm"); /// /// #[proc_macro_derive(MyDerive)] /// pub fn the_macro(input: TokenStream) -> TokenStream { /// MACRO.proc_macro_derive("the_macro", input) /// } /// # }; /// ``` pub fn proc_macro_derive(&self, fun: &str, input: TokenStream) -> TokenStream { exec::proc_macro(fun, vec![input], self) } /// A #\[proc_macro_attribute\] implemented in wasm! /// /// # Canonical macro implementation: /// /// ``` /// # const IGNORE: &str = stringify! { /// use proc_macro2::TokenStream; /// /// #[no_mangle] /// pub extern "C" fn the_macro(args: TokenStream, input: TokenStream) -> TokenStream { /// ... /// } /// # }; /// ``` /// /// # Canonical entry point: /// /// ``` /// # const IGNORE: &str = stringify! { /// use proc_macro::TokenStream; /// use watt::WasmMacro; /// /// static MACRO: WasmMacro = WasmMacro::new(WASM); /// static WASM: &[u8] = include_bytes!("my_macros.wasm"); /// /// #[proc_macro_attribute] /// pub fn the_macro(args: TokenStream, input: TokenStream) -> TokenStream { /// MACRO.proc_macro_attribute("the_macro", args, input) /// } /// # }; /// ``` pub fn proc_macro_attribute( &self, fun: &str, args: TokenStream, input: TokenStream, ) -> TokenStream { exec::proc_macro(fun, vec![args, input], self) } fn id(&self) -> usize { static NEXT_ID: AtomicUsize = AtomicUsize::new(1); match self.id.load(SeqCst) { 0 => {} n => return n, } let id = NEXT_ID.fetch_add(1, SeqCst); self.id .compare_exchange(0, id, SeqCst, SeqCst) .unwrap_or_else(|id| id) } }