<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Eli Bendersky's website - WebAssembly</title><link href="https://eli.thegreenplace.net/" rel="alternate"></link><link href="https://eli.thegreenplace.net/feeds/webassembly.atom.xml" rel="self"></link><id>https://eli.thegreenplace.net/</id><updated>2026-06-07T00:38:58-07:00</updated><entry><title>Thoughts on starting new projects with LLM agents</title><link href="https://eli.thegreenplace.net/2026/thoughts-on-starting-new-projects-with-llm-agents/" rel="alternate"></link><published>2026-06-06T17:38:00-07:00</published><updated>2026-06-07T00:38:58-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-06-06:/2026/thoughts-on-starting-new-projects-with-llm-agents/</id><summary type="html">&lt;p&gt;A few months ago I wrote about &lt;a class="reference external" href="https://eli.thegreenplace.net/2026/rewriting-pycparser-with-the-help-of-an-llm/"&gt;using LLM agents to help restructuring one of my
Python projects&lt;/a&gt;.
It's worth beginning by saying that the
rewrite has been successful by all reasonable measures; I've been able to
continue maintaining that project since then without an issue.&lt;/p&gt;
&lt;p&gt;In this post, I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few months ago I wrote about &lt;a class="reference external" href="https://eli.thegreenplace.net/2026/rewriting-pycparser-with-the-help-of-an-llm/"&gt;using LLM agents to help restructuring one of my
Python projects&lt;/a&gt;.
It's worth beginning by saying that the
rewrite has been successful by all reasonable measures; I've been able to
continue maintaining that project since then without an issue.&lt;/p&gt;
&lt;p&gt;In this post, I want to discuss another project I've recently completed with
significant help from agents: &lt;a class="reference external" href="https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go/"&gt;watgo&lt;/a&gt;. In
this project many things are different; most notably, it's a from-scratch
project rather than a rewrite, and it uses a different programming language
(Go). This post describes my experience working on the project, and some lessons
learned along the way.&lt;/p&gt;
&lt;div class="section" id="the-process"&gt;
&lt;h2&gt;The process&lt;/h2&gt;
&lt;p&gt;This is a new project, so it required extensive design. I began by iterating on
the design with the agent, with a sketch of the API. For this purpose, I
recommend using a Markdown file &lt;a class="reference external" href="https://github.com/eliben/watgo/blob/main/doc/notes.md"&gt;committed into the repository&lt;/a&gt;
for future reference.&lt;/p&gt;
&lt;p&gt;After that, I started asking the agent to write CLs &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; in a logical order that
made sense to me, keeping them small
and reviewable (more on this in the next section). Sometimes it's not easy to
have a small CL, and multiple rounds of revision may confuse the agent;
in this case, I commit the CL and then go back and ask the agent to modify
or refactor the code, as much as needed, with separate CLs. In the worst case,
the whole sequence can be reverted if I feel we've taken the wrong direction
(branches could also be helpful here for more complicated scenarios).&lt;/p&gt;
&lt;p&gt;This point is worth reiterating: sometimes a single CL is a huge step forward,
but requires lots of review, cleanup and refactoring to be viable. I've had
multiple instances where an agent produced several days of work in a single
CL, but I then spent hours instructing it to clean up and refactor. Overall,
it's still a productivity gain, just not as much as some pundits would like us
to believe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="keeping-the-human-in-the-loop"&gt;
&lt;h2&gt;Keeping the human in the loop&lt;/h2&gt;
&lt;p&gt;Given the current state of agent capabilities, I think it's worth splitting
projects into two categories:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Low importance / prototype / throw away projects where deep code
understanding is unnecessary. These can be &amp;quot;vibe-coded&amp;quot; (submitting agent
code without even reviewing it).&lt;/li&gt;
&lt;li&gt;High importance projects that I actually want to maintain; here, vibe-coding
is ill advised and I insist on reviewing and guiding all code the agent
writes before it's submitted (or shortly after, as discussed above).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;watgo&lt;/tt&gt; projects is a clear example of (2): I certainly intend to maintain
this project in the long term, so I insist on code that I understand. With very
few exceptions, no code gets in without full review and often multiple rounds
of revisions.&lt;/p&gt;
&lt;p&gt;Even if the cost for writing code went down, maintaining a project is so much
more than that. It's triaging and fixing bugs, it's thinking through what needs
to be done rather than how to do it, it's keeping the code healthy over time,
and so on. As &lt;a class="reference external" href="https://www.goodreads.com/quotes/273375-everyone-knows-that-debugging-is-twice-as-hard-as-writing"&gt;Brian Kernighan said&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
Everyone knows that debugging is twice as hard as writing a program in the
first place. So if you're as clever as you can be when you write it, how will
you ever debug it?&lt;/blockquote&gt;
&lt;p&gt;Maybe at some point agents will become good enough that projects in category
(2) can be implemented and maintained completely autonomously. Maybe. But
we're certainly not there yet. My hunch is that getting there will require
crossing the AGI line &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;, after which little in our world remains certain.&lt;/p&gt;
&lt;div class="section" id="practical-workflow"&gt;
&lt;h3&gt;Practical workflow&lt;/h3&gt;
&lt;p&gt;If you're using an agent to send an actual PR and only review &lt;em&gt;that&lt;/em&gt;, it's
difficult to be disciplined enough to actually perform a thorough review. I find
the following method to be more reliable:&lt;/p&gt;
&lt;p&gt;I use a CLI agent running locally in my repository, and ask it to update the
code there. In parallel, I have a VSCode window open in the same project, where
I can:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Review the agent's changes using VSCode's diff view&lt;/li&gt;
&lt;li&gt;Make my own tweaks and code changes if needed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once I'm pleased with the change, I manually create a commit.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="keeping-the-cls-small"&gt;
&lt;h2&gt;Keeping the CLs small&lt;/h2&gt;
&lt;p&gt;As mentioned above, it's imperative to keep making progress in small chunks,
with small enough CLs that a human can fully understand in a single review. It's
very tempting to sprint ahead submitting thousands of lines of code every day,
but this temptation has to be avoided. Coding with an agent is like
speed-reading; yes, you're making more progress, but comprehension suffers
the faster you go.&lt;/p&gt;
&lt;p&gt;Particularly for refactoring, agents still take the shortest route to
destination. It's important to guide them to think about the &amp;quot;big picture&amp;quot; at
all times, find all instances where X is better done as Y, not just a single
place noticed during a review. This is why it's sometimes OK to have
a CL submitted before you fully agree with everything, and go back to it later
for several refactoring rounds. Source control works amazingly well when
pair-coding with agents.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-strategy"&gt;
&lt;h2&gt;Testing strategy&lt;/h2&gt;
&lt;p&gt;It's a key point discussed in every &amp;quot;how to succeed with AI&amp;quot; article, but
still critical enough to reiterate here: a solid testing strategy is absolutely
crucial for success. Agents produce - by far - the best results when they have
a solid test suite to test their code against.&lt;/p&gt;
&lt;p&gt;With the &lt;a class="reference external" href="https://github.com/eliben/pycparser"&gt;pycparser&lt;/a&gt; rewrite, I had
a large existing test suite. For &lt;a class="reference external" href="https://github.com/eliben/watgo"&gt;watgo&lt;/a&gt;,
the very first thing I did was think through how to adapt the test suites of
the &lt;a class="reference external" href="https://github.com/WebAssembly/spec/"&gt;WASM spec&lt;/a&gt; and of the
&lt;a class="reference external" href="https://github.com/WebAssembly/wabt"&gt;wabt project&lt;/a&gt; for my needs.&lt;/p&gt;
&lt;p&gt;If your project doesn't have such tests to rely on, this should be your first
order of business - finding one, or building one from scratch. Beware of
self-reinforcing loops though; it's dangerous to trust agents for both the
tests and the implementations tested against them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="language-choice-go-for-agent-written-projects"&gt;
&lt;h2&gt;Language choice - Go for agent-written projects&lt;/h2&gt;
&lt;p&gt;Go is a fantastic language for agents to write, because it's designed to be
very readable by humans. The biggest strengths of Go
are exactly what makes the experience of reviewing agent code so positive:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Go changes very infrequently, so you don't have to wonder &amp;quot;are we using the
most modern / idiomatic approach&amp;quot; or &amp;quot;what the hell is this construct&amp;quot;
as often as with other languages (looking at you, Python and TypeScript).&lt;/li&gt;
&lt;li&gt;There are relatively few ways to accomplish the same thing in Go, further
lowering the mental burden.&lt;/li&gt;
&lt;li&gt;The standard library is rich and there's much less need to keep abreast of
the package-everyone-uses du jour.&lt;/li&gt;
&lt;li&gt;In general, Go is designed for readability, with a mild-but-still-strong type
system, uniform formatting, explicit error propagation and opinionated choices
already made for you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since most of the time spent by humans when using agents is &lt;em&gt;reading&lt;/em&gt; rather
than &lt;em&gt;writing&lt;/em&gt; code, these effects compound and produce a great experience.
Recall the discussion of how some languages are optimized for writability (Perl)
while others are optimized for readability (Go)? Well, when working on a project
with an agent we live in a world of 99% reading vs. 1% writing, so this really
matters.&lt;/p&gt;
&lt;p&gt;I find this aspect really crucial in light of the earlier points made in this
post - namely, keeping the human in the loop by understanding and reviewing
all of the agent's design choices and code.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="final-thoughts"&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;If you're working on a subject that's completely new to you, I would strongly
recommend &lt;em&gt;against&lt;/em&gt; the approach described in this post. To really learn
something, you have to work through it from scratch, yourself, reading,
designing, writing the code. Agents don't change this basic fact; even before
agents, if you wanted to learn X, copying it from Stack Overflow or some other
project clearly wasn't the right way to go. Similarly, while agents can be used
as a prop for learning, they cannot learn &lt;em&gt;for you&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As a corollary, junior engineers should exercise &lt;em&gt;extreme caution&lt;/em&gt; when relying
on LLMs. There's no replacement to hard-won experience and the sweat and tears
of learning new, challenging topics. Learning is supposed to be hard; if it's
too easy, you're probably not learning.&lt;/p&gt;
&lt;p&gt;For senior engineers, agents are a boon; it's a great tool to increase
productivity, avoid the boring stuff, and get unstuck from procrastination; but
only when used judiciously.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;CL stands for Changelist, also known as a &amp;quot;patch&amp;quot; or a &amp;quot;diff&amp;quot; - basically
a standalone commit that touches one or more files. This term originates
from the source control systems Perforce and Subversion.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Programming is the ultimate realization of thought; if machines can
design, produce, maintain and understand code better than humans, it
means they can start improving themselves, which is the definition of
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Technological_singularity"&gt;singularity&lt;/a&gt;.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="Machine Learning"></category><category term="WebAssembly"></category></entry><entry><title>Thoughts on WebAssembly as a stack machine</title><link href="https://eli.thegreenplace.net/2026/thoughts-on-webassembly-as-a-stack-machine/" rel="alternate"></link><published>2026-04-29T19:28:00-07:00</published><updated>2026-04-30T02:30:02-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-04-29:/2026/thoughts-on-webassembly-as-a-stack-machine/</id><summary type="html">&lt;p&gt;This week the article &lt;a class="reference external" href="https://purplesyringa.moe/blog/wasm-is-not-quite-a-stack-machine/"&gt;Wasm is not quite a stack machine&lt;/a&gt; has been
making the rounds and has caught my eye. The post claims that WASM is not a pure
stack machine because it has locals and is missing some stack manipulation
operations like &lt;tt class="docutils literal"&gt;dup&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;swap&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;While I don't …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This week the article &lt;a class="reference external" href="https://purplesyringa.moe/blog/wasm-is-not-quite-a-stack-machine/"&gt;Wasm is not quite a stack machine&lt;/a&gt; has been
making the rounds and has caught my eye. The post claims that WASM is not a pure
stack machine because it has locals and is missing some stack manipulation
operations like &lt;tt class="docutils literal"&gt;dup&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;swap&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;While I don't necessarily disagree, IMHO it's a bit of a semantic
discussion because - to the best of my knowledge - there is no &lt;em&gt;formal&lt;/em&gt;
definition of what is a stack machine. Wikipedia, for example,
says:&lt;/p&gt;
&lt;blockquote&gt;
[...], a stack machine is a computer processor or a process virtual machine in
which the primary interaction is moving short-lived temporary values to and
from a push-down stack.&lt;/blockquote&gt;
&lt;p&gt;WASM certainly fits this definition; the &lt;em&gt;primary&lt;/em&gt; interaction is through the
stack, though WASM is augmented with an infinite register file (locals).
The more purist stack machines like Forth are only limited to the stack and a
memory (pointers into which are managed on the stack); WASM has these too, plus
the registers.&lt;/p&gt;
&lt;p&gt;Speaking of Forth, the mention of &lt;tt class="docutils literal"&gt;dup&lt;/tt&gt; reminded me of my own impressions
of programming in that language, documented in my post about
&lt;a class="reference external" href="https://eli.thegreenplace.net/2025/implementing-forth-in-go-and-c/"&gt;implementing Forth in Go and C&lt;/a&gt;. There,
I highlighted the following essential library function for Forth; it adds an
addend to a value stored in memory.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;+!&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;( addend addr -- )&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;tuck&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;( addr addend addr )&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;@&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;( addr addend value-at-addr )&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;+&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c1"&gt;( addr updated-value )&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;swap&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;( updated-value addr )&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And lamented how difficult it is to understand such code without the
detailed stack view in comments alongside it.&lt;/p&gt;
&lt;p&gt;I find it much simpler to reason about this WASM code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;add_to_byte&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;param&lt;/span&gt; &lt;span class="nv"&gt;$addr&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;param&lt;/span&gt; &lt;span class="nv"&gt;$delta&lt;/span&gt; &lt;span class="kt"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.store8&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.add&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;i32.load8_u&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$addr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$delta&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You may say this is cheating because folded WASM instructions help readability
and they're just syntactic sugar; OK, here's the linear code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$addr&lt;/span&gt;
&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$addr&lt;/span&gt;
&lt;span class="nb"&gt;i32.load8_u&lt;/span&gt;
&lt;span class="nb"&gt;local.get&lt;/span&gt; &lt;span class="nv"&gt;$delta&lt;/span&gt;
&lt;span class="nb"&gt;i32.add&lt;/span&gt;
&lt;span class="nb"&gt;i32.store8&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's still very readable, because - while the stack is used for all the
calculations and actual commands - some of the data lives in named &amp;quot;registers&amp;quot;
instead of on the stack. So we don't need all those tuck-swap contortions to get
things into the right order.&lt;/p&gt;
&lt;p&gt;One might worry about the duplicated &lt;tt class="docutils literal"&gt;local.get $addr&lt;/tt&gt;; wouldn't a real &lt;tt class="docutils literal"&gt;dup&lt;/tt&gt;
be better? Well, not in terms of readability, as we've already discussed. How
about performance? Since the stack VM is just an abstraction and the underlying
CPUs executing this code are register machines anyway, the answer is no - it
doesn't matter at all.&lt;/p&gt;
&lt;p&gt;Modern compiler engineers were forged in the fires of C and its descendants;
arbitrary control flow, arbitrary register and memory access, anything goes.
Compilers are quite sophisticated. Let's see how &lt;tt class="docutils literal"&gt;wasmtime&lt;/tt&gt; compiles our
&lt;tt class="docutils literal"&gt;add_to_byte&lt;/tt&gt; to native code (using &lt;tt class="docutils literal"&gt;wasmtime explore&lt;/tt&gt; with its
default &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;opt-level=2&lt;/span&gt;&lt;/tt&gt;); comments are added by me:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;// Prologue&lt;/span&gt;
&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;mov&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// wasmtime&amp;#39;s VM context pointer lives in rdi; 0x38 is likely its offset&lt;/span&gt;
&lt;span class="c1"&gt;// to the default linear memory. Therefore, r10 will hold the base address&lt;/span&gt;
&lt;span class="c1"&gt;// of the linear memory buffer&lt;/span&gt;
&lt;span class="n"&gt;mov&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;qword&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rdi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x38&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// The first parameter ($addr) is in edx; since WASM values are i32, it&amp;#39;s&lt;/span&gt;
&lt;span class="c1"&gt;// zero-extended into the 64-bit r11 by copying into r11d&lt;/span&gt;
&lt;span class="n"&gt;mov&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r11d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;edx&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// r10+r11 is memory[$addr]; this loads the current value into rsi&lt;/span&gt;
&lt;span class="c1"&gt;// (zero-extending from 8 bits)&lt;/span&gt;
&lt;span class="n"&gt;movzx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// ecx is the first parameter ($delta); this adds the addend to the&lt;/span&gt;
&lt;span class="c1"&gt;// current value&lt;/span&gt;
&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;esi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ecx&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Store cur_value+addend back into memory[$addr]&lt;/span&gt;
&lt;span class="n"&gt;mov&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;r11&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sil&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Epilogue&lt;/span&gt;
&lt;span class="n"&gt;mov&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rbp&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is pretty much the code we'd expect to be emitted for the C statement
&lt;tt class="docutils literal"&gt;mem[addr] += addend&lt;/tt&gt;, or if we were writing x86-64 assembly by hand. The
compiler had no difficulty figuring out that two consecutive loads from
the same WASM local produce the same value and do not - in fact - have to be
duplicated. The WASM model makes it rather easy, because you can't alias locals;
as long as there are no intervening writes into the same local, multiple reads
are known to produce the same value (redundant load elimination).&lt;/p&gt;
</content><category term="misc"></category><category term="WebAssembly"></category><category term="Compilation"></category></entry><entry><title>Debugging WASM in Chrome DevTools</title><link href="https://eli.thegreenplace.net/2026/debugging-wasm-in-chrome-devtools/" rel="alternate"></link><published>2026-04-22T19:23:00-07:00</published><updated>2026-04-23T02:24:51-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-04-22:/2026/debugging-wasm-in-chrome-devtools/</id><summary type="html">&lt;p&gt;When I was working on the &lt;a class="reference external" href="https://eli.thegreenplace.net/2026/compiling-scheme-to-webassembly/"&gt;WASM backend for my Scheme compiler&lt;/a&gt;,
I ran into several tricky situations with debugging generated WASM code. It
turned out that Chrome has a very capable WASM debugger in its DevTools, so in
this brief post I want to share how it can be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When I was working on the &lt;a class="reference external" href="https://eli.thegreenplace.net/2026/compiling-scheme-to-webassembly/"&gt;WASM backend for my Scheme compiler&lt;/a&gt;,
I ran into several tricky situations with debugging generated WASM code. It
turned out that Chrome has a very capable WASM debugger in its DevTools, so in
this brief post I want to share how it can be used.&lt;/p&gt;
&lt;div class="section" id="the-setup-and-harness"&gt;
&lt;h2&gt;The setup and harness&lt;/h2&gt;
&lt;p&gt;I'll be using an example from my &lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples"&gt;wasm-wat-samples project&lt;/a&gt; for this post. In fact,
everything is already in place in the &lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples/tree/main/gc-print-scheme-pairs"&gt;gc-print-scheme-pairs&lt;/a&gt;
sample. This sample shows how to construct Scheme-like s-exprs in WASM using gc
references and print them out recursively. The sample supports nested pairs
of integers, booleans and symbols.&lt;/p&gt;
&lt;p&gt;To see this in action, we have to first compile the WAT file to WASM, for
example using &lt;a class="reference external" href="https://github.com/eliben/watgo"&gt;watgo&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ cd gc-print-scheme-pairs
$ watgo parse gc-print-scheme-pairs.wat -o gc-print-scheme-pairs.wasm
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;browser-loader.html&lt;/span&gt;&lt;/tt&gt; file in that directory already expects to load
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gc-print-scheme-pairs.wasm&lt;/span&gt;&lt;/tt&gt;. But we can't just open it
directly from the file-system; since it loads WASM, this file needs to be
served with a local HTTP server. I personally use &lt;a class="reference external" href="https://github.com/eliben/static-server/"&gt;static-server&lt;/a&gt;
for this, but you can use anything else - like Python's built-in &lt;tt class="docutils literal"&gt;http.server&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ static-server
2026/04/10 08:55:20.244096 Serving directory &amp;quot;.&amp;quot; on http://127.0.0.1:8080
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now it can be opened in the browser by following the printed link and selecting
the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;browser-loader.html&lt;/span&gt;&lt;/tt&gt; file.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-debugging-process"&gt;
&lt;h2&gt;The debugging process&lt;/h2&gt;
&lt;p&gt;Open the Chrome DevTools, and in &lt;em&gt;Sources&lt;/em&gt;, open the &lt;em&gt;Page&lt;/em&gt; view on the left.
It should have one entry under &lt;em&gt;wasm&lt;/em&gt;, which will show the decompiled WAT
code for our module. Note: this code is disassembled from the binary WASM, so
it will lose some WAT syntactic sugar (like folded instructions):&lt;/p&gt;
&lt;img alt="Screenshot showing where WASM source is in DevTools" class="align-center" src="https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot1.png" /&gt;
&lt;p&gt;You can set a breakpoint by clicking on the address column to the left of the
code, and then refresh the page. The DevTools debugger will run the program
again and stop at the breakpoint:&lt;/p&gt;
&lt;img alt="Screenshot showing debugger stopping on the breakpoint line" class="align-center" src="https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot2.png" /&gt;
&lt;p&gt;Here you can step over, into, see local values and call stack, etc - a real
debugger!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="debugging-unexpected-exceptions"&gt;
&lt;h2&gt;Debugging unexpected exceptions&lt;/h2&gt;
&lt;p&gt;The most important use case for me while developing the compiler was debugging
unexpected exceptions (coming from instructions like &lt;tt class="docutils literal"&gt;ref.cast&lt;/tt&gt;). Notice
the checkboxes saying &amp;quot;Pause on ... exceptions&amp;quot; on the right-hand side of the
previous screenshot. With these selected, the DevTools debugger will
automatically stop on an exception and show where it is coming from. Let's
modify the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gc-print-scheme-pairs.wat&lt;/span&gt;&lt;/tt&gt; sample to see this in action. The
&lt;tt class="docutils literal"&gt;$emit_value&lt;/tt&gt; function performs a set of &lt;tt class="docutils literal"&gt;ref.test&lt;/tt&gt; checks to see which kind
of reference it's dealing with before casting; let's add this line at the
very start:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(call $emit_bool (ref.cast (ref $Bool) (local.get $v)))
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's clearly wrong to assume that &lt;tt class="docutils literal"&gt;$v&lt;/tt&gt; is a bool reference without first
testing it; this is just for demonstration purposes.&lt;/p&gt;
&lt;p&gt;Without setting any breakpoints, recompiling this code with &lt;tt class="docutils literal"&gt;watgo&lt;/tt&gt; and
reloading the page, we get:&lt;/p&gt;
&lt;img alt="Screenshot showing debugger stopping on an exception" class="align-center" src="https://eli.thegreenplace.net/images/2026/wasm-debug-screenshot3.png" /&gt;
&lt;p&gt;The debugger stopped at the instruction causing the exception; moreover, in the
&lt;em&gt;Scope&lt;/em&gt; pane on the right we can see that the actual type of &lt;tt class="docutils literal"&gt;$v&lt;/tt&gt; is
&lt;tt class="docutils literal"&gt;(ref $Pair)&lt;/tt&gt;, so it's immediately clear what's going on.&lt;/p&gt;
&lt;p&gt;I've found this capability extremely valuable when writing (or emitting from
a compiler) non-trivial chunks of WASM code using gc types and instructions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="debugger-vs-printfs-in-wasm"&gt;
&lt;h2&gt;Debugger vs. printfs in wasm&lt;/h2&gt;
&lt;p&gt;&amp;quot;Should I use a debugger or just printfs&amp;quot; is a common topic of debate among
programmers. While I'm usually in the &amp;quot;printf debugging&amp;quot;
camp, I'm not dogmatic, and will certainly reach for a debugger when
the situation calls for it.&lt;/p&gt;
&lt;p&gt;Specifically, when investigating reference exceptions in WASM, two strong
factors tilt the decision towards using a debugger:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;In general, WASM's printf capabilities aren't great. We can import print-like
functions from the host (and - in fact - our sample does just that), but
they're not very flexible and dealing with strings in WASM is painful in
general. This is compounded even more when working with gc types, because
these aren't even visible to the host (they're opaque references). If we want
to do printf debugging of gc values, we have to build &lt;em&gt;a lot&lt;/em&gt; of scaffolding
first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Exception debugging - in general - is much easier with a supportive debugger
in hand. Our &lt;tt class="docutils literal"&gt;ref.cast&lt;/tt&gt; exception from the example above could have
happened &lt;em&gt;anywhere&lt;/em&gt; in the code. Imagine having to debug a very large
WASM program (emitted by a compiler) to find the source of a failed
&lt;tt class="docutils literal"&gt;ref.cast&lt;/tt&gt;; the debugger takes you right to the spot!&lt;/p&gt;
&lt;p&gt;In fact, even for C programming, I've always found &lt;tt class="docutils literal"&gt;gdb&lt;/tt&gt; most useful for
pinpointing the source of segmentation faults and similar crashes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="WebAssembly"></category><category term="Debuggers"></category><category term="JavaScript"></category></entry><entry><title>watgo - a WebAssembly Toolkit for Go</title><link href="https://eli.thegreenplace.net/2026/watgo-a-webassembly-toolkit-for-go/" rel="alternate"></link><published>2026-04-09T19:28:00-07:00</published><updated>2026-04-10T02:28:00-07:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-04-09:/2026/watgo-a-webassembly-toolkit-for-go/</id><summary type="html">&lt;p&gt;I'm happy to announce the general availability of &lt;a class="reference external" href="https://github.com/eliben/watgo"&gt;watgo&lt;/a&gt;
- the &lt;strong&gt;W&lt;/strong&gt;eb&lt;strong&gt;A&lt;/strong&gt;ssembly &lt;strong&gt;T&lt;/strong&gt;oolkit for &lt;strong&gt;G&lt;/strong&gt;o. This project is similar to
&lt;a class="reference external" href="https://github.com/webassembly/wabt"&gt;wabt&lt;/a&gt; (C++) or
&lt;a class="reference external" href="https://github.com/bytecodealliance/wasm-tools"&gt;wasm-tools&lt;/a&gt; (Rust), but in
pure, zero-dependency Go.&lt;/p&gt;
&lt;p&gt;watgo comes with a CLI and a Go API to parse WAT (WebAssembly Text), validate
it …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'm happy to announce the general availability of &lt;a class="reference external" href="https://github.com/eliben/watgo"&gt;watgo&lt;/a&gt;
- the &lt;strong&gt;W&lt;/strong&gt;eb&lt;strong&gt;A&lt;/strong&gt;ssembly &lt;strong&gt;T&lt;/strong&gt;oolkit for &lt;strong&gt;G&lt;/strong&gt;o. This project is similar to
&lt;a class="reference external" href="https://github.com/webassembly/wabt"&gt;wabt&lt;/a&gt; (C++) or
&lt;a class="reference external" href="https://github.com/bytecodealliance/wasm-tools"&gt;wasm-tools&lt;/a&gt; (Rust), but in
pure, zero-dependency Go.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;At the center of it all is &lt;a class="reference external" href="https://pkg.go.dev/github.com/eliben/watgo/wasmir"&gt;wasmir&lt;/a&gt; - a semantic
representation of a WebAssembly module that users can examine (and manipulate).
This diagram shows the functionalities provided by watgo:&lt;/p&gt;
&lt;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" /&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Parse: a parser from WAT to &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Validate: uses the official WebAssembly validation semantics to check that the
module is well formed and safe&lt;/li&gt;
&lt;li&gt;Encode: emits &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt; into WASM binary representation&lt;/li&gt;
&lt;li&gt;Decode: read WASM binary representation into &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="cli-use-case"&gt;
&lt;h2&gt;CLI use case&lt;/h2&gt;
&lt;p&gt;watgo comes with a CLI, which you can install by issuing this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;go install github.com/eliben/watgo/cmd/watgo@latest
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The CLI aims to be compatible with wasm-tools &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;, and I've already switched my
&lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples"&gt;wasm-wat-samples&lt;/a&gt; projects to
use it; e.g. a command to parse a WAT file, validate it and encode it into
binary format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;watgo parse stack.wat -o stack.wasm
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="api-use-case"&gt;
&lt;h2&gt;API use case&lt;/h2&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt; 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:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/eliben/watgo&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/eliben/watgo/wasmir&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;
&lt;span class="s"&gt;(module&lt;/span&gt;
&lt;span class="s"&gt;  (func (export &amp;quot;add&amp;quot;) (param i32 i32) (result i32)&lt;/span&gt;
&lt;span class="s"&gt;    local.get 0&lt;/span&gt;
&lt;span class="s"&gt;    local.get 1&lt;/span&gt;
&lt;span class="s"&gt;    i32.add&lt;/span&gt;
&lt;span class="s"&gt;  )&lt;/span&gt;
&lt;span class="s"&gt;  (func (param f32 i32) (result i32)&lt;/span&gt;
&lt;span class="s"&gt;    local.get 1&lt;/span&gt;
&lt;span class="s"&gt;    i32.const 1&lt;/span&gt;
&lt;span class="s"&gt;    i32.add&lt;/span&gt;
&lt;span class="s"&gt;    drop&lt;/span&gt;
&lt;span class="s"&gt;    i32.const 0&lt;/span&gt;
&lt;span class="s"&gt;  )&lt;/span&gt;
&lt;span class="s"&gt;)`&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;watgo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ParseWAT&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wasmText&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;i32Params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;localGets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;i32Adds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// Module-defined functions carry a type index into m.Types. The function&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// body itself is a flat sequence of wasmir.Instruction values.&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Funcs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TypeIdx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ValueKindI32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;i32Params&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InstrLocalGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;localGets&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wasmir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InstrI32Add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;i32Adds&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;module-defined funcs: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Funcs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;i32 params: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i32Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;local.get instructions: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localGets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;i32.add instructions: %d\n&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i32Adds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One important note: the WAT format supports several syntactic niceties that
are flattened / canonicalized when lowered to &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;. For example, all folded
instructions are lowered to unfolded ones (linear form), function &amp;amp; type
names are resolved to numeric indices, etc. This matches the validation and
execution semantics of WASM and its binary representation.&lt;/p&gt;
&lt;p&gt;These syntactic details are present in watgo in the &lt;tt class="docutils literal"&gt;textformat&lt;/tt&gt; package
(which parses WAT into an AST) and are removed when this is lowered to &lt;tt class="docutils literal"&gt;wasmir&lt;/tt&gt;.
The &lt;tt class="docutils literal"&gt;textformat&lt;/tt&gt; package is kept internal at this time, but in the future I
may consider exposing it publicly - if there's interest.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing-strategy"&gt;
&lt;h2&gt;Testing strategy&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;WebAssembly comes with a &lt;a class="reference external" href="https://github.com/WebAssembly/spec/"&gt;large official test suite&lt;/a&gt;,
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 &lt;a class="reference external" href="https://github.com/WebAssembly/spec/tree/main/interpreter#scripts"&gt;.wast files&lt;/a&gt; and
leverage a custom spec interpreter.&lt;/p&gt;
&lt;p&gt;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 &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;; 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.&lt;/p&gt;
&lt;p&gt;Similarly, we leverage &lt;a class="reference external" href="https://github.com/WebAssembly/wabt/tree/main/test/interp"&gt;wabt's interp test suite&lt;/a&gt; which also
includes end-to-end tests, using a simpler Node-based harness to test them
against watgo.&lt;/p&gt;
&lt;p&gt;Finally, I maintain a collection of realistic program samples written in
WAT in the &lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples"&gt;wasm-wat-samples repository&lt;/a&gt;;
these are also used by watgo to test itself.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Though not all of wasm-tools's functionality is supported yet.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;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).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Go"></category><category term="WebAssembly"></category><category term="Compilation"></category></entry><entry><title>Compiling Scheme to WebAssembly</title><link href="https://eli.thegreenplace.net/2026/compiling-scheme-to-webassembly/" rel="alternate"></link><published>2026-01-17T14:37:00-08:00</published><updated>2026-01-17T22:40:40-08:00</updated><author><name>Eli Bendersky</name></author><id>tag:eli.thegreenplace.net,2026-01-17:/2026/compiling-scheme-to-webassembly/</id><summary type="html">&lt;p&gt;One of my oldest open-source projects - &lt;a class="reference external" href="https://github.com/eliben/bobscheme"&gt;Bob&lt;/a&gt;
- has &lt;a class="reference external" href="https://eli.thegreenplace.net/2010/11/06/bob-a-scheme-interpreter-compiler-and-vm-in-python"&gt;celebrated 15 a couple of months ago&lt;/a&gt;.
Bob is a suite of implementations of the Scheme programming language in Python,
including an interpreter, a compiler and a VM. Back then I was doing some hacking
on CPython internals and was very curious …&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of my oldest open-source projects - &lt;a class="reference external" href="https://github.com/eliben/bobscheme"&gt;Bob&lt;/a&gt;
- has &lt;a class="reference external" href="https://eli.thegreenplace.net/2010/11/06/bob-a-scheme-interpreter-compiler-and-vm-in-python"&gt;celebrated 15 a couple of months ago&lt;/a&gt;.
Bob is a suite of implementations of the Scheme programming language in Python,
including an interpreter, a compiler and a VM. Back then I was doing some hacking
on CPython internals and was very curious about how CPython-like bytecode VMs
work; Bob was an experiment to find out, by implementing one from scratch for
R5RS Scheme.&lt;/p&gt;
&lt;p&gt;Several months later I &lt;a class="reference external" href="https://eli.thegreenplace.net/2011/04/09/a-c-vm-added-to-bob"&gt;added a C++ VM to Bob&lt;/a&gt;,
as an exercise to learn how such VMs are implemented in a low-level language
without all the runtime support Python provides; most importantly, without the
built-in GC. The C++ VM in Bob implements its own mark-and-sweep GC.&lt;/p&gt;
&lt;p&gt;After many quiet years (with just a sprinkling of cosmetic changes, porting to
GitHub, updates to Python 3, etc), I felt the itch to work on Bob again just
before the holidays. Specifically, I decided to add another compiler to the
suite - this one from Scheme directly to WebAssembly.&lt;/p&gt;
&lt;p&gt;The goals of this effort were two-fold:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Experiment with lowering a real, high-level language like Scheme to
WebAssembly. Experiments like the recent &lt;a class="reference external" href="https://eli.thegreenplace.net/2025/revisiting-lets-build-a-compiler/"&gt;Let's Build a Compiler&lt;/a&gt;
compile toy languages that are at the C level (no runtime). Scheme has built-in
data structures, lexical closures, garbage collection, etc. It's much more challenging.&lt;/li&gt;
&lt;li&gt;Get some hands-on experience with the WASM GC extension &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;. I have several
samples of using WASM GC in the &lt;a class="reference external" href="https://github.com/eliben/wasm-wat-samples"&gt;wasm-wat-samples repository&lt;/a&gt;,
but I really wanted to try it for something &amp;quot;real&amp;quot;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Well, it's done now; here's an updated schematic of the Bob project:&lt;/p&gt;
&lt;img alt="Bob project diagram with all the components it includes" class="align-center" src="https://eli.thegreenplace.net/images/2026/bob_toplevel.png" /&gt;
&lt;p&gt;The new part is the rightmost vertical path. A &lt;a class="reference external" href="https://github.com/eliben/bobscheme/blob/main/bob/wasmcompiler.py"&gt;WasmCompiler&lt;/a&gt;
class lowers parsed Scheme expressions all the way down to WebAssembly text,
which can then be compiled to a binary and executed using standard WASM tools &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="highlights"&gt;
&lt;h2&gt;Highlights&lt;/h2&gt;
&lt;p&gt;The most interesting aspect of this project was working with WASM GC to
represent Scheme objects. As long as we properly box/wrap all values in
&lt;tt class="docutils literal"&gt;ref&lt;/tt&gt;s, the underlying WASM execution environment will take care of the
memory management.&lt;/p&gt;
&lt;p&gt;For Bob, here's how some key Scheme objects are represented:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;;; PAIR holds the car and cdr of a cons cell.
(type $PAIR (struct (field (mut (ref null eq))) (field (mut (ref null eq)))))

;; BOOL represents a Scheme boolean. zero -&amp;gt; false, nonzero -&amp;gt; true.
(type $BOOL (struct (field i32)))

;; SYMBOL represents a Scheme symbol. It holds an offset in linear memory
;; and the length of the symbol name.
(type $SYMBOL (struct (field i32) (field i32)))
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;$PAIR&lt;/tt&gt; is of particular interest, as it may contain arbitrary objects in
its fields; &lt;tt class="docutils literal"&gt;(ref null eq)&lt;/tt&gt; means &amp;quot;a nullable reference to something that
has identity&amp;quot;. &lt;tt class="docutils literal"&gt;ref.test&lt;/tt&gt; can be used to check - for a given
reference - the run-time type of the value it refers to.&lt;/p&gt;
&lt;p&gt;You may wonder - what about numeric values? Here WASM has a trick - the &lt;tt class="docutils literal"&gt;i31&lt;/tt&gt;
type can be used to represent a reference to an integer, but without
actually boxing it (one bit is used to distinguish such an object from a
real reference). So we don't need a separate type to hold references to numbers.&lt;/p&gt;
&lt;p&gt;Also, the &lt;tt class="docutils literal"&gt;$SYMBOL&lt;/tt&gt; type looks unusual - how is it represented with two
numbers? The key to the mystery is that WASM has no built-in support for
strings; they should be implemented manually using offsets to linear memory.
The Bob WASM compiler emits the string values of all symbols encountered into
linear memory, keeping track of the offset and length of each one; these are
the two numbers placed in &lt;tt class="docutils literal"&gt;$SYMBOL&lt;/tt&gt;. This also allows to fairly easily
implement the string interning feature of Scheme; multiple instances of the
same symbol will only be allocated once.&lt;/p&gt;
&lt;p&gt;Consider this trivial Scheme snippet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The compiler emits the symbols &amp;quot;foo&amp;quot; and &amp;quot;bar&amp;quot; into linear memory as follows &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(data (i32.const 2048) &amp;quot;foo&amp;quot;)
(data (i32.const 2051) &amp;quot;bar&amp;quot;)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And looking for one of these addresses in the rest of the emitted code, we'll
find:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(struct.new $SYMBOL (i32.const 2051) (i32.const 3))
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As part of the code for constructing the constant &lt;tt class="docutils literal"&gt;cons&lt;/tt&gt; list representing the
argument to &lt;tt class="docutils literal"&gt;write&lt;/tt&gt;; address 2051 and length 3: this is the symbol &lt;tt class="docutils literal"&gt;bar&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Speaking of &lt;tt class="docutils literal"&gt;write&lt;/tt&gt;, implementing this builtin was quite interesting. For
compatibility with the other Bob implementations in my repository, &lt;tt class="docutils literal"&gt;write&lt;/tt&gt;
needs to be able to print recursive representations of arbitrary Scheme values,
including lists, symbols, etc.&lt;/p&gt;
&lt;p&gt;Initially I was reluctant to implement all of this functionality by hand in
WASM text, but all alternatives ran into challenges:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Deferring this to the host is difficult because the host environment has
no access to WASM GC references - they are completely opaque.&lt;/li&gt;
&lt;li&gt;Implementing it in another language (maybe C?) and lowering to WASM is also
challenging for a similar reason - the other language is unlikely to have
a good representation of WASM GC objects.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So I bit the bullet and - with some AI help for the tedious parts - just wrote
an implementation of &lt;tt class="docutils literal"&gt;write&lt;/tt&gt; directly in WASM text; it wasn't really that
bad. I import only two functions from the host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(import &amp;quot;env&amp;quot; &amp;quot;write_char&amp;quot; (func $write_char (param i32)))
(import &amp;quot;env&amp;quot; &amp;quot;write_i32&amp;quot; (func $write_i32 (param i32)))
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Though emitting integers &lt;a class="reference external" href="https://eli.thegreenplace.net/2023/itoa-integer-to-string-in-webassembly/"&gt;directly from WASM isn't hard&lt;/a&gt;,
I figured this project already has enough code and some host help here would
be welcome. For all the rest, only the lowest level &lt;tt class="docutils literal"&gt;write_char&lt;/tt&gt; is used.
For example, here's how booleans are emitted in the canonical Scheme notation
(&lt;tt class="docutils literal"&gt;#t&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;#f&lt;/tt&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(func $emit_bool (param $b (ref $BOOL))
    (call $emit (i32.const 35)) ;; &amp;#39;#&amp;#39;
    (if (i32.eqz (struct.get $BOOL 0 (local.get $b)))
        (then (call $emit (i32.const 102))) ;; &amp;#39;f&amp;#39;
        (else (call $emit (i32.const 116))) ;; &amp;#39;t&amp;#39;
    )
)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was a really fun project, and I learned quite a bit about realistic code
emission to WASM. Feel free to check out the source code of &lt;a class="reference external" href="https://github.com/eliben/bobscheme/blob/main/bob/wasmcompiler.py"&gt;WasmCompiler&lt;/a&gt; - it's
very well documented. While it's a bit over 1000 LOC in total &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt;, more than half
of that is actually WASM text snippets that implement the builtin types and
functions needed by a basic Scheme implementation.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The GC proposal &lt;a class="reference external" href="https://github.com/WebAssembly/gc"&gt;is documented here&lt;/a&gt;.
It was officially added to the WASM spec in Oct 2023.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;p class="first"&gt;In Bob this is currently done with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;bytecodealliance/wasm-tools&lt;/span&gt;&lt;/tt&gt; for the
text-to-binary conversion and Node.js for the execution environment, but
this can change in the future.&lt;/p&gt;
&lt;p class="last"&gt;I actually wanted to use Python bindings to wasmtime, but these don't
appear to support WASM GC yet.&lt;/p&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;2048 is just an arbitrary offset the compiler uses as the beginning of
the section for symbols in memory. We could
also use the multiple memories feature of WASM and dedicate a separate
linear memory just for symbols.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;To be clear, this is just the WASM compiler class; it uses the &lt;tt class="docutils literal"&gt;Expr&lt;/tt&gt;
representation of Scheme that is created by Bob's parser (and lexer);
the code of these other components is shared among all Bob
implementations and isn't counted here.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="Lisp"></category><category term="Python"></category><category term="WebAssembly"></category></entry></feed>