I’ve bootstrapped a Smalltalk virtual machine and object memory as a WebAssembly (WASM) module, using the type system that supports garbage collection there. I have two motivations for doing this: I’d like to see how fast it can run, and I’d like to see how it can interoperate with other WASM modules in diverse settings, including web browsers, servers, and native mainstream OS apps.
the current test: evaluating (3 squared)
The very first thing I ran was a method adding three and four, reporting the result through a webpage. This required types and classes for Object, SmallInteger, ByteArray, Dictionary, Class, CompiledMethod, Context, and Process, a selectors for #+, and functions for interpreting bytecodes, creating arrays, dictionaries, contexts, and the initial object memory, manipulating dictionaries, stacks, contexts, and for reporting results to JavaScript.
Evaluating (3 + 4) only uses an addition bytecode, instead of actually sending a message. After I got a successful result, I changed the expression to (3 squared). This tested sending an actual message, creating a context for the computation, invoking a method different from the one sending the message.
Using WASM’s JavaScript interoperation facilities, I export two WASM functions to JS for execution in a web browser: createMinimalBootstrap() and interpret(). The createMinimalBootstrap function creates classes, selectors, an unbound method that sends “squared” and an initial context for it, and a “squared” method installed in class SmallInteger, and initializes interpreter state.
With the interpreter and object memory set up, JS can tell the WASM module to start interpreting bytecodes, with interpret(). The initial unbound method, after the bytecodes for performing (3 squared), has a special bytecode for reporting the result to JS. It calls a JS function imported from the webpage, which simply prints the result in the webpage. The webpage also reports how long the interpreter takes to run; it might be interesting when measuring the speed of methods compiled to WASM functions.
the interpreter
The interpreter implements all the traditional Smalltalk bytecodes, and a few more for interoperating with JavaScript. Smalltalk objects are represented with WASM GC reference types: i31refs for SmallIntegers, and structrefs for all other objects. There is a type hierarchy mirroring the Smalltalk classes used in the system. With all objects implemented as reference types, rather than as byte sequences in linear WASM memory, we can leverage WASM’s garbage collector. This is similar to the way SqueakJS leverages the JS runtime engine’s garbage collector in a web browser. Also like SqueakJS, SmallIntegers larger than a signed 31-bit integer are boxed LargeIntegers, as WASM GC doesn’t yet have a built-in reference type for 63-bit signed integers.
the next test: just-in-time compilation of methods to WASM
Now that I can run SmallInteger>>squared as interpreted bytecodes, I’ll write a rudimentary translator from bytecode sequences to WASM functions. It may provide an interesting micro-benchmark for comparing execution speeds.
future work: reading snapshots and more
Obviously, a Smalltalk virtual machine does many things; this is a tiny but promising beginning. In the near future, I’d like to support reading existing Squeak, Pharo, and Cuis object memories, provide more extensive integration with device capabilities through JavaScript, web browsers, and WASI, and support the Sista instruction set for compatibility with the OpenSmalltalk Cog virtual machine. I’m especially interested to see how SqueakWASM might integrate with other WASM modules in the wild.
What would you do with WASM Smalltalk? Please let me know!





