Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

README.md

4.14 Kb · 93 lines

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:

gno.land/p/moul/x/vm/vmkit/v0 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.