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.
1addi a0, zero, 1 # fd
2lui a1, 0x1000 # buf
3addi a1, a1, 0x20
4addi a2, zero, 16 # len
5addi a7, zero, 64 # write
6ecall
7addi a7, zero, 93 # exit
8ecall
9.ascii "Hello, gno.land\n"
Sum 1 to 100: A real loop, 303 instructions. Writes nothing: the answer is in t0, and the instance page shows the register file.
1addi s0, zero, 100
2addi t0, zero, 0
3addi t1, zero, 0
4loop:
5addi t1, t1, 1
6add t0, t0, t1
7bne t1, s0, loop
8addi a7, zero, 93
9ecall
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.
1lui s0, 0xc000 # s0 = 50000
2addi s0, s0, 0x350
3addi t0, zero, 0
4addi t1, zero, 0
5loop:
6addi t1, t1, 1
7add t0, t0, t1
8addi t2, zero, 0
9bne t1, s0, loop
10addi a7, zero, 93
11ecall
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.
1lui t0, 0x3b9ad000 # t0 = 1000000007
2addi t0, t0, -1529
3addi t1, zero, 3
4mul t0, t0, t1
5addi t2, zero, 7
6divu t1, t0, t2
7addi a7, zero, 93
8ecall
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.
1long n = syscall3(SYS_READ, 0, (long)in, sizeof(in));
2
3unsigned int h = 2166136261u;
4for (long i = 0; i < n; i++) {
5 h ^= in[i];
6 h *= 16777619u; /* the reason this is rv32im */
7}
8
9for (int i = 0; i < 8; i++) {
10 unsigned int nib = (h >> (28 - i * 4)) & 0xF;
11 out[i] = nib < 10 ? '0' + nib : 'a' + (nib - 10);
12}
13
14syscall3(SYS_WRITE, 1, (long)out, 8);
15syscall3(SYS_EXIT, 0, 0, 0);
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.
1match cmd {
2 b"send" => {
3 let (from, rest) = field(rest);
4 let (to, rest) = field(rest);
5 let (amount, _) = field(rest);
6 let n = parse_u32(amount).ok_or("bad amount")?;
7 let fi = led.find(from).ok_or("unknown sender")?;
8 if led.balances[fi] < n {
9 return Err("insufficient balance");
10 }
11 let ti = led.intern(to).ok_or("ledger full or bad name")?;
12 led.balances[fi] -= n;
13 led.balances[ti] += n;
14 Ok(())
15 }
16 _ => Err("unknown command"),
17}
Instances
| id | status | instructions | slices | output |
|---|---|---|---|---|
| 0000003 | halted | 2229 | 1 | alice 70\\nbob 80\\n |
| 0000002 | halted | 56 | 1 | `` |
| 0000001 | halted | 143 | 1 | fb7ffba0 |
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
| limit | value |
|---|---|
| instances | 64 |
| image bytes | 8192 |
| input bytes | 1024 |
| output bytes | 4096 |
| instructions per slice | 100000 |
| address space | 1048576 |