/p/moul/x/vm/vmkit/v0
p/moul/x/vm/vmkit
The host ABI shared by every guest virtual machine that runs inside a gno
realm: a stepping contract with an explicit fuel budget, a capability-scoped
Host, and a snapshot format that lets a guest program outlive the transaction
that started it.
A guest VM implements Machine. Everything else here is the machinery a VM
should not have to write twice: the Meter, the canonical Writer/Reader
snapshots are built from, an in-memory TestHost, and the avl-backed Store a
realm keeps its instances in.
First consumer: p/moul/x/vm/bf. Live demo:
r/moul/x/vm/bfdemo.
1type Machine interface {
2 Step(h Host, fuel int64) (used int64, status Status)
3 Snapshot() []byte
4 Restore(b []byte) error
5}
Status is one of Running, Halted, Trapped, OutOfFuel.
What the ABI is for
Fuel is the interface, not the backstop. Running out of fuel yields
OutOfFuel plus a machine that can be snapshotted, never a panic. Gas still
bounds the transaction; it is just not the thing a guest program is written
against.
Continuations. Snapshot and Restore mean a program runs across blocks.
The realm stores the bytes, the next caller pays for the next slice. Gno realm
code cannot pause itself; a guest can. The property that makes this real is
tested rather than asserted: five slices of one fuel unit must produce exactly
what one slice of five produces, for every program in bf's corpus.
Capabilities, not ambient authority. A guest gets exactly the Host it was
handed. Send returns ErrNotGranted unless the deploying realm funded a
budget, storage is scoped to the instance, and nothing is looked up. Gno itself
has ambient authority through the realm frame, so the guest is where the
capability-secure version can actually be tried.
Meter is not a hot-path type
Meter exists for the API boundary: compute a slice budget, charge it once,
report what was used. Calling Meter.Charge once per guest instruction was
measured at +86% on top of an entire interpreter dispatch loop, and more
than tripled its allocation count. A machine should count fuel in a local and
settle up with the caller. The numbers are in bf's README.
Charge spends nothing when the budget cannot cover the request, so a machine
that stops for lack of fuel is exactly at the instruction it could not pay for,
and resuming re-executes that instruction and no other.
The snapshot codec
Writer and Reader are fixed-width big-endian with length-prefixed bytes, so
the encoding is canonical: the same machine state always produces the same
bytes, on every node. A snapshot is consensus state, so two nodes encoding it
differently is a fork, and a short read is a hard error rather than a zero
value.
Machines are not required to use it, but a machine that invents its own layout owes the zoo an explanation: the cross-VM snapshot cost comparison only means something when the encodings match.
Instance and Store
Instance is one guest program as a realm stores it: code, snapshot between
slices, status, and the fuel accounting that survives the transaction. It holds
bytes rather than a Machine on purpose, which is what makes the storage cost
of a paused program measurable.
Instance.Run folds one slice back into the instance and leaves it untouched
when Restore fails, so a snapshot that cannot be decoded costs the caller gas
but never corrupts the stored program.
Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.
Dependency graph:

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.