This is a simple stack-based virtual machine that can execute a small set of instructions. It is intended to be the starting point for learning more deeply how WebAssembly works. Right now, like WebAssembly, it only supports 32- and 64-bit integers and floats for operations.
- Open the Python REPL by running
python3
in your terminal. - Install the package
wasmvm
by runningpip install wasmvm
. - Import the
StackVM
class and instructions.from wasmvm import StackVM, Add, Sub, Push, i32
- Create an instance of the
StackVM
class.vm = StackVM() # Optionally, you can specify the number of pages of memory # you want your VM to start with, and the maximum number it # may have: StackVM(1, 10)
- Add instructions to your virtual machine.
vm.instructions = [Push(i32(2)), Push(i32(3)), Add("i32")]
- Execute the instructions.
vm.run()
- Inspect the stack after your instructions have been executed:
vm.inspect() # [5]
Deprecation warning: The API is being deprecated in favor of loading the VM in the browser, by way of Pyodide.
- Create a Python virtual environment, e.g.
python3 -m venv venv
. - Activate the virtual environment, e.g.
source .venv/bin/activate
. - Install the dependencies, e.g.
pip install -r requirements.txt
. - Install the API dependency:
pip install "fastapi[standard]"
- Start the API:
fastapi dev main.py
- Navigate to http://127.0.0.1:8000/docs to use the OpenAPI documentation.
In the directory /static
, there resides a simple frontend that can be used to interact with the API. To start the frontend, you need to first install the necessary tools:
# install bun for macos
brew install oven-sh/bun/bun
bun upgrade
From here, you'll next install the dependencies:
cd static
bun install
Finally, you can start the frontend:
bun run dev
To build the frontend for production, you can run:
bun run build
Since this is a learning tool, it can be useful to see what your machine is doing while it's operating. Since this is intended to be an extensible tool, you can add different types of observers suited to your execution environment.
For example, you can add a simple observer that prints the
stack at each step by running (this one is included in the
virtual_machine
module):
from virtual_machine import StackVM
from lib import Add, Sub, Push
from shared import VMState
def print_state(state: VMState) -> None:
print(f"Stack: {state.stack} || Program Counter: {state.pc}")
vm = StackVM()
vm.add_observer(print_state)
Then, when your virtual machine runs, you'll see the stack printed after each instruction has been executed.
In short, a stack-based virtual machine is a computer that uses a stack to store data and execute instructions ("stack" like pancakes). Operations are performed by pushing values onto the stack and then popping them off to be used as arguments to instructions. The program counter (PC) keeps track of where the virtual machine is in the program, and the stack is used to store intermediate values.
Stack-based virtual machines are found in the wild in many places, such as the Java Virtual Machine (JVM), the .NET Common Language Runtime (CLR), and (of course) the WebAssembly virtual machine that's in your browser (unless you're using Lynx).
Add
Sub
Mul
Div
Eq
Eqz
Lt
Le
coming soonGt
Ge
coming soon
source venv/bin/activate
python -m pip install -r requirements.txt
# Install dev version locally
python -m pip install -e .
# Build wheel
python -m build
# Upload wheel to testpypi -- expects a ~/.pypirc file with credentials
python -m twine upload --verbose --repository testpypi dist/*
# Upload wheel to pypi -- expects a ~/.pypirc file with credentials
python -m twine upload --verbose dist/*
There are many resources available to learn more about WebAssembly and how it works. Here's what I've been using:
- WebAssembly: The Definitive Guide by Brian Sletten
- WABT, the WebAssembly Binary Toolkit
- Claude and GitHub Copilot