watgo - a WebAssembly Toolkit for Go

Eli Bendersky Tools

Summary

watgo is a pure Go WebAssembly toolkit that can parse WAT, validate, encode to WASM binary, and decode binary format, with both CLI and Go API.

<p>I'm happy to announce the general availability of <a class="reference external" href="https://github.com/eliben/watgo">watgo</a> - the <strong>W</strong>eb<strong>A</strong>ssembly <strong>T</strong>oolkit for <strong>G</strong>o. This project is similar to <a class="reference external" href="https://github.com/webassembly/wabt">wabt</a> (C++) or <a class="reference external" href="https://github.com/bytecodealliance/wasm-tools">wasm-tools</a> (Rust), but in pure, zero-dependency Go.</p> <p>watgo comes with a CLI and a Go API to parse WAT (WebAssembly Text), validate it, and encode it into WASM binaries; it also supports decoding WASM from its binary format.</p> <p>At the center of it all is <a class="reference external" href="https://pkg.go.dev/github.com/eliben/watgo/wasmir">wasmir</a> - a semantic representation of a WebAssembly module that users can examine (and manipulate). This diagram shows the functionalities provided by watgo:</p> <img alt="Block diagram showing the different parts of watgo; described in the next paragraph" class="align-center" src="https://eli.thegreenplace.net/images/2026/watgo-diagram.png" /> <ul class="simple"> <li>Parse: a parser from WAT to <tt class="docutils literal">wasmir</tt></li> <li>Validate: uses the official WebAssembly validation semantics to check that the module is well formed and safe</li> <li>Encode: emits <tt class="docutils literal">wasmir</tt> into WASM binary representation</li> <li>Decode: read WASM binary representation into <tt class="docutils literal">wasmir</tt></li> </ul> <div class="section" id="cli-use-case"> <h2>CLI use case</h2> <p>watgo comes with a CLI, which you can install by issuing this command:</p> <div class="highlight"><pre><span></span>go install github.com/eliben/watgo/cmd/watgo@latest </pre></div> <p>The CLI aims to be compatible with wasm-tools <a class="footnote-reference" href="#footnote-1" id="footnote-reference-1">[1]</a>, and I've already switched my <a class="reference external" href="https://github.com/eliben/wasm-wat-samples">wasm-wat-samples</a> projects to use it; e.g. a command to parse a WAT file, validate it and encode it into binary format:</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 use case</h2> <p><tt class="docutils literal">wasmir</tt> semantically represents a WASM module with an API that's easy to work with. Here's an example of using watgo to parse a simple WAT program and do some analysis:</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">// Module-defined functions carry a type index into m.Types. The function</span><span class="w"></span> <span class="w"> </span><span class="c1">// body itself is a flat sequence of wasmir.Instruction values.</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><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">fn</span><span class="p">.</span><span class="nx">Body</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="nx">instr</span><span class="p">.</span><span class="nx">Kind</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">wasmir</span><span class="p">.</span><span class="nx">InstrLocalGet</span><span class="p">:</span><span class="w"></span> <span class="w"> </span><span class="nx">localGets</span><span class="o">++</span><span class="w"></span> <span class="w"> </span><span class="k">case</span><span class="w"> </span><span class="nx">wasmir</span><span class="p">.</span><span class="nx">InstrI32Add</span><span class="p">:</span><span class="w"></span> <span class="w"> </span><span class="nx">i32Adds</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="p">}</span><span class="w"></span> <span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;module-defined funcs: %d\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">m</span><span class="p">.</span><span class="nx">Funcs</span><span class="p">))</span><span class="w"></span> <span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;i32 params: %d\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">i32Params</span><span class="p">)</span><span class="w"></span> <span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;local.get instructions: %d\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">localGets</span><span class="p">)</span><span class="w"></span> <span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">&quot;i32.add instructions: %d\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">i32Adds</span><span class="p">)</span><span class="w"></span> <span class="p">}</span><span class="w"></span> </pre></div> <p>One important note: the WAT format supports several syntactic niceties that are flattened / canonicalized when lowered to <tt class="docutils literal">wasmir</tt>. For example, all folded instructions are lowered to unfolded ones (linear form), function &amp; type names are resolved to numeric indices, etc. This matches the validation and execution semantics of WASM and its binary representation.</p> <p>These syntactic details are present in watgo in the <tt class="docutils literal">textformat</tt> package (which parses WAT into an AST) and are removed when this is lowered to <tt class="docutils literal">wasmir</tt>. The <tt class="docutils literal">textformat</tt> package is kept internal at this time, but in the future I may consider exposing it publicly - if there's interest.</p> </div> <div class="section" id="testing-strategy"> <h2>Testing strategy</h2> <p>Even though it's still early days for watgo, I'm reasonably confident in its correctness due to a strategy of very heavy testing right from the start.</p> <p>WebAssembly comes with a <a class="reference external" href="https://github.com/WebAssembly/spec/">large official test suite</a>, which is perfect for end-to-end testing of new implementations. The core test suite includes almost 200K lines of WAT files that carry several modules with expected execution semantics and a variety of error scenarios exercised. These live in specially designed <a class="reference external" href="https://github.com/WebAssembly/spec/tree/main/interpreter#scripts">.wast files</a> and leverage a custom spec interpreter.</p> <p>watgo hijacks this approach by using the official test suite for its own testing. A custom harness parses .wast files and uses watgo to convert the WAT in them to binary WASM, which is then executed by Node.js <a class="footnote-reference" href="#footnote-2" id="footnote-reference-2">[2]</a>; this harness is a significant effort in itself, but it's very much worth it - the result is excellent testing coverage. watgo passes the entire WASM spec core test suite.</p> <p>Similarly, we leverage <a class="reference external" href="https://github.com/WebAssembly/wabt/tree/main/test/interp">wabt's interp test suite</a> which also includes end-to-end tests, using a simpler Node-based harness to test them against watgo.</p> <p>Finally, I maintain a collection of realistic program samples written in WAT in the <a class="reference external" href="https://github.com/eliben/wasm-wat-samples">wasm-wat-samples repository</a>; these are also used by watgo to test itself.</p> <hr class="docutils" /> <table class="docutils footnote" frame="void" id="footnote-1" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#footnote-reference-1">[1]</a></td><td>Though not all of wasm-tools's functionality is supported yet.</td></tr> </tbody> </table> <table class="docutils footnote" frame="void" id="footnote-2" rules="none"> <colgroup><col class="label" /><col /></colgroup> <tbody valign="top"> <tr><td class="label"><a class="fn-backref" href="#footnote-reference-2">[2]</a></td><td>To stick to a pure-Go approach also for testing, I originally tried using wazero for this, but had to give up because wazero doesn't support some of the recent WASM proposals that have already made it into the standard (most notably Garbage Collection).</td></tr> </tbody> </table> </div>
Original Article
View Cached Full Text

Cached at: 05/16/26, 03:36 AM

# watgo - a WebAssembly Toolkit for Go Source: [https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go) I'm happy to announce the general availability of[watgo](https://github.com/eliben/watgo)\- the**W**eb**A**ssembly**T**oolkit for**G**o\. This project is similar to[wabt](https://github.com/webassembly/wabt)\(C\+\+\) or[wasm\-tools](https://github.com/bytecodealliance/wasm-tools)\(Rust\), but in pure, zero\-dependency Go\. watgo comes with a CLI and a Go API to parse WAT \(WebAssembly Text\), validate it, and encode it into WASM binaries; it also supports decoding WASM from its binary format\. At the center of it all is[wasmir](https://pkg.go.dev/github.com/eliben/watgo/wasmir)\- a semantic representation of a WebAssembly module that users can examine \(and manipulate\)\. This diagram shows the functionalities provided by watgo: ![Block diagram showing the different parts of watgo; described in the next paragraph](https://eli.thegreenplace.net/images/2026/watgo-diagram.png) - Parse: a parser from WAT towasmir - Validate: uses the official WebAssembly validation semantics to check that the module is well formed and safe - Encode: emitswasmirinto WASM binary representation - Decode: read WASM binary representation intowasmir ## CLI use case watgo comes with a CLI, which you can install by issuing this command: ``` go install github.com/eliben/watgo/cmd/watgo@latest ``` The CLI aims to be compatible with wasm\-tools[\[1\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-1), and I've already switched my[wasm\-wat\-samples](https://github.com/eliben/wasm-wat-samples)projects to use it; e\.g\. a command to parse a WAT file, validate it and encode it into binary format: ``` watgo parse stack.wat -o stack.wasm ``` ## API use case wasmirsemantically represents a WASM module with an API that's easy to work with\. Here's an example of using watgo to parse a simple WAT program and do some analysis: ``` 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 // Module-defined functions carry a type index into m.Types. The function // body itself is a flat sequence of wasmir.Instruction values. 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("module-defined funcs: %d\n", len(m.Funcs)) fmt.Printf("i32 params: %d\n", i32Params) fmt.Printf("local.get instructions: %d\n", localGets) fmt.Printf("i32.add instructions: %d\n", i32Adds) } ``` One important note: the WAT format supports several syntactic niceties that are flattened / canonicalized when lowered towasmir\. For example, all folded instructions are lowered to unfolded ones \(linear form\), function & type names are resolved to numeric indices, etc\. This matches the validation and execution semantics of WASM and its binary representation\. These syntactic details are present in watgo in thetextformatpackage \(which parses WAT into an AST\) and are removed when this is lowered towasmir\. Thetextformatpackage is kept internal at this time, but in the future I may consider exposing it publicly \- if there's interest\. ## Testing strategy Even though it's still early days for watgo, I'm reasonably confident in its correctness due to a strategy of very heavy testing right from the start\. WebAssembly comes with a[large official test suite](https://github.com/WebAssembly/spec/), which is perfect for end\-to\-end testing of new implementations\. The core test suite includes almost 200K lines of WAT files that carry several modules with expected execution semantics and a variety of error scenarios exercised\. These live in specially designed[\.wast files](https://github.com/WebAssembly/spec/tree/main/interpreter#scripts)and leverage a custom spec interpreter\. watgo hijacks this approach by using the official test suite for its own testing\. A custom harness parses \.wast files and uses watgo to convert the WAT in them to binary WASM, which is then executed by Node\.js[\[2\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-2); this harness is a significant effort in itself, but it's very much worth it \- the result is excellent testing coverage\. watgo passes the entire WASM spec core test suite\. Similarly, we leverage[wabt's interp test suite](https://github.com/WebAssembly/wabt/tree/main/test/interp)which also includes end\-to\-end tests, using a simpler Node\-based harness to test them against watgo\. Finally, I maintain a collection of realistic program samples written in WAT in the[wasm\-wat\-samples repository](https://github.com/eliben/wasm-wat-samples); these are also used by watgo to test itself\. --- [\[1\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-reference-1)Though not all of wasm\-tools's functionality is supported yet\.[\[2\]](https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go#footnote-reference-2)To stick to a pure\-Go approach also for testing, I originally tried using wazero for this, but had to give up because wazero doesn't support some of the recent WASM proposals that have already made it into the standard \(most notably Garbage Collection\)\.

Similar Articles

WASI 0.3 Launched

Lobsters Hottest

The article announces the launch of WASI 0.3, which integrates async primitives natively into WebAssembly components via the Component Model, simplifying APIs and enabling better component composition.

Theseus: translating win32 to wasm

Lobsters Hottest

Translates Windows executables (win32/x86) to WebAssembly to run in the browser, discussing challenges like blocking vs async design.

Hoot 0.9.0 released

Lobsters Hottest

Hoot 0.9.0, a Scheme to WebAssembly compiler backend for Guile, has been released with new features and bug fixes, including DWARF support, standard Wasm exceptions, and a game jam template for the Lisp Game Jam.

Gooey: A GPU-accelerated UI framework for Zig

Hacker News Top

Gooey is a GPU-accelerated UI framework for Zig, targeting macOS, Linux, and browser via Metal, Vulkan/Wayland, and WebGPU/WASM. It offers declarative UI, animations, theming, accessibility, and zero external dependencies.