watgo - 一个针对Go语言的WebAssembly工具包

Eli Bendersky 工具

摘要

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">&quot;fmt&quot;</span><span class="w"></span> <span class="w"> </span><span class="s">&quot;github.com/eliben/watgo&quot;</span><span class="w"></span> <span class="w"> </span><span class="s">&quot;github.com/eliben/watgo/wasmir&quot;</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 &quot;add&quot;) (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

Lobsters Hottest

Weblings 是一个编译为 WebAssembly 的 Rust 编译器工具链,通过带有 playground 和 Rustlings 练习的网页 UI,支持在浏览器中直接编译和执行 Rust 代码。

在浏览器代码运行器中添加Go语言

Lobsters Hottest

作者详细介绍了在基于浏览器的代码运行器(dailyprog)中添加Go语言所面临的挑战及解决方案,解释了为什么标准的GOOS=js方法无法在V8隔离环境中工作,以及如何通过使用GOOS=wasip1和最小的WASI主机shim来成功实现。