Realm

v0

gno.land/r/moul/x/vm/riscvdemo/v0

Render

RV32IM, on chain, a slice at a time

Demo of p/moul/x/vm/riscv (the hart) and p/moul/x/vm/vmkit (the host ABI, the fuel meter, the instance store). This realm holds no logic of its own.

The programs below are flat .text images: the same bytes a cross compiler emits for riscv32im-unknown-none-elf. Nothing here was written in a chain language, and nothing translated it.

A program does not run to completion. It runs until its fuel slice is spent, then pauses: the realm keeps the snapshot and the next caller pays for the next slice. Realm code cannot pause itself, and a guest can.

Samples

Hello, gno.land: Eight instructions and a string constant. The write syscall reads the bytes straight out of the text segment and hands them to Host.Output.

addi a0, zero, 1          # fd
lui  a1, 0x1000           # buf
addi a1, a1, 0x20
addi a2, zero, 16         # len
addi a7, zero, 64         # write
ecall
addi a7, zero, 93         # exit
ecall
.ascii "Hello, gno.land\n"

Upload Hello, gno.land

Sum 1 to 100: A real loop, 303 instructions. Writes nothing: the answer is in t0, and the instance page shows the register file.

addi s0, zero, 100
addi t0, zero, 0
addi t1, zero, 0
loop:
addi t1, t1, 1
add  t0, t0, t1
bne  t1, s0, loop
addi a7, zero, 93
ecall

Upload Sum 1 to 100

Heavy loop: 200,006 instructions, four per iteration. At the largest slice this realm allows it takes three transactions to finish, which is the thing it exists to show.

lui  s0, 0xc000           # s0 = 50000
addi s0, s0, 0x350
addi t0, zero, 0
addi t1, zero, 0
loop:
addi t1, t1, 1
add  t0, t0, t1
addi t2, zero, 0
bne  t1, s0, loop
addi a7, zero, 93
ecall

Upload Heavy loop

Multiply and divide: The M extension. t0 = 1000000007 * 3 truncated to 32 bits, then t1 = t0 / 7. Division by zero and the one signed overflow case return values here rather than trapping, which is what the spec says and what a chain needs.

lui  t0, 0x3b9ad000       # t0 = 1000000007
addi t0, t0, -1529
addi t1, zero, 3
mul  t0, t0, t1
addi t2, zero, 7
divu t1, t0, t2
addi a7, zero, 93
ecall

Upload Multiply and divide

Compiled by clang: Not written for gno and not written by hand: a freestanding C program, compiled by clang for riscv32im and shipped as the bytes LLVM emitted. It reads the call input, hashes it with FNV-1a and writes eight hex digits back. With the default input it prints fb7ffba0. Source and build command: tools/riscv-guests/fnv.

long n = syscall3(SYS_READ, 0, (long)in, sizeof(in));

unsigned int h = 2166136261u;
for (long i = 0; i < n; i++) {
	h ^= in[i];
	h *= 16777619u;      /* the reason this is rv32im */
}

for (int i = 0; i < 8; i++) {
	unsigned int nib = (h >> (28 - i * 4)) & 0xF;
	out[i] = nib < 10 ? '0' + nib : 'a' + (nib - 10);
}

syscall3(SYS_WRITE, 1, (long)out, 8);
syscall3(SYS_EXIT, 0, 0, 0);

Upload Compiled by clang

A token, in Rust: no_std Rust, compiled by rustc for riscv32im: mint, send and burn, applied from the call input. It links Rust's real core and compiler_builtins, so the balances being 64-bit means a divide runs __udivdi3 here, because RV32 has no 64-bit divide instruction. With the default input it prints alice 70 and bob 80. Source: tools/riscv-guests/ledger.

match cmd {
	b"send" => {
		let (from, rest) = field(rest);
		let (to, rest) = field(rest);
		let (amount, _) = field(rest);
		let n = parse_u32(amount).ok_or("bad amount")?;
		let fi = led.find(from).ok_or("unknown sender")?;
		if led.balances[fi] < n {
			return Err("insufficient balance");
		}
		let ti = led.intern(to).ok_or("ledger full or bad name")?;
		led.balances[fi] -= n;
		led.balances[ti] += n;
		Ok(())
	}
	_ => Err("unknown command"),
}

Upload A token, in Rust

Instances

idstatusinstructionsslicesoutput
0000003halted22291alice 70\\nbob 80\\n
0000002halted561``
0000001halted1431fb7ffba0

What a slice costs

An RV32IM instruction costs about 8,500 gas here, 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 the image cap below is small. The numbers and how they were taken are in the library README.

Limits

limitvalue
instances64
image bytes8192
input bytes1024
output bytes4096
instructions per slice100000
address space1048576

Transactions

Calls, deploys, and child packages in indexed history.

Call function

Source (qfile)