☰
wasm3 可挂起执行与快照(Snapshots)实战指南:`--snapshot` / `--resume` 的中断恢复、Gas 检查点与跨架构续跑
2026/9/27 0:42:46 网站建设 项目流程
  • 解释器
  • 嵌入式
  • 语言运行时

【免费下载链接】wasm3

🚀 A fast WebAssembly interpreter and the most universal WASM runtime

项目地址:https://gitcode.com/gh_mirrors/wa/wasm3
点击查看免费下载

wasm3 的快照机制允许正在运行的 WebAssembly 模块在执行中途把控制权交还给宿主(host),并把挂起时的完整执行状态持久化到文件,之后在同一个进程、甚至另一台完全不同架构的机器上,从同一条指令继续运行。本文以 docs/Snapshots.md 为主体,结合 快照格式提案、核心实现、CLI 入口、C API 声明 与嵌入测试,系统讲解命令行用法、C 宿主 API、二进制格式、跨平台可移植性以及快照工具链,帮助你掌握"挂起—持久化—恢复"的完整闭环。


1. 快照是什么:把"运行到一半的程序"整个保存下来

--snapshot <fn>让一个正在运行的模块把控制权交还给宿主,并在之后从同一个位置继续执行。CLI 会将挂起状态保存到指定文件后退出;--resume <fn>则在一个新进程中加载该状态并继续计算。

快照可以保存为:

  • 独立的容器文件(.dmp):以 4 字节魔数\0dmp(0x00 0x64 0x6D 0x70)和 4 字节小端版本号(当前为 1)开头,与 WebAssembly 二进制的开头方式一致,读取方在了解文件内容之前就能识别它;
  • 直接嵌入 WebAssembly 模块(.wasm)的自定义段(custom section):通过--snapshot <out.wasm>[:<name>]写入名为"snapshot"或"snapshot.<name>"的自定义段,嵌入后的.wasm依然是合法、自包含的标准 WebAssembly 二进制,可直接用--resume <out.wasm>恢复。

该特性需要d_m3HasStackSwitching;保存或加载快照还额外需要d_m3HasSnapshots。查看 source/m3_config.h 可知二者的默认关系:d_m3HasStackSwitching默认随d_m3HasTypedRefs开启,d_m3HasSnapshots默认随d_m3HasStackSwitching开启,且d_m3HasStackSwitching依赖d_m3HasTypedRefs(否则编译期报错)。

1.1 快照到底保存了什么

从 docs/Snapshots.md 和 docs/proposals/Snapshots.md 可以明确,快照记录的是WebAssembly 抽象机状态,而不是解释器内部状态:

  • 实例存储(Instance Store):入口模块的线性内存(当前页数 + 稀疏字节块)、所有全局变量的当前值、所有表的运行时尺寸与其中引用(funcref/externref/exnref/contref),以及每个数据段/元素段的动态 drop 状态;
  • 执行/激活状态(Activation):被挂起调用的根续延(root continuation),以及程序仍能到达的所有续延(continuation)——每个挂起续延的激活帧链(函数索引、Wasm 字节偏移、挂起点类型、局部变量与操作数栈的类型化值);
  • 异常存储:所有仍存活的异常包(exnref)及其 tag 载荷。

宿主状态不属于快照,除非宿主主动把它放进去:m3_SetSnapshotHooks提供的回调(详见第 6.2 节)用于把externref变成可在另一进程中重新解析的数字、以及携带宿主自己的字节(打开的文件、时钟等)。CLI 不设置任何 hooks,因此被它恢复的程序不能依赖标准输入输出、预打开目录之外的任何宿主资源。


2. 键盘中断挂起:Ctrl+C 也能做检查点

使用--snapshot <fn>时,键盘中断会请求一个检查点(checkpoint),快照在下一个挂起点(suspension point)处写入:

平台快捷键中断信号
Linux / macOS / 其他 POSIX 系统Ctrl+CSIGINT
Linux / macOS / 其他 POSIX 系统Ctrl+ZSIGTSTP
Windows 控制台Ctrl+C或Ctrl+BreakConsole control event

当执行到达挂起点时,CLI 写入快照文件、打印其路径并退出。用--resume <fn>重新启动即可继续计算。

2.1 从零开始:一个计数到十亿的循环

创建count.wat——一个循环计数到十亿的模块:

(module (memory 1) (global $count (mut i32) (i32.const 0)) (func (export "run") (loop $again (global.set $count (i32.add (global.get $count) (i32.const 1))) (br_if $again (i32.lt_u (global.get $count) (i32.const 1000000000))))) )

用仓库自带的 WABT 工具(test/wasi/wabt/wat2wasm.wasm)将其汇编成.wasm,然后运行并按下Ctrl+C:

$ build/wasm3 test/wasi/wabt/wat2wasm.wasm --enable-all count.wat -o count.wasm $ build/wasm3 --snapshot count.dmp --func run count.wasm Execution suspended. Snapshot saved to count.dmp

2.2 挂起点(Pause Point)是什么

中断请求在下一个挂起点生效,挂起点有两类:

  • 循环的回边(loop back edge);
  • 函数体的开头(entry)。

由于每个循环和每次递归都会经过其中一个挂起点,任何运行足够久的程序都能很快到达。需要注意的是:仍在运行的宿主导入(host import)必须先返回,挂起请求才能生效。此外,在 Windows 上,如果挂起请求处于 pending 状态时用户再次按下键盘中断,该中断会落到默认控制台处理器,可能在任何快照保存之前就终止进程。

从快照提案的 Safepoint Semantics 一节可以进一步理解挂起点的精确定义:每个引擎的挂起点集合是相同的,因为它们由模块中的指令固定:

Kind名称位置
0loop/ back edge每个分支到loop标签的指令:br、br_if、br_table、br_on_*族;catch 子句在它的try_table处,on子句在它的resume/resume_throw处
1suspend紧接suspend或switch之后
2call等待被调用方返回的call/call_indirect/call_ref(绝不包括return_call*)
3resume等待其运行续延的resume/resume_throw/resume_throw_ref
4entry函数体第一条指令之前(空函数体则为end)

其中只有0、1、4三种是暂停可以开始的位置;2和3是外层帧在更深层被挂起时等待的位置。

2.3--interrupt:运行前就请求挂起

--interrupt在运行开始前就发出同样的挂起请求,因此运行会在第一个可挂起点处暂停:

$ build/wasm3 --interrupt --snapshot count.dmp --func run count.wasm

由于恢复时会越过之前暂停的那个点继续执行,所以再次配合--interrupt恢复会在下一个挂起点暂停——这正好实现"从挂起点到挂起点"的逐步执行(stepping)。

2.4 恢复运行

用同一个模块恢复:

$ build/wasm3 --resume count.dmp count.wasm

已保存的执行状态中已经包含函数及其参数,因此恢复时不需要再次指定函数。--resume <fn>会自动启用挂起能力;如果再次被中断,它会覆盖输入的快照文件,除非用--snapshot <fn>指定不同的输出文件。对于首次运行,--snapshot <fn>既启用挂起、又指定输出文件。正常运行结束(未被挂起)不会保存快照。


3. 把快照嵌入.wasm:单文件自检查点

与其维护独立的.wasm与.dmp两个文件,不如把快照直接嵌入.wasm:

$ build/wasm3 --snapshot count_checkpoint.wasm --func run count.wasm Execution suspended. Snapshot (embedded) saved to count_checkpoint.wasm
  • 原地覆盖:再次向同一文件保存会用新快照替换旧快照(而不是追加),因此程序可以随意地反复自检查点,二进制不会无限增长;
  • 原子写入:新文件先写在旧文件旁边,再移动覆盖旧文件,因此中途失败的保存不会破坏原文件。

3.1 自动恢复(Automatic Resumption)

携带未命名snapshot段的模块在被运行时会自动从该快照恢复,无需--resume:

$ build/wasm3 count_checkpoint.wasm
  • --resume none则绕过快照,从_start冷启动(cold start);
  • 用--func显式指定函数也会跳过快照,在一个全新实例上运行该函数。

这背后的语义(见 docs/proposals/Snapshots.md 的 Host Instantiation 小节)是:恢复快照时模块的start函数不得重新执行(其存储修改已反映在快照中);宿主请求调用默认入口之外的导出函数时应当绕过快照恢复,而指定默认入口(如--func _start)不算"显式请求"。

3.2 命名快照(Named Snapshots)

一个二进制可以携带多个不同名字的快照。<file>.wasm:<name>在任何需要文件参数的地方都可以选中其中一个:

# 保存到自定义段 "snapshot.stage1" $ build/wasm3 --snapshot app.wasm:stage1 --func run count.wasm # 恢复它 $ build/wasm3 --resume app.wasm:stage1 $ build/wasm3 app.wasm:stage1

命名规则与优先级:

  • --snapshot或--resume上带的名称优先于运行文件上带的名称;
  • 名称从参数的最后一个.wasm:之后开始(这样目录名里含有.wasm:也不会提前截断路径);
  • --resume <file>.wasm同时指定了要运行的模块和快照,因此不能再附带第二个文件(CLI 在 platforms/app/main.c 会直接报错:--resume <fn> is the module to run; drop the other file)。

两个同名段不会阻止模块加载或冷启动,只有显式选中该名称才会失败(详见 m3_test.c 中的 duplicate_name_fails_when_selected 测试)。命名规范见提案的 Section Naming 约定:默认段"snapshot"至多一个;命名段"snapshot.<name>"的每个完整名称必须唯一,重复保存同名段会替换旧段。


4. Gas 用尽时暂停:按工作量做检查点

把挂起与 gas 上限结合,可以在有界的工作量之后做检查点:

$ wasm3 --gas-limit 100 --snapshot fib.dmp --func fib test/lang/fib32.wasm 24 $ wasm3 --gas-limit 10000 --resume fib.dmp test/lang/fib32.wasm

关键语义:

  • 使用--snapshot <fn>时,gas 耗尽会暂停而不是 trap;
  • 它本质上与键盘中断一样是"暂停请求":运行会继续走到下一个挂起点,因此会略微超出预算一点点;
  • 每次恢复都从--gas-limit获得全新的预算:快照不保存 gas 计数器,也不关心恢复它的这次运行是否被计量——去掉--gas-limit,剩余部分会一直运行到结束;
  • 恢复后的 CLI 运行不会打印函数的返回值。

这一行为在 test/internal/m3_test.c 的snapshot.gas_out_suspend_and_resume测试中有完整印证:先以m3_SetGasLimit(rt1, 100)运行到挂起,验证全局变量g已取得部分进度(> 0 && < 100000),保存快照后,在一个补充了 gas(m3_SetGasLimit(rt2, 10000000))的全新 runtime 中恢复并运行到结束。此外,snapshot.gas_pause_resumes_unmetered 还验证了"在计量运行的暂停点拍的快照,可以由不计量运行恢复"这一特性。


5. 迁移到另一台机器:跨架构恢复

快照按WebAssembly 的术语书写,而非解释器的术语:

  • 每个函数"站在哪里"是其 Wasm 函数体中的字节偏移;
  • 帧持有的是类型化的局部变量和操作数栈值;
  • 任何数字都按小端写出,引用是函数索引、续延 id 或异常 id——文件中没有任何地址、槽位或解释器内部词。

恢复该快照的构建会重新编译同一个模块,把每个值放到它自己代码存放的位置。因此,在 x86-64 上保存的快照可以在完全不同架构、字节序、指针/槽位宽度(d_m3Use32BitSlots)、编译器或操作系统上恢复:

$ build/wasm3 --gas-limit 100 --snapshot fib.dmp --func fib test/lang/fib32.wasm 24 $ qemu-s390x-static build-cross/wasm3-linux-s390x --resume fib.dmp test/lang/fib32.wasm

(上述示例来自 docs/Snapshots.md,展示在 qemu 模拟的 s390x 上恢复;test/internal/m3_test.c 的snapshot.header_is_portable测试则验证了快照头在所有平台上的字节序稳定性。)

模块身份通过wasm_hash校验:它是模块所有非自定义段(section_id != 0,含各段的 ID 字节、原始 LEB128 长度字节和完整载荷,按原始顺序拼接)的XXH64(seed 0)哈希,见 m3_snapshot.c 的 ModuleFingerprint。因此修改自定义元数据不会使快照失效,但修改任何标准段或其二进制编码会。提案同时规定:无法计算哈希的引擎(已不持有模块字节)必须拒绝写入或恢复快照,而不是写一个替代值。

一个值得注意的细节:--resume运行不会再次把程序参数传给程序。程序只在启动时从 WASI 读取一次参数并保存在自己的内存里——因此这一点只对"在程序读取参数之前"拍摄的快照有意义。


6. 从 C 语言挂起与恢复

6.1 基本调用序列

关键原则:必须在编译或查找函数之前启用挂起,这样循环和函数体才会包含服务于m3_RequestSuspend的检查。对于一个导出零参数函数run的已加载模块,宿主可以按如下顺序调用:

m3_SetSuspendable(runtime, true); IM3Function function = NULL; M3Result result = m3_FindFunction(&function, runtime, "run"); if (result) return result; /* Request a pause at the first pause point: the start of run. */ m3_RequestSuspend(runtime); result = m3_Call(function, 0, NULL); if (result == m3Err_continuationSuspended) { /* Service other work here, then continue the same invocation. */ result = m3_ResumeRuntime(runtime); } return result;

语义要点:

  • m3Err_continuationSuspended是一个控制类结果,宿主必须把它与 trap 区分开来单独处理;
  • 一次恢复可以再次挂起(配合每次恢复前的m3_RequestSuspend即可逐步执行);
  • m3_IsSuspended(runtime)报告当前是否存在被挂起的调用;
  • 被挂起的调用保留它的位置:宿主在m3_ResumeRuntime之前发起的其他调用运行在它自己的上下文中,不会触碰被挂起的那一个;
  • 对于 gas 驱动的调度:在编译前用m3_SetGasLimit设置第一个预算,并在每次恢复前补充预算;
  • 恢复运行到结束后,结果会放在原调用本应放置的位置,因此用m3_GetResults读取被调用函数的返回值即可。

上述 API 均声明于 source/wasm3.h,其中m3_RequestSuspend的注释明确说明:请求在下一个挂起点生效,每次m3_ResumeRuntime前再次请求即可实现逐步推进。

6.2 持久化挂起调用

要把一个被挂起的调用持久化到缓冲区,调用m3_SaveSnapshotToBuffer,传入void *bytes = NULL和size_t size = 0,并检查返回值:

m3_SaveSnapshotToBuffer(runtime, &bytes, &size);

恢复到一个全新 runtime 的流程:

  1. 启用挂起;
  2. 解析并加载原始模块,链接其宿主导入;
  3. 若该 runtime 应有 gas 上限,设置 gas 上限;
  4. 调用m3_LoadSnapshotFromBuffer(runtime, module, bytes, size);
  5. 加载成功后调用m3_ResumeRuntime。

约束与细节:

  • 模块必须是全新实例化的:一旦其任何代码运行过(包括start函数),加载就会被拒绝——对应错误信息为"a snapshot restores only into a freshly instantiated module, and this one has run"(见 m3_test.c);
  • 加载在中途失败后模块会不可用:拒绝运行、恢复、保存或再次接收快照,宿主必须释放 runtime 重新开始;
  • 用完快照缓冲区后要用free(bytes)释放;
  • 快照命名的每个函数都必须在启用挂起之后编译;之前编译的函数会被拒绝。

6.3 嵌入模块内的快照 API

快照也可以"搭乘"在它所属的模块里。name用于选中一个快照,NULL或""表示未命名默认快照:

  • m3_HasSnapshot(module, name)—— 判断模块是否携带该快照;
  • m3_GetEmbeddedSnapshot(module, name, &bytes, &size)—— 指向该快照。返回的字节属于模块,与解析它的二进制同生命周期;
  • m3_LoadEmbeddedSnapshot(runtime, module, name)—— 恢复它;
  • m3_SaveSnapshotToModule(runtime, module, name, &out_bytes, &out_size)—— 写出携带被挂起调用的模块字节,替换已存在的同名快照。没有挂起调用时它会失败:postmortem 永远不会被嵌入。out_bytes用free释放。

完整的逐用例示例见 test/internal/m3_test.c 的 snapshot.embedded_roundtrip 测试(保存循环进度、写入模块字节、再在全新 runtime 中恢复)。

6.4 流式读写与宿主 hooks

m3_SaveSnapshot和m3_LoadSnapshot则通过 writer / reader 回调流式传输。每个回调必须恰好传输请求的字节数并返回m3Err_none,或返回一个错误。保存循环进度并恢复到新 runtime 的完整示例位于 snapshot.* 嵌入测试。

m3_SetSnapshotHooks让 runtime 持有最多四个回调组成的M3SnapshotHooks:

  • nameExternRef/bindExternRef:把externref变成一个宿主可在另一进程中重新解析的数字,以及反向恢复;
  • saveHostState/loadHostState:携带宿主自己的字节(打开的文件、时钟,以及它的导入所维护的任何东西)。

规则与边界:

  • 没有nameExternRef的 runtime拒绝保存非空externref;没有加载回调的 runtime 拒绝需要它的快照;
  • 哪些宿主状态可以跨进程恢复由宿主决定:按路径和偏移量重新打开常规文件是合理的,但对 socket 或 pipe 是错误做法;
  • 提案(docs/proposals/Snapshots.md)规定externref被序列化为宿主定义的符号化 64 位名称,0xFFFF_FFFF_FFFF_FFFF被保留、宿主不得用作名称;空引用有独立编码。

snapshot.hooks_carry_host_references_and_state测试(m3_test.c)系统地验证了这些 hooks:缺 namer 时报"the snapshot holds an externref, and nothing here can bind one"、缺 binder 时报错、宿主状态字节往返、读短/读越界均被拒绝等。


7. 快照格式:二进制布局速览

理解格式有助于调试与对接工具。docs/proposals/Snapshots.md 规定快照遵循 WebAssembly 二进制约定编码:

  • 数字用LEB128(无符号u32/u64、有符号s32/s64),容器的版本号和浮点位模式除外(定宽、小端);
  • 序列按vec(T) ::= count:u32, (elem:T)*编码;
  • 类型标识对应标准 Wasmvaltype字节,contref(0x68)除外——它是本格式对续延引用存储家族的专用标签(栈切换在结构上描述这些类型,不赋予单一字节valtype);
  • 内容组织为段(Section),每段有 1 字节段 ID 和 LEB128 载荷长度;未识别的段可用长度跳过。

段 ID 表(在 m3_snapshot.c 中一一对应):

ID段名说明
0Meta头部信息:timestamp、flags、模块哈希
1Memory线性内存页数与稀疏块流
2Table表尺寸与引用元素
3Global全局变量当前值
4Segment数据/元素段的动态 drop 状态
5Exception活跃异常实例与 tag 载荷
6Continuation调用栈、帧、续延与活跃值栈
7Host State可选的不透明宿主数据

Meta 段必须第一个出现,因为它说明恢复引擎需要准备什么;Meta 之后的段可以任意顺序出现,读取方必须接受任何顺序(一个引用可以指向后文才定义的记录,跨记录约束在所有段读完后一并判定)。没有内容要说的段会被省略而不是写成空段;已经存在却无内容(零长度体、空向量)的段会被拒绝。

值得注意的格式细节:

  • typed_value:值自描述编码——i32/i64用 LEB128,f32/f64用定宽小端 IEEE 754(NaN 载荷原样携带,归一化会改变程序后续的计算,因此它们是状态而非噪声),v128原样 16 字节(向量字节无自有字节序,逐字传输),引用类型则编码为ref_value(kind + id);
  • 内存稀疏编码:内存段用三类 chunk 流——0x00结束、0x01原始字节、0x02填充0xFF。参考实现省略0x00的长串,并对达到编译期阈值(默认 128 字节)的0xFF长串发出 fill chunk;
  • 激活帧的values:按声明顺序存放局部变量[L_0..L_{M-1}],随后是从底到顶的操作数栈[S_0..S_{K-1}]。不同挂起 kind 的栈内容不同(back edge 只保留进入目标循环时的栈加循环参数;suspend包含结果;call在操作数之下、参数已消费;resume在操作数之下、参数与续延引用已消费;entry为空),详细对照表见提案的 What a frame holds;
  • wasm_offset从函数在 code section 中条目的起点(越过该条目 size 字段)计数:偏移 0 是局部声明组的计数,第一条指令位于局部声明之后——因此偏移不依赖 size 的编码方式或函数在模块中的位置。

安全与健全性(提案第 868-912 行):读取方必须在校验全部边界后才能应用快照——内存页数不得超出声明最大值、不得低于实例化页数;表尺寸不得超出表限制;wasm_offset必须指向函数代码中的合法挂起点;恢复的局部变量与栈值类型必须与验证器在该挂起点给出的类型严格一致;回边的target_loop必须命名一个包围该指令且可被分支到达的loop;非最内层激活帧必须是call(2) 挂起点、最内层必须是挂起类挂起点(0/1/3/4)、activations不得为空;不得有两个resume激活指向同一续延……无法执行这些检查的读取方必须拒绝整份快照而非部分恢复——"半应用的存储是程序从未处于过的状态"。恢复一旦在已开始修改实例之后失败,该实例即不可用,引擎必须拒绝再运行、恢复、快照或恢复进它,宿主必须丢弃。


8. 快照工具链:snapshot-tool.py与格式测试

8.1 独立于 Wasm3 的格式读写工具

extra/snapshot-tool.py 独立于 Wasm3 引擎读取快照格式,作为命令支持:

snapshot-tool.py info run.dmp [--wasm run.wasm] snapshot-tool.py info app.wasm[:<name>] snapshot-tool.py unpack run.dmp -o run.d [--wasm run.wasm] snapshot-tool.py unpack app.wasm[:<name>] -o run.d snapshot-tool.py pack run.d -o run.dmp snapshot-tool.py verify run.dmp [--wasm run.wasm] snapshot-tool.py verify app.wasm[:<name>] snapshot-tool.py embed run.dmp --wasm app.wasm -o app_with_snap.wasm[:<name>] snapshot-tool.py extract app_with_snap.wasm[:<name>] [-o run.dmp]

各子命令职责:

  • info—— 概要总结一份快照(如snap.root.activations、snap.memories[0].data等);
  • verify—— 校验结构完整性(以及往返一致性,round-trip equality);
  • unpack/pack—— 把快照转成可读的 JSON + 原始二进制块,再打包回字节一致的容器;
  • embed/extract—— 在.wasm二进制中管理嵌入快照。

它还可以作为 Python 模块导入使用,例如:

import importlib.util, pathlib spec = importlib.util.spec_from_file_location("snapshot", "extra/snapshot-tool.py") snaptool = importlib.util.module_from_spec(spec); spec.loader.exec_module(snaptool) snap = snaptool.load("run.dmp") print(snap.globals, snap.root.activations) snap.memories[0].data # bytes, expanded from the chunk encoding open("out.dmp", "wb").write(snaptool.pack(snap))

8.2 格式与语义测试:引擎输出可对照

仓库把快照语义钉死在一组测试与"黄金参考"上:

  • test/internal/m3_test.c 中的snapshot.*测试族覆盖:循环挂起/恢复(snapshot.suspend_and_resume_loop)、gas 耗尽挂起与补充预算恢复(snapshot.gas_out_suspend_and_resume)、嵌套调用挂起(snapshot.nested_call_suspend)、稀疏压缩(snapshot.sparse_compression)、每个挂起点的往返(snapshot.round_trip_at_every_pause_point)、funcref / loop 参数 / exnref 往返、externref拒绝(snapshot.externref_is_refused)、hooks 携带宿主引用与状态、头部可移植性、拒绝异构模块(snapshot.refuses_another_module)、嵌入往返、无物可存(snapshot.nothing_to_save)、postmortem 不可嵌入、重名选择失败等;
  • test/snapshot/frames/README.md 为每个场景提供手工写出的期望帧(每个用例一个模块,格式如@pause interrupt、@frame <continuation> <function> <site> [loop@+<offset>] : [<local>...] | [<value>...]),任何引擎都可以用它对照自己的输出;test/run-snapshot-format-test.py则用它们检查 Wasm3 自身的输出;
  • test/snapshot/frames/ 下的.wat/.wast用例覆盖br-table-loop-params、call-in-block、catch-to-loop、entry、left-regions、on-clause-to-loop、resume-throw-ref、resume、suspend-results、tail-calls等栈切换场景。

9. Postmortem 快照:--dump-on-trap

--dump-on-trap在 trap 之后写入wasm3_dump.dmp。它是事后快照(postmortem snapshot):

  • 它记录状态供检查,但不能恢复;
  • 它不启用挂起,因此 gas 耗尽仍然会 trap;
  • 从 C 语言侧,对没有挂起调用的 runtime 调用m3_SaveSnapshot会写出同样的 postmortem(针对最后一次调用进入的模块);如果没有任何调用进入过任何模块,则失败(对应错误"there is nothing to snapshot",见 m3_test.c 的snapshot.nothing_to_save)。

在格式上,postmortem 由 Meta 段的flags位0x1标记(d_m3SnapshotFlagPostmortem,见 m3_snapshot.c)。提案规定:

  • 无根调用:num_continuations可以为 0(此时整个 Continuation 段省略),所有存储的续延is_root = 0;运行中的续延被记录为 finished;
  • Continuation 记录在resume_throw之后立即停止:不包含bound-argument 计数与激活帧(无论 state 如何);
  • 只包含从已记录存储可达的续延对象,活跃调用的帧不捕获;Host State 段缺席;
  • 读取方可以检查这类快照,但必须拒绝恢复它们;拒绝判定应发生在 Meta 段整体读完之后,而不是看到 flag 就立即中止——flag 之后的字段依然存在且含义不变,中途停下的工具无法继续行走剩余容器。

对应地,嵌入的快照必须是可恢复的:生产者不得嵌入 postmortem(否则携带"snapshot"段 postmortem 的模块根本无法运行,因为运行即恢复该段),读取方同样拒绝;嵌入的快照还必须属于携带它的模块(wasm_hash必须与封闭模块一致)。m3_test.c 的snapshot.postmortem_is_not_embedded与 postmortem_without_suspension 分别验证了这两点。


10. 典型应用场景与上手路径

综合 docs/Snapshots.md 与提案的 Motivation,快照机制面向的场景包括:

  1. Serverless 快速启动(Snapshot-to-Run):大型运行时环境(编译到 Wasm 的 Python/Ruby/JS 引擎)把昂贵的初始化阶段执行一次、拍摄快照并分发,worker 直接从"初始化后状态"恢复;
  2. 异构云/边缘节点间活迁移:在 x86-64 上开始的长时间计算,检查点后跨网络转移到 ARM64/RISC-V 边缘设备无缝继续;
  3. 容错与分布式检查点/重启:定期保存进度,节点故障后从最近快照而不是从头开始;
  4. 确定性时间旅行调试:在关键执行间隔捕获快照,确定性地回放与单步检查历史状态;
  5. 有状态的 AI Agent / 长时工作流:等待外部事件、人工反馈或限速 API 响应时暂停,把整个执行状态序列化到持久存储,收到事件后恢复。

实践上手顺序建议:

  1. 先跑通第 2.1 节的count.wat全流程(汇编 →--snapshot→ Ctrl+C →--resume),直观感受挂起点与恢复语义;
  2. 再用第 3 节的嵌入方式与第 4 节的--gas-limit组合,验证"有界工作量检查点";
  3. 之后阅读 test/internal/m3_test.c 的snapshot.*测试理解 C API 的正确使用顺序(启用挂起 → 解析/加载 → 找函数 → 请求挂起 → 调用 → 处理m3Err_continuationSuspended→ 保存/恢复 → 补充 gas 后恢复);
  4. 最后用 extra/snapshot-tool.py 的info/verify/unpack检查你生成的.dmp或.wasm,对照 test/snapshot/frames/ 的黄金帧深入理解激活帧布局。

快照能力的开关在 source/m3_config.h:需要d_m3HasStackSwitching(依赖d_m3HasTypedRefs),保存/加载还需d_m3HasSnapshots;默认三者随 typed references 一并开启,可在构建时按需裁剪。

  • 解释器
  • 嵌入式
  • 语言运行时

【免费下载链接】wasm3

🚀 A fast WebAssembly interpreter and the most universal WASM runtime

项目地址:https://gitcode.com/gh_mirrors/wa/wasm3
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询