-
-
Notifications
You must be signed in to change notification settings - Fork 29
/
lib.rs
315 lines (308 loc) · 9.35 KB
/
lib.rs
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
//! # Watt
//!
//! Watt<sup>*</sup> is a runtime for executing Rust procedural macros compiled
//! as WebAssembly.
//!
//! <sub><b>*</b><i>optional but entertaining pronunciation:
//! <u>wahteetee</u></i></sub>
//!
//! <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! {
//! extern crate proc_macro;
//!
//! use proc_macro::TokenStream;
//!
//! #[proc_macro]
//! pub fn my_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. Finally, add a call to
//! `proc_macro2::set_wasm_panic_hook()` at the top of your macro to ensure
//! panics get printed to the console; this is optional but helpful!
//!
//! It will look like:
//!
//! ```
//! # const IGNORE: &str = stringify! {
//! use proc_macro2::TokenStream;
//!
//! #[no_mangle]
//! pub extern "C" fn my_macro(input: TokenStream) -> TokenStream {
//! proc_macro2::set_wasm_panic_hook();
//!
//! /* 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", "rlib"]
//!
//! [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. In a new Cargo.toml, put:
//!
//! ```toml
//! [lib]
//! proc-macro = true
//!
//! [dependencies]
//! watt = "0.1"
//! ```
//!
//! And in its src/lib.rs put:
//!
//! ```
//! # const IGNORE: &str = stringify! {
//! extern crate proc_macro;
//!
//! use proc_macro::TokenStream;
//!
//! static WASM: &[u8] = include_bytes!("my_macro.wasm");
//!
//! #[proc_macro]
//! pub fn (input: TokenStream) -> TokenStream {
//! watt::proc_macro("my_macro", input, WASM)
//! }
//! # };
//! ```
//!
//! Finally, copy the compiled Wasm binary from
//! target/wasm32-unknown-unknown/release/my_macro.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.
//!
//! There is a great deal of low-hanging fruit in the runtime; as I said, it
//! hasn't been optimized. In particular I am doing something silly with how
//! strings are handed off one character at a time instead of in bulk in
//! memory. I would love contributions from people who have a better idea of
//! how this stuff is intended to work in Wasm in general.
//!
//! As another idea, maybe there could be 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.
//!
//! - **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.
//!
//! <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
extern crate proc_macro;
#[path = "../runtime/src/lib.rs"]
mod watt;
mod data;
mod debug;
mod exec;
mod import;
mod sym;
use crate::watt::*;
use proc_macro::TokenStream;
/// A #\[proc_macro\] implemented in wasm!
///
/// # Canonical macro implementation:
///
/// ```
/// # const IGNORE: &str = stringify! {
/// use proc_macro2::TokenStream;
///
/// #[no_mangle]
/// pub extern "C" fn my_macro(input: TokenStream) -> TokenStream {
/// proc_macro2::set_wasm_panic_hook();
///
/// ...
/// }
/// # };
/// ```
///
/// # Canonical entry point:
///
/// ```
/// # const IGNORE: &str = stringify! {
/// extern crate proc_macro;
///
/// use proc_macro::TokenStream;
///
/// static WASM: &[u8] = include_bytes!("my_macro.wasm");
///
/// #[proc_macro]
/// pub fn my_macro(input: TokenStream) -> TokenStream {
/// watt::proc_macro("my_macro", input, WASM)
/// }
/// # };
/// ```
pub fn proc_macro(fun: &str, input: TokenStream, wasm: &[u8]) -> TokenStream {
exec::proc_macro(fun, vec![input], wasm)
}
/// A #\[proc_macro_derive\] implemented in wasm!
///
/// # Canonical macro implementation:
///
/// ```
/// # const IGNORE: &str = stringify! {
/// use proc_macro2::TokenStream;
///
/// #[no_mangle]
/// pub extern "C" fn my_macro(input: TokenStream) -> TokenStream {
/// proc_macro2::set_wasm_panic_hook();
///
/// ...
/// }
/// # };
/// ```
///
/// # Canonical entry point:
///
/// ```
/// # const IGNORE: &str = stringify! {
/// extern crate proc_macro;
///
/// use proc_macro::TokenStream;
///
/// static WASM: &[u8] = include_bytes!("my_macro.wasm");
///
/// #[proc_macro_derive(MyDerive)]
/// pub fn my_macro(input: TokenStream) -> TokenStream {
/// watt::proc_macro_derive("my_macro", input, WASM)
/// }
/// # };
/// ```
pub fn proc_macro_derive(fun: &str, input: TokenStream, wasm: &[u8]) -> TokenStream {
exec::proc_macro(fun, vec![input], wasm)
}
/// A #\[proc_macro_attribute\] implemented in wasm!
///
/// # Canonical macro implementation:
///
/// ```
/// # const IGNORE: &str = stringify! {
/// use proc_macro2::TokenStream;
///
/// #[no_mangle]
/// pub extern "C" fn my_macro(args: TokenStream, input: TokenStream) -> TokenStream {
/// proc_macro2::set_wasm_panic_hook();
///
/// ...
/// }
/// # };
/// ```
///
/// # Canonical entry point:
///
/// ```
/// # const IGNORE: &str = stringify! {
/// extern crate proc_macro;
///
/// use proc_macro::TokenStream;
///
/// static WASM: &[u8] = include_bytes!("my_macro.wasm");
///
/// #[proc_macro_attribute]
/// pub fn my_macro(args: TokenStream, input: TokenStream) -> TokenStream {
/// watt::proc_macro_attribute("my_macro", args, input, WASM)
/// }
/// # };
/// ```
pub fn proc_macro_attribute(
fun: &str,
args: TokenStream,
input: TokenStream,
wasm: &[u8],
) -> TokenStream {
exec::proc_macro(fun, vec![args, input], wasm)
}