watgo - 一个针对Go语言的WebAssembly工具包
摘要
watgo是一个纯Go语言的WebAssembly工具包,可以解析WAT、验证、编码为WASM二进制,以及解码二进制格式,同时提供CLI和Go API。
<p>我很高兴地宣布<a class="reference external" href="https://github.com/eliben/watgo">watgo</a>正式发布——这是一个针对Go语言的<strong>W</strong>eb<strong>A</strong>ssembly <strong>T</strong>oolkit。该项目类似于<a class="reference external" href="https://github.com/webassembly/wabt">wabt</a>(C++)或<a class="reference external" href="https://github.com/bytecodealliance/wasm-tools">wasm-tools</a>(Rust),但完全使用纯Go实现,无外部依赖。</p>
<p>watgo提供了CLI和Go API,用于解析WAT(WebAssembly文本格式)、验证并将其编码为WASM二进制文件;它还支持从二进制格式解码WASM。</p>
<p>其核心是<a class="reference external" href="https://pkg.go.dev/github.com/eliben/watgo/wasmir">wasmir</a>——一个WebAssembly模块的语义表示,用户可以检视(和操作)。下图展示了watgo提供的功能:</p>
<img alt="展示watgo不同部分的框图;下一段有描述" class="align-center" src="https://eli.thegreenplace.net/images/2026/watgo-diagram.png" />
<ul class="simple">
<li>解析:从WAT到<tt class="docutils literal">wasmir</tt>的解析器</li>
<li>验证:使用官方的WebAssembly验证语义来检查模块结构正确且安全</li>
<li>编码:将<tt class="docutils literal">wasmir</tt>输出为WASM二进制表示</li>
<li>解码:读取WASM二进制表示到<tt class="docutils literal">wasmir</tt></li>
</ul>
<div class="section" id="cli-use-case">
<h2>CLI使用案例</h2>
<p>watgo附带了CLI,您可以通过以下命令安装:</p>
<div class="highlight"><pre><span></span>go install github.com/eliben/watgo/cmd/watgo@latest
</pre></div>
<p>CLI旨在与wasm-tools <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>兼容,我已经将自己的<a class="reference external" href="https://github.com/eliben/wasm-wat-samples">wasm-wat-samples</a>项目切换为使用它;例如,解析WAT文件、验证并编码为二进制格式的命令:</p>
<div class="highlight"><pre><span></span>watgo parse stack.wat -o stack.wasm
</pre></div>
</div>
<div class="section" id="api-use-case">
<h2>API使用案例</h2>
<p><tt class="docutils literal">wasmir</tt>通过易于使用的API语义表示WASM模块。以下是一个使用watgo解析简单WAT程序并进行一些分析的示例:</p>
<div class="highlight"><pre><span></span><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>
<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s">"fmt"</span><span class="w"></span>
<span class="w"> </span><span class="s">"github.com/eliben/watgo"</span><span class="w"></span>
<span class="w"> </span><span class="s">"github.com/eliben/watgo/wasmir"</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">wasmText</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span>
<span class="s">(module</span>
<span class="s"> (func (export "add") (param i32 i32) (result i32)</span>
<span class="s"> local.get 0</span>
<span class="s"> local.get 1</span>
<span class="s"> i32.add</span>
<span class="s"> )</span>
<span class="s"> (func (param f32 i32) (result i32)</span>
<span class="s"> local.get 1</span>
<span class="s"> i32.const 1</span>
<span class="s"> i32.add</span>
<span class="s"> drop</span>
<span class="s"> i32.const 0</span>
<span class="s"> )</span>
<span class="s">)`</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">m</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">watgo</span><span class="p">.</span><span class="nx">ParseWAT</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">wasmText</span><span class="p">))</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nx">i32Params</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nx">localGets</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nx">i32Adds</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="c1">// 模块定义的函数会携带有指向 m.Types 的类型索引。函数体本身是由 wasmir.Instruction 值组成的平面序列。</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">fn</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">Funcs</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">sig</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">Types</span><span class="p">[</span><span class="nx">fn</span><span class="p">.</span><span class="nx">TypeIdx</span><span class="p">]</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">param</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">sig</span><span class="p">.</span><span class="nx">Params</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">param</span><span class="p">.</span><span class="nx">Kind</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">wasmir</span><span class="p">.</span><span class="nx">ValueKindI32</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nx">i32Params</span><span class="o">++</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">instr</span><span class="w"> </span><span class="o">:=</span
查看缓存全文
缓存时间: 2026/05/16 03:36
# watgo - 一个面向 Go 的 WebAssembly 工具包
来源:https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go
我很高兴地宣布 **watgo** (https://github.com/eliben/watgo) 正式可用——这是**W**eb**A**ssembly**T**oolkit for **G**o。该项目的定位类似于 wabt (https://github.com/webassembly/wabt)(C++)或 wasm-tools (https://github.com/bytecodealliance/wasm-tools)(Rust),但它完全用纯 Go 编写且零依赖。
watgo 提供了 CLI 和 Go API,用于解析 WAT(WebAssembly 文本格式)、验证它并将其编码为 WASM 二进制文件;同时也支持从二进制格式解码 WASM。
这一切的核心是 **wasmir** (https://pkg.go.dev/github.com/eliben/watgo/wasmir)——WebAssembly 模块的语义表示形式,用户可以对其进行检查(和操作)。下图展示了 watgo 提供的功能:
![框图展示了 watgo 的不同部分;在下一段中描述]()
- **解析**:从 WAT 解析到 wasmir
- **验证**:使用官方的 WebAssembly 验证语义来检查模块是否格式正确且安全
- **编码**:将 wasmir 转换为 WASM 二进制表示
- **解码**:读取 WASM 二进制表示并转换为 wasmir
## CLI 用例
watgo 附带了一个 CLI,你可以通过以下命令安装:
```
go install github.com/eliben/watgo/cmd/watgo@latest
```
CLI 旨在与 wasm-tools[\[1\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-1) 兼容,并且我已经将我的 wasm-wat-samples (https://github.com/eliben/wasm-wat-samples) 项目切换为使用它;例如,解析 WAT 文件、验证并将其编码为二进制格式的命令:
```
watgo parse stack.wat -o stack.wasm
```
## API 用例
wasmir 以易于使用的 API 语义表示 WASM 模块。以下示例展示了如何使用 watgo 解析一个简单的 WAT 程序并进行分析:
```go
package main
import (
"fmt"
"github.com/eliben/watgo"
"github.com/eliben/watgo/wasmir"
)
const wasmText = `
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
(func (param f32 i32) (result i32)
local.get 1
i32.const 1
i32.add
drop
i32.const 0
)
)`
func main() {
m, err := watgo.ParseWAT([]byte(wasmText))
if err != nil {
panic(err)
}
i32Params := 0
localGets := 0
i32Adds := 0
// 模块定义的函数携带一个指向 m.Types 的类型索引。函数体本身是由 wasmir.Instruction 值组成的扁平序列。
for _, fn := range m.Funcs {
sig := m.Types[fn.TypeIdx]
for _, param := range sig.Params {
if param.Kind == wasmir.ValueKindI32 {
i32Params++
}
}
for _, instr := range fn.Body {
switch instr.Kind {
case wasmir.InstrLocalGet:
localGets++
case wasmir.InstrI32Add:
i32Adds++
}
}
}
fmt.Printf("模块定义的函数个数: %d\n", len(m.Funcs))
fmt.Printf("i32 参数个数: %d\n", i32Params)
fmt.Printf("local.get 指令个数: %d\n", localGets)
fmt.Printf("i32.add 指令个数: %d\n", i32Adds)
}
```
一个重要的说明:WAT 格式支持若干语法糖,这些在降级到 wasmir 时会被扁平化/规范化。例如,所有折叠指令都被降级为展开形式(线性形式),函数和类型名称被解析为数字索引等。这与 WASM 及其二进制表示的验证和执行语义一致。
这些语法细节在 watgo 的 `textformat` 包(将 WAT 解析为 AST)中存在,并且在降级到 wasmir 时被移除。`textformat` 包目前保持内部使用,但将来我可能会考虑将其公开——如果有需求的话。
## 测试策略
尽管 watgo 仍处于早期阶段,但由于从一开始就采用了非常密集的测试策略,我对它的正确性相当有信心。
WebAssembly 附带了一套庞大的官方测试套件 (https://github.com/WebAssembly/spec/),非常适合对新实现进行端到端测试。核心测试套件包含近 20 万行 WAT 文件,其中包含多个模块,附带了预期的执行语义和各种错误场景。这些内容位于特殊设计的 .wast 文件 (https://github.com/WebAssembly/spec/tree/main/interpreter#scripts) 中,并利用了自定义的规范解释器。
watgo 采用了一种巧妙的方法,利用官方测试套件进行自身测试。一个自定义测试工具解析 .wast 文件,并使用 watgo 将其中的 WAT 转换为二进制 WASM,然后由 Node.js[\[2\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-2) 执行;这个测试工具本身就是一个重要的工程,但非常值得——结果是极佳的测试覆盖率。watgo 通过了整个 WASM 规范核心测试套件。
类似地,我们还利用了 wabt 的解释器测试套件 (https://github.com/WebAssembly/wabt/tree/main/test/interp),其中也包含了端到端测试,使用更简单的基于 Node 的测试工具来针对 watgo 进行测试。
最后,我在 wasm-wat-samples 仓库 (https://github.com/eliben/wasm-wat-samples) 中维护了一系列用 WAT 编写的真实程序示例;这些示例也被 watgo 用于自我测试。
---
[\[1\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-reference-1) 不过尚未支持 wasm-tools 的全部功能。
[\[2\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-reference-2) 为了在测试中也保持纯 Go 方式,我最初尝试使用 wazero,但不得不放弃,因为 wazero 不支持某些已纳入标准的最新 WASM 提案(最明显的是垃圾回收)。
相似文章
weblings:在 WASM 内部将 Rust 编译为 WASM
Weblings 是一个编译为 WebAssembly 的 Rust 编译器工具链,通过带有 playground 和 Rustlings 练习的网页 UI,支持在浏览器中直接编译和执行 Rust 代码。
WATaBoy:将Game Boy指令即时编译为Wasm,性能超越原生解释器
本文介绍了WATaBoy,一个Game Boy模拟器,它使用即时编译到WebAssembly的方式,实现了超越原生解释器的性能,是JIT到Wasm在模拟领域的一个概念验证。
hwatu:用于本地编码代理的验证浏览器。无头WebKit,DOM评估,带真实匹配百分比的像素差异,无需Chromium(MIT协议,Rust语言)
hwatu是一款轻量级的验证浏览器,专为本地编码代理设计,具备无头WebKit、带真实匹配百分比的像素差异评分功能,且无Chromium依赖;采用MIT许可协议,使用Rust语言编写。
在浏览器代码运行器中添加Go语言
作者详细介绍了在基于浏览器的代码运行器(dailyprog)中添加Go语言所面临的挑战及解决方案,解释了为什么标准的GOOS=js方法无法在V8隔离环境中工作,以及如何通过使用GOOS=wasip1和最小的WASI主机shim来成功实现。
Wanix — 基于Wasm的Unix沙箱工具,适用于Web
Wanix是一款Wasm原生的Unix沙箱工具,让你在浏览器中完全使用Web组件运行并与真实的Wasm和x86程序交互,灵感来自Plan 9。