1 个不稳定版本
0.4.0 | 2020年7月17日 |
---|
#301 在 进程宏
225KB
5.5K SLoC
Watt
Watt是执行作为WebAssembly编译的Rust进程宏的运行时。
[dependencies]
watt = "0.4"
编译器支持:需要rustc 1.35+
理由
-
更快的编译。 通过将宏提前编译成Wasm,我们可以让所有宏的下游用户免于自己编译宏逻辑或其依赖项。
相反,他们需要编译的是一个小的自包含Wasm运行时(约3秒,由所有宏共享)以及每个宏crate的微小的进程宏包装器,将Wasm字节码传递给Watt运行时(每个进程宏crate约0.3秒)。这比编译复杂的进程宏及其依赖项所需的时间(20+秒)要少得多。
-
隔离。 Watt运行时是100%安全的代码,无依赖。在此环境中运行时,宏与世界唯一的可能交互是消费和生成标记。即使宏本身可能包含大量不安全代码,也是如此!在Rust编译器或标准库的故障之外,宏不可能做任何其他事情,只能重新排列标记。
-
确定性。 从构建系统的角度来看,基于Wasm的宏的优势在于它可以被视为一个从输入到输出的纯确定性函数。不存在隐式依赖,例如通过文件系统,这是构建系统无法看到或考虑的。
入门
首先,像往常一样实现和测试您的进程宏,使用您想要的任何依赖项(syn、quote等)。您最终会得到类似以下内容:
use proc_macro::TokenStream;
#[proc_macro]
pub fn the_macro(input: TokenStream) -> TokenStream {
/* ... */
}
#[proc_macro_derive]
和 #[proc_macro_attribute]
也受支持;一切都与下面展示的 #[proc_macro]
类似。
当您的宏准备就绪时,我们只需要对签名和Cargo.toml进行一些修改。在您的lib.rs中,将每个宏入口点修改为no_mangle extern "C"函数,并将签名中的TokenStream从proc_macro更改为proc_macro2。
它看起来像这样
use proc_macro2::TokenStream;
#[no_mangle]
pub extern "C" fn the_macro(input: TokenStream) -> TokenStream {
/* same as before */
}
现在在您的宏的Cargo.toml中,之前包含的是这个
# my_macros/Cargo.toml
[lib]
proc-macro = true
改为以下内容
[lib]
crate-type = ["cdylib"]
[patch.crates-io]
proc-macro2 = { git = "https://github.com/dtolnay/watt" }
这个crate将被编译成Wasm的二进制文件。通过运行以下命令进行编译:
$ cargo build --release --target wasm32-unknown-unknown
接下来,我们需要创建一个小的proc-macro shim crate,以便将编译好的Wasm字节传递给Watt运行时。由于上一个crate不会发布到crates.io,因此可以将这个crate与上一个crate使用相同的crate名称。在一个新的Cargo.toml中,放入以下内容
[lib]
proc-macro = true
[dependencies]
watt = "0.4"
在其src/lib.rs中,定义与之前在另一个crate中定义为no_mangle extern "C"函数的每个宏对应的真实proc宏
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)
}
最后,将target/wasm32-unknown-unknown/release/my_macros.wasm下的编译好的Wasm二进制文件复制到实现crate的src目录下shim crate中,即可发布!
剩余工作
-
性能。 Watt编译速度很快,但到目前为止,我还没有对运行时进行优化。这意味着宏展开可能比使用本地编译的proc宏耗时更长。
请注意,由于我们的proc宏以发布模式编译到Wasm,Wasm环境的性能开销在一定程度上得到了抵消。这意味着当它原本应该运行传统的proc宏的调试模式时,
cargo build
将运行发布模式的宏。一种很好的方法是提供一种
cargo install watt-runtime
,它会在本地安装一个优化的Wasm运行时,Watt crate可以检测到并传递代码,如果可用。这样我们就完全避免了在调试模式运行时运行代码。这个实验性的开始可以在jit/目录下找到。 -
工具。 Watt的入门部分显示了构建宏有很多步骤,并且proc-macro2的集成相当原始。理想情况下,这一切都应该更简单,包括执行可重复构建Wasm实体的简单工具,以确认它确实是从公开可用的源代码编译的。
-
RFCs。 快速编译时间、隔离和确定性可能使将Wasm proc宏作为一等支持构建到rustc和Cargo中变得值得。工具链可以提供自己的高性能Wasm运行时,这比Watt更好,因为该运行时可以进行高度优化,并且宏的消费者不需要编译它。
这不可能真的
为了帮助您相信这是真实的,这里有serde_derive编译成Wasm。它是从serde-rs/serde@37bf6984提交编译的。您可以免费尝试它作为
// [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());
}
致谢
当前的底层Wasm运行时是Yoann Blein和Hugo Guiroux的Rust-WASM项目的分支,这是一个简单且符合规范的WebAssembly解释器。
许可证
除了 `runtime` 目录之外,所有内容均根据您的选择,分别受以下许可证的约束:[Apache License, Version 2.0](https://github.com/dtolnay/watt/blob/HEAD/LICENSE-APACHE) 或 [MIT 许可证](https://github.com/dtolnay/watt/blob/HEAD/LICENSE-MIT)。`runtime` 目录受 [ISC 许可证](https://github.com/dtolnay/watt/blob/HEAD/runtime/LICENSE_ISC) 的约束。除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交以包含在本软件包中的任何贡献,都将按照上述方式双重许可,不附加任何额外条款或条件。