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

v0 source realm

Package riscvdemo runs real compiled machine code on chain, a slice at a time.

Readme View source

r/moul/x/vm/riscvdemo

Demo of p/moul/x/vm/riscv, the RV32IM hart, and p/moul/x/vm/vmkit, the host ABI. This realm holds no logic of its own: it stores images, builds a vmkit.Host, and renders.

What it exists to show is two things realm code cannot do for itself.

The guest was not written for gno. The programs on the front page are flat .text images, the bytes a cross compiler emits for riscv32im-unknown-none-elf. Upload takes hex, so anything you can build with clang, rustc, TinyGo or Zig and strip to its text segment goes in unchanged.

Two samples are exactly that and neither is a mock up. Compiled by clang is a freestanding C program; A token, in Rust is #![no_std] Rust that mints, sends and burns, linking Rust's real core and compiler_builtins, so a 64-bit divide in it runs __udivdi3 on a machine with no 64-bit divide instruction. Both are shipped as the bytes the compiler emitted, and the page shows their source rather than a disassembly, because a disassembly would bury the only interesting fact: nobody wrote the machine code. Sources and build commands: tools/riscv-guests.

A program pauses instead of failing. It runs until its fuel slice is spent, then stops with a snapshot the realm keeps, and the next caller pays for the next slice. The "Heavy loop" sample is 200,006 instructions and takes three transactions at the default slice, which is the whole point.

UploadSample("heavy", "", 0)   -> id
Step(id, 80000)                -> "running"
Step(id, 80000)                -> "running"
Step(id, 80000)                -> "halted"

Reading the instance page

A guest that writes nothing has still computed something, so the instance page restores the snapshot and shows the register file under its ABI names. That is the only place the state is legible: the hart itself does not survive the transaction, only its snapshot does.

What a slice costs

An RV32IM instruction costs about 8,500 gas, measured rather than estimated, so a block buys roughly 350,000 guest instructions. Loading the image costs about 19,600 gas per instruction word and is paid again on every resume, which is why MaxImage is 8 KiB and not a megabyte. The measurements and how they were taken are in the library README.

What this realm does not grant

Send always returns vmkit.ErrNotGranted. No instance here is funded, so a guest that tries to move coins is refused, and that is the capability rule working rather than a missing feature. Guest key-value storage is scoped to the instance being stepped by construction: the tree belongs to the instance, so one program cannot reach another's even though both live in this realm.


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/r/moul/x/vm/riscvdemo/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.

Overview

Package riscvdemo runs real compiled machine code on chain, a slice at a time.

It is a demo of two libraries and carries no logic of its own: p/moul/x/vm/riscv(/p/moul/x/vm/riscv/v0) is the RV32IM hart, and p/moul/x/vm/vmkit(/p/moul/x/vm/vmkit/v0) is the host ABI, the fuel meter and the instance store.

What it exists to show is that the guest was not written for gno. The programs on the front page are flat .text images: the same bytes `rustc --target riscv32im-unknown-none-elf` emits, uploaded as hex. The realm holds the snapshot between transactions, so one computation finishes across several of them, and realm code cannot do that for itself.

Constants 1

const MaxInstances, MaxImage, MaxOutput, MaxInput, DefaultFuel, MaxSliceFuel

 1const (
 2	// MaxInstances is how many programs the realm keeps at once.
 3	MaxInstances = 64
 4	// MaxImage caps an uploaded text segment at 2,048 instructions.
 5	//
 6	// This one is not arbitrary. Predecoding costs about 19,600 gas per word
 7	// and is redone on every resume, so the image size is a tax on every
 8	// transaction that touches the instance, not just the upload. 8 KiB works
 9	// out to roughly 40M gas per slice before the guest executes anything,
10	// which is about 1% of a block.
11	MaxImage = 8192
12	// MaxOutput caps the bytes one program may write. Past it the guest is
13	// trapped rather than truncated, so rendered output is never a lie.
14	MaxOutput = 4096
15	// MaxInput caps the call input a program can be given.
16	MaxInput = 1024
17	// DefaultFuel is the budget an upload gets when it asks for none, and the
18	// slice size Step uses when asked for none.
19	DefaultFuel = 100000
20	// MaxSliceFuel bounds one transaction's work regardless of what the
21	// caller asked for, and it is derived from the measurement rather than
22	// picked: an RV32IM instruction costs about 8,500 gas, so 100,000 of them
23	// is about 850M, a little under a third of a 3G block. Twice this would
24	// still be a legal transaction and a rude one, and it would leave no room
25	// for the predecode and the realm's own storage writes on top.
26	MaxSliceFuel = 100000
27)
source

Caps. Everything a caller can grow is bounded, because all of it is storage somebody pays a deposit on.

Functions 5

func Remove

crossing Action
1func Remove(cur realm, id string)
source

Remove deletes an instance. Owner only.

func Render

1func Render(path string) string
source

Render is the realm's gnoweb view.

  • "/" the samples, the instance list, and what the numbers mean.
  • "/<id>" one instance: its status, fuel, output and register file.

func Step

crossing Action
1func Step(cur realm, id string, fuel int64) string
source

Step runs one slice of the instance: up to `fuel` guest instructions, then stop and keep the snapshot. Anyone may pay for a slice, not only the owner: a paused program that only its owner can advance is a worse demo and no safer, since the program and its budget were both fixed at upload.

func Upload

crossing Action
1func Upload(cur realm, hexImage, input string, budget int64) string
source

Upload stores a hex-encoded text image as a new instance and returns its id.

The image is loaded here rather than at the first Step, so a misaligned or oversized one is rejected by the transaction that submitted it instead of costing somebody else the gas later.

func UploadSample

crossing Action
1func UploadSample(cur realm, name, input string, budget int64) string
source

UploadSample stores one of the programs the front page offers. Writing RV32IM by hand is not the point of this realm, and without this nobody without a cross compiler could press a button.

Imports 11

Source Files 6