- 语言运行时
- JIT编译
- 编译器
【免费下载链接】wasmtime
A lightweight WebAssembly runtime that is fast, secure, and standards-compliant
Wiggle 是 Bytecode Alliance Wasmtime 仓库中的一个代码生成器,负责为witx接口定义生成宿主(host)侧的 Rust 绑定代码,并以 Rust 过程宏的形式对外提供。本文以 crates/wiggle/README.md 为核心骨架,结合仓库内宏实现、代码生成器与运行时源码,完整讲解 Wiggle 的工作原理、from_witx!与wasmtime_integration!两个宏的全部配置项、生成的 trait 与Linker集成方式,以及GuestMemory/GuestPtr等运行时抽象的实现细节。读完本文,你将能够独立为自定义的 witx 接口编写宿主实现,并把它接入 Wasmtime 的Linker。
Wiggle 是什么:为 witx 接口生成宿主端代码
在 Wasmtime 的生态中,guest(WASI 等)与 host 之间的接口通常用witx(WebAssembly Interface Types eXtended)格式描述。witx文档声明了类型(typename)、常量(constant)和带导出名的接口函数(@interface func),但宿主端要把这些 ABI 级别的函数翻译成类型安全、符合 Rust 惯用风格的代码,需要大量重复的样板工作。
Wiggle 解决的就是这个问题。它读取一份witx文档,生成:
- 一个
types模块,包含文档中每个typename对应的 Rust 类型定义(名称转换为 Rust 惯用的 CamelCase),以及文档中每个常量的pub const定义; - 每个
module对应的同名 Rust 模块(模块名转换为 snake_case),模块内含 ABI 级函数、模块 trait 以及可选的add_to_linker集成函数。
关键设计点是:Wiggle 并不绑定任何特定的 WebAssembly 运行时。它在 Wasmtime 与 Lucet 中都可以使用。本仓库中的 crates/wiggle 负责与 Wasmtime 集成(另一侧的lucet-wiggle位于 Lucet 仓库,不在本仓库内)。该 crate 自身划分为三个子 crate,各自职责清晰:
- crates/wiggle/macro:暴露
from_witx!与wasmtime_integration!两个过程宏的入口; - crates/wiggle/generate:实际的代码生成库,负责解析宏参数、生成 token 流;
- crates/wiggle/src:运行时支撑代码(
GuestMemory、GuestPtr、GuestError等)。
在 Wasmtime 生态中,wasi-common就是基于 Wiggle 实现的,wasmtime-wasi(本仓库对应 crates/wasi)再把 WASI 的宿主实现接入 Wasmtime 引擎;crates/wasi-nn 则是一个完全基于 Wiggle 编写的新 WASI 提案实现,是研究 Wiggle 实战用法的最佳参考。
工作方式:过程宏驱动的代码生成管线
两个宏的分工
from_witx!负责"文档 → Rust 代码"的翻译。从 crates/wiggle/macro/src/lib.rs 可以看到它的完整流程:
- 解析宏参数为
wiggle_generate::Config(config.rs); - 调用
config.load_document()加载并解析 witx 文档; - 用
CodegenSettings::new校验错误映射与 async 配置(codegen_settings.rs); - 调用
wiggle_generate::generate(&doc, &settings)生成全部代码(generate/src/lib.rs)。
wasmtime_integration!则负责"生成代码 → Wasmtime 引擎"的桥接:它同样加载 witx 文档,但对每个 module 额外调用wiggle_generate::wasmtime::link_module,生成把宿主实现注册进wasmtime::Linker的函数。
生成的代码结构
根据 generate/src/lib.rs 的实现,from_witx!展开后大致是:
pub mod types { // 每个 typename 对应的 Rust 类型定义 // 每个常量对应的 pub const 定义 // 用户错误转换 trait UserErrorConversion } pub mod <module_name> { use super::types::*; // 每个 @interface func 对应的 ABI 级函数(接收 ABI 级参数, // 外加一个实现模块 trait 的 ctx 引用和一个 GuestMemory 实现) // 公开的模块 trait(方法接收惯用 Rust 类型,返回 // Result<(...), 错误类型>) // 启用 wasmtime 配置时,额外生成 add_to_linker }普通用户通常不会直接调用 ABI 级函数:要么通过wasmtime_integration!宏,要么通过 Lucet 侧的适配 crate 把它们接入具体引擎。用户真正要打交道的是模块 trait——它为 witx 文档中的每个函数声明一个&mut self(或&self)方法,参数和返回值都是惯用 Rust 类型。
快速上手:完整的 from_witx! 使用示例
下面的示例取自 macro/src/lib.rs 的文档测试,它完整演示了从 witx 声明到宿主实现的全部环节。
第一步:声明接口(内联 witx 文档)
use wiggle::GuestPtr; wiggle::from_witx!({ witx_literal: " (typename $errno (enum (@witx tag u32) $ok $invalid_arg $io $overflow)) (typename $alias_to_float f32) (module $example (@interface func (export \"int_float_args\") (param $an_int u32) (param $some_floats (list f32)) (result $r (expected (error $errno)))) (@interface func (export \"double_int_return_float\") (param $an_int u32) (result $r (expected $alias_to_float (error $errno))))) ", errors: { errno => YourRichError }, async: { example::double_int_return_float }, });这里展示了三个要点:
witx_literal直接把完整文档内嵌在宏调用中(等价于witx字段给出文件路径列表,区别在于内联字面量不允许使用(use ...)指令);errors把 witx 中的errno映射到宿主的富错误类型YourRichError;async声明example::double_int_return_float在宿主侧是异步函数。
第二步:定义 ctx 类型并实现模块 trait
/// Witx 生成一组 trait,用户必须在自定义类型上实现它们。 /// 这个类型被称为 ctx 类型,存放这些函数执行所需的上下文。 pub struct YourCtxType {} /// 上面 witx 文本只包含一个名为 $example 的模块, /// 因此需要为 ctx 类型实现这一个方法 trait。 impl example::Example for YourCtxType { /// 注意:GuestPtr 类型来自 wiggle,而 witx 定义的 /// Errno 等类型来自 from_witx! 展开的 pub mod types。 fn int_float_args(&mut self, _int: u32, _floats: &GuestPtr<[f32]>) -> Result<(), YourRichError> { unimplemented!() } async fn double_int_return_float(&mut self, int: u32) -> Result<f32, YourRichError> { Ok(int.checked_mul(2).ok_or(YourRichError::Overflow)? as f32) } }模块 trait 的方法签名体现了两条约定:
- 参数与返回值都使用 Rust 惯用类型;
(list f32)映射为&GuestPtr<[f32]>,避免无谓拷贝; - 凡是出现在
expected错误位置的 witx 类型,都必须为它实现wiggle::GuestErrorType(guest_type.rs),告知生成代码在方法返回Ok(..)时应该给 guest 返回什么成功值:
impl wiggle::GuestErrorType for types::Errno { fn success() -> Self { unimplemented!() } }第三步:实现错误转换
一旦在errors中做了映射,还必须为 ctx 类型实现types::UserErrorConversiontrait。这个 trait 让你有机会记录/记录富错误,同时只把扁平的 witx 枚举返回给 WebAssembly 调用方;它甚至允许你直接终止 WebAssembly 执行(返回Err(...)即 trap):
impl types::UserErrorConversion for YourCtxType { fn errno_from_your_rich_error(&mut self, e: YourRichError) -> Result<types::Errno, wiggle::wasmtime_crate::Error> { println!("Rich error: {:?}", e); match e { YourRichError::InvalidArg{..} => Ok(types::Errno::InvalidArg), YourRichError::Io{..} => Ok(types::Errno::Io), YourRichError::Overflow => Ok(types::Errno::Overflow), YourRichError::Trap(s) => Err(wiggle::wasmtime_crate::Error::msg(s)), } } }转换方法名errno_from_your_rich_error由生成器根据 ABI 错误名与富类型名自动拼接,规则定义在 generate/src/names.rs 的user_error_conversion_method中;生成逻辑见 generate/src/lib.rs。
宏配置参数详解
from_witx!与wasmtime_integration!的全部参数由 generate/src/config.rs 中的Config/WasmtimeConfig解析,字段使用 Rust 结构体语法传入。下表汇总了所有可用字段:
| 字段 | 取值 | 说明 |
|---|---|---|
witx | ["path/a.witx", ...] | 一组 witx 文件路径,相对于调用宏的 crate 的CARGO_MANIFEST_DIR解析(config.rs) |
witx_literal | "..." | 完整的 witx 文档字符串;不可使用(use ...)指令 |
errors | { errno => YourErrnoType } | 把 witx 标识符映射为富错误类型;也可以使用errno => trappable AnErrorType让 Wiggle 为你生成一个错误类型 |
async | { module::{f1, f2} }或* | 指定哪些模块/函数生成 Rustasynctrait 方法;*表示全部 |
block_on | { module::f }(可选[...]指定执行器) | 宿主方法为 async、但注册进 Wasmtime 的 Func 仍为同步,用block_with执行器阻塞等待;默认使用wiggle::run_in_dummy_executor |
wasmtime | true/false | 是否为每个模块生成add_to_linker;from_witx!默认true |
tracing | true,可带disable_for { module::f } | 是否在生成代码中埋入tracing日志;默认true |
mutable | true/false | ctx 引用是&mut U(默认)还是&U |
target(仅wasmtime_integration!) | crate 路径,如crate | 指向from_witx!生成的模块所在位置 |
几点值得注意的实现细节:
- 除
witx/witx_literal二选一且必填外,其余字段均有默认值(errors为空、async为空、wasmtime默认为true、tracing默认为开启、mutable默认为true),重复提供同一字段会报错(config.rs); errors有两种映射形态(config.rs):UserErrorConfField要求你手写UserErrorConversion方法;TrappableErrorConfField(trappable关键字)则让生成器替你产出错误类型与转换;async的三种语义定义在Asyncness枚举(config.rs):Sync(全同步)、Blocking(Wiggle 异步但 Wasmtime Func 同步,配合block_on)、Async(两边都异步)。
与 Wasmtime 集成:wasmtime_integration! 宏
生成 add_to_linker
当wasmtime_integration!宏被调用时,generate/src/wasmtime.rs 的link_module会为每个模块生成一个注册函数。当提供了target路径时,函数命名为add_<module>_to_linker(无target时命名为add_to_linker),签名大致为:
pub fn add_atoms_to_linker<T, U>( linker: &mut wiggle::wasmtime_crate::Linker<T>, get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static, ) -> wiggle::error::Result<()> where T: 'static, U: atoms::Atoms + Send, // 含异步方法时追加 Send 约束get_cx闭包负责从Store<T>的宿主数据T中取出实现了模块 trait 的U;生成的每个函数体通过caller.get_export("memory")获取 guest 导出的线性内存、读取hostcall_fuel消耗燃料,再构造GuestMemory后调用 ABI 函数(wasmtime.rs)。
内存导出约定与 shim 模块
Wiggle 生成的宿主函数要求调用方模块导出名为memory的线性内存。由于 Wasmtime 的Linker只有在调用方本身是 wasm 模块时才能提供导出内存,测试中通常需要一个 shim 模块来导入这些宿主函数并转导出内存(tests/wasmtime_integration.rs):
(module (import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32))) (import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32))) (memory 1) (export "memory" (memory 0)) ;; ... shim 函数转发调用 ... )用测试验证集成
crates/wiggle/tests/wasmtime_integration.rs 展示了最小可运行的集成测试:from_witx!声明函数为 async,而wasmtime_integration!用block_on声明为 blocking,从而在不支持 async 的同步 Store上也能正常工作:
wiggle::from_witx!({ witx: ["tests/atoms.witx"], async: { atoms::{double_int_return_float} } }); pub mod integration { wiggle::wasmtime_integration!({ target: crate, witx: ["tests/atoms.witx"], block_on: { atoms::{double_int_return_float} } }); }测试中先integration::add_atoms_to_linker(&mut linker, |cx| cx)注册宿主函数,再实例化 shim 模块并调用导出函数,最后断言返回的 errno 为Errno::Ok。异步函数的返回值写入 guest 内存(result_location指针处),测试直接从内存中读回f32字节并校验(wasmtime_integration.rs)——这解释了 ABI 层为什么把"返回多个值"编码为"写内存 + 返回状态码"。
运行时核心抽象
运行时支撑代码位于 crates/wiggle/src,是理解 Wiggle 生成代码行为的关键。
GuestMemory:宿主眼中的 guest 线性内存
src/lib.rs 中的GuestMemory是生成代码里"guest 内存"的表示,用字节数组建模,区分两种内存:
pub enum GuestMemory<'a> { Unshared(&'a mut [u8]), // 宿主独占访问,可安全借用 Shared(&'a [UnsafeCell<u8>]), // 共享内存,随时可能被并发修改 }- 非共享内存允许
as_slice/as_slice_mut直接借用 guest 内存的零拷贝视图;共享内存则只能通过as_cow返回拥有所有权的拷贝,或用to_vec复制到Vec(UnsafeCell<u8>的存在使GuestMemory需要手动实现Send/Sync,见 lib.rs); - 所有读写入口(
read/write/as_slice/as_slice_mut/to_vec/copy_from_slice)都先经过validate_range做越界与溢出检查(u32偏移与长度相乘使用checked_mul),再经validate_size_align做对齐检查,失败时返回GuestError::PtrOutOfBounds/PtrOverflow/PtrNotAligned(lib.rs); - 整数读写统一走
Ordering::Relaxed原子访问并做小端转换,因此共享内存场景也不会引入数据竞争(guest_type.rs)。
GuestPtr:guest 指针的抽象
GuestPtr<T>本质是一个(对定长类型)32 位偏移量,或(对str/[T]不定长类型)(offset, len)偏移/长度对(lib.rs)。它的存在不隐含任何有效性保证——指针可以越界、未对齐,可以随时安全构造,语义上等价于*mut T。类型参数T主要用于静态安全:例如GuestPtr<MyEnum>实际按底层数据读取后再校验字节是否符合枚举定义。
常用操作:
cast::<U>()安全地重解释类型参数;add(amt)做带溢出检查的指针算术;as_array(len)把定长指针扩展为数组指针;GuestPtr<[T]>提供iter()、get(index)、get_range(range)等切片式 API。
GuestType 与 GuestTypeTransparent
GuestTypetrait(guest_type.rs)抽象了"如何从 guest 内存读/写一个类型",通过guest_size()/guest_align()报告大小与对齐,通过read/write完成取值与校验。为i8..u64、f32/f64以及GuestPtr<T>/GuestPtr<[T]>都提供了内置实现(guest_type.rs)。
GuestTypeTransparent是一个unsafe标记 trait,表示该类型在宿主与 guest 中表示完全一致,因而可以使用as_slice零拷贝视图;它只应该由 wiggle 生成代码实现,用户不要手写。
GuestError 与 Region
GuestError(guest_error.rs)统一了所有内存访问错误:越界(PtrOutOfBounds)、未对齐(PtrNotAligned)、溢出(PtrOverflow)、非法枚举/标志值、UTF-8 非法、切片长度不一致,以及带模块/函数/位置信息的嵌套InFunc包装,便于定位出错的具体调用点。Region(region.rs)表示一段连续内存(起始偏移 + 长度),提供overlaps重叠检测(零长度区域永不重叠)与extend放大,配套单元测试见 region.rs。
异步与阻塞模式
Wiggle 的异步支持有三种组合方式(Asyncness):
Sync:宿主方法同步执行;Blocking:宿主方法写成async fn,但注册进 Wasmtime 的是同步Func,调用时用block_on指定的执行器阻塞等待。不指定执行器时默认使用 src/lib.rs 的run_in_dummy_executor——它用一个手工构造的 dummyWaker轮询一次 future;若 future 尚未就绪(Pending),会直接bail!提示必须改用wasmtime_asyncfeature 与异步 Store;Async:宿主方法与 WasmtimeFunc均为异步,需要启用wasmtime_asyncfeature 并使用支持 async 的 Store。
对应的集成测试分别位于 tests/atoms_async.rs、tests/wasmtime_async.rs 与 tests/wasmtime_sync.rs,其中异步测试在 Cargo.toml 中通过required-features标注,只有启用对应 feature 才会编译运行。
Cargo features 说明
wiggle/Cargo.toml 定义了四个 feature:
| feature | 内容 |
|---|---|
wiggle_metadata | 让生成代码附带pub mod metadata(内含 witx 原文与可重解析的document()),需要直接依赖witxcrate;默认开启 |
tracing_log | 让tracing接入log生态后端,便于不引入tracing-subscriber也能输出日志;非默认 |
wasmtime | 引入对wasmtimecrate 的依赖,是wasmtime_integration!生成的Linker代码所必需的;默认开启 |
wasmtime_async | 启用wasmtime/async,支持异步宿主函数;默认开启 |
默认配置为["wiggle_metadata", "wasmtime", "wasmtime_async"]。
仓库中的测试与调试技巧
crates/wiggle/tests 目录提供了覆盖各种 witx 特性的测试集,每对*.rs+*.witx对应一类特性的端到端验证:
- atoms.witx /
atoms.rs、atoms_async.rs:基础标量参数与返回; flags.witx、handles.witx、ints.witx、lists.witx、pointers.witx、records.witx、strings.witx、variant.witx:标志、句柄、整数、列表、指针、记录、字符串、变体等类型;errors.rs/excuse.witx:错误转换;keywords.rs:Rust 关键字规避;wasi.rs/wasi.witx:以 WASI 接口为蓝本的完整演练。
调试生成代码时,可以设置环境变量WIGGLE_DEBUG_BINDGEN:宏会把每次展开的代码写入DEBUG_OUTPUT_DIR下的wiggleN.rs(并用rustfmt格式化),再通过include!引入,方便用cargo expand直接检视生成结果(macro/src/lib.rs)。tracing: false配置项则可以彻底移除生成代码中的日志语句,进一步降低阅读噪音(codegen_settings.rs)。
生态中的实战应用
Wiggle 的价值在大型接口上体现得最充分。本仓库中 crates/wasi(wasmtime-wasi)以 Wiggle 为桥梁把 WASI 宿主实现接入 Wasmtime 引擎;crates/wasi-nn 则是新提案的完整示范——其示例与测试(crates/wasi-nn/examples、crates/wasi-nn/tests)展示了从 witx 声明、from_witx!生成、模块 trait 实现到Linker注册的完整链路。如果你正在为自定义的 WASI 风格接口寻找落地方案,这两个 crate 与本文给出的最小示例足以构成一条可复制的实现路径。
- 语言运行时
- JIT编译
- 编译器
【免费下载链接】wasmtime
A lightweight WebAssembly runtime that is fast, secure, and standards-compliant
相关推荐
wasmtime 中 wiggle-generate 代码生成器架构解析:从 witx 接口到 Rust 绑定的完整实现
wasmtime 中 wiggle generate 代码生成器架构解析:从 witx 接口到 Rust 绑定的完整实现 导读 本文围绕 crates/wigg
语言运行时JIT编译编译器深入解析 Linera Witty:从 Rust 源码生成 WIT 接口与宿主端代码的实践指南
深入解析 Linera Witty:从 Rust 源码生成 WIT 接口与宿主端代码的实践指南 Linera Witty 是 Linera 协议仓库中负责 We
区块链Web3Wasmtime 实战:用 Rust 嵌入 WASIp2 组件(wasmtime-wasi 宿主集成指南)
Wasmtime 实战:用 Rust 嵌入 WASIp2 组件(wasmtime wasi 宿主集成指南) 本篇指南以 Wasmtime 仓库中的 exampl
语言运行时JIT编译编译器
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考