#proc-macro #run-time #executing #compiler #watt #rustc

watt-non-static-runtime

作为WebAssembly编译的Rust进程宏的运行时

1 个不稳定版本

0.4.0 2020年7月17日

#301进程宏

MIT/Apache

225KB
5.5K SLoC

Watt

github crates.io docs.rs build status

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 许可证定义的,您有意提交以包含在本软件包中的任何贡献,都将按照上述方式双重许可,不附加任何额外条款或条件。

无运行时依赖