#proc-macro #run-time #watt #how #compiler #procedural

yanked watt-abi

Watt 如何调用 WebAssembly 程序的细节

0.0.0 2019年10月31日

#168 in #how

每月32次下载

MIT/Apache

2KB

Watt

github crates.io docs.rs

Watt 是一个用于执行作为 WebAssembly 编译的 Rust 程序宏的运行时。

[dependencies]
watt = "0.5"

编译器支持:需要 rustc 1.42+


原理

  • 更快的编译。 通过将宏预编译到 Wasm,我们可以节省所有下游用户编译宏逻辑或其依赖项的时间。

    相反,他们需要编译的是一个小的自包含 Wasm 运行时(约3秒,所有宏共享)以及每个宏crate的微小的 proc宏模拟,以便将Wasm字节码传递给Watt运行时(每个proc-macro crate约0.3秒)。这比编译复杂的程序宏及其依赖项所需的时间少得多(20+秒)。

  • 隔离。 Watt 运行时是100%安全代码,无依赖。在此环境中运行时,宏的唯一可能的与世界交互的方式仅限于消耗令牌和产生令牌。这适用于宏可能包含多少不安全代码的情况!除了Rust编译器或标准库中的错误之外,宏不可能做任何事情,而只是在令牌之间进行排序。

  • 确定性。 从构建系统的角度来看,由 Wasm 支持的宏的优点是它可以被视为一个从输入到输出的纯粹确定性函数。不存在隐式依赖的可能性,例如通过文件系统,这是构建系统看不到或考虑的。


入门指南

首先像平常一样实现和测试您的 proc宏,使用您想要的任何依赖项(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 中,将每个宏入口点更改为无杂音的 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 适配器 crate,以便将编译后的 Wasm 字节传递给 Watt 运行时。由于其他 crate 不会发布到 crates.io,因此可以将其命名为与之前 crate 相同的名称。在一个新的 Cargo.toml 文件中,输入以下内容:

[lib]
proc-macro = true

[dependencies]
watt = "0.5"

然后在它的 src/lib.rs 中,定义与之前定义的每个 no_mangle extern "C" 函数相对应的实际 proc macros。

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 目录下,然后就可以发布它了!


剩余工作

  • 性能。 Watt 编译速度相当快,但到目前为止,我还没有对运行时进行优化。这意味着宏展开可能比使用本地编译的 proc macro 花费更长的时间。

    请注意,Wasm 环境的性能开销部分被我们以发布模式编译 proc macros 的事实所抵消,因此下游的 cargo build 将运行发布模式的宏,而如果是传统的 proc macro,则会运行调试模式。

    一个不错的方法是提供一个 cargo install watt-runtime 命令,该命令将安装一个本地的优化 Wasm 运行时,Watt crate 可以检测到它并将代码传递给它。这样我们就可以完全避免在调试模式运行时运行任何内容。这个实验性的开始可以在这个目录下找到:jit/

  • 工具。 初始化部分显示构建 Watt 宏有很多步骤,并且 proc-macro2 的集成相当棘手。理想情况下,这应该更直接,包括易于工具进行可重复构建的 Wasm 艺术品的工具,以确认它确实是从公开可用的源编译的。

  • RFCs。 快速编译时间、隔离和确定性等优势可能使得在 rustc 和 Cargo 中构建 Wasm proc macros 的首选支持变得有意义。工具链可以自带自己的高性能 Wasm 运行时,这比 Watt 更好,因为该运行时可以高度优化,并且宏的使用者不需要编译它。


这不是真的

为了帮助您相信这是真的,这里是将 serde_derive 编译成 Wasm 的示例。它是从 serde-rs/serde@1afae183 提交编译的。您可以自由尝试它。

// [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.0MIT 许可证 的许可。`runtime` 目录受 ISC 许可证 的许可。
除非您明确表示,否则根据 Apache-2.0 许可证定义的,您有意提交给此 crate 的任何贡献,都应按上述方式双许可,不得附加任何其他条款或条件。

无运行时依赖