samples.gno
5.61 Kb · 198 lines
1package riscvdemo
2
3import "gno.land/p/moul/x/vm/riscv/v0"
4
5// The programs the front page offers, as the machine words a cross compiler
6// would have emitted. They are assembled outside this repo and pinned here,
7// and riscvdemo_test.gno runs every one of them to the expected answer, so a
8// wrong word is a failing test rather than a page that traps when clicked.
9type sample struct {
10 slug string // what UploadSample takes: short, no punctuation, stable
11 name string
12 budget int64 // the instance's total instruction budget, not the slice
13
14 // builtin names an image the library ships compiled, instead of the words
15 // below. Empty means the words are the program.
16 builtin string
17 words []uint32
18 input string
19 note string
20 asm string
21}
22
23var samples = []sample{
24 {
25 slug: "hello",
26 name: "Hello, gno.land",
27 words: []uint32{
28 0x00100513, 0x000015B7, 0x02058593, 0x01000613,
29 0x04000893, 0x00000073, 0x05D00893, 0x00000073,
30 0x6C6C6548, 0x67202C6F, 0x6C2E6F6E, 0x0A646E61,
31 },
32 budget: 1000,
33 note: "Eight instructions and a string constant. The write syscall reads " +
34 "the bytes straight out of the text segment and hands them to Host.Output.",
35 asm: `addi a0, zero, 1 # fd
36lui a1, 0x1000 # buf
37addi a1, a1, 0x20
38addi a2, zero, 16 # len
39addi a7, zero, 64 # write
40ecall
41addi a7, zero, 93 # exit
42ecall
43.ascii "Hello, gno.land\n"`,
44 },
45 {
46 slug: "sum100",
47 name: "Sum 1 to 100",
48 words: []uint32{
49 0x06400413, 0x00000293, 0x00000313, 0x00130313,
50 0x006282B3, 0xFE831CE3, 0x05D00893, 0x00000073,
51 },
52 budget: 1000,
53 note: "A real loop, 303 instructions. Writes nothing: the answer is in t0, " +
54 "and the instance page shows the register file.",
55 asm: `addi s0, zero, 100
56addi t0, zero, 0
57addi t1, zero, 0
58loop:
59addi t1, t1, 1
60add t0, t0, t1
61bne t1, s0, loop
62addi a7, zero, 93
63ecall`,
64 },
65 {
66 slug: "heavy",
67 name: "Heavy loop",
68 words: []uint32{
69 0x0000C437, 0x35040413, 0x00000293, 0x00000313,
70 0x00130313, 0x006282B3, 0x00000393, 0xFE831AE3,
71 0x05D00893, 0x00000073,
72 },
73 budget: 250000,
74 note: "200,006 instructions, four per iteration. At the largest slice this realm " +
75 "allows it takes three transactions to finish, which is the thing it exists to show.",
76 asm: `lui s0, 0xc000 # s0 = 50000
77addi s0, s0, 0x350
78addi t0, zero, 0
79addi t1, zero, 0
80loop:
81addi t1, t1, 1
82add t0, t0, t1
83addi t2, zero, 0
84bne t1, s0, loop
85addi a7, zero, 93
86ecall`,
87 },
88 {
89 slug: "mext",
90 name: "Multiply and divide",
91 words: []uint32{
92 0x3B9AD2B7, 0xA0728293, 0x00300313, 0x026282B3,
93 0x00700393, 0x0272D333, 0x05D00893, 0x00000073,
94 },
95 budget: 1000,
96 note: "The M extension. t0 = 1000000007 * 3 truncated to 32 bits, then t1 = t0 / 7. " +
97 "Division by zero and the one signed overflow case return values here " +
98 "rather than trapping, which is what the spec says and what a chain needs.",
99 asm: `lui t0, 0x3b9ad000 # t0 = 1000000007
100addi t0, t0, -1529
101addi t1, zero, 3
102mul t0, t0, t1
103addi t2, zero, 7
104divu t1, t0, t2
105addi a7, zero, 93
106ecall`,
107 },
108 {
109 slug: "clang",
110 name: "Compiled by clang",
111 input: "gno.land",
112 budget: 20000,
113 note: "Not written for gno and not written by hand: a freestanding C program, " +
114 "compiled by clang for riscv32im and shipped as the bytes LLVM emitted. It reads " +
115 "the call input, hashes it with FNV-1a and writes eight hex digits back. " +
116 "With the default input it prints fb7ffba0. Source and build command: " +
117 "tools/riscv-guests/fnv.",
118 builtin: "fnv",
119 asm: `long n = syscall3(SYS_READ, 0, (long)in, sizeof(in));
120
121unsigned int h = 2166136261u;
122for (long i = 0; i < n; i++) {
123 h ^= in[i];
124 h *= 16777619u; /* the reason this is rv32im */
125}
126
127for (int i = 0; i < 8; i++) {
128 unsigned int nib = (h >> (28 - i * 4)) & 0xF;
129 out[i] = nib < 10 ? '0' + nib : 'a' + (nib - 10);
130}
131
132syscall3(SYS_WRITE, 1, (long)out, 8);
133syscall3(SYS_EXIT, 0, 0, 0);`,
134 },
135 {
136 slug: "ledger",
137 name: "A token, in Rust",
138 input: "mint alice 100\nmint bob 50\nsend alice bob 30\n",
139 budget: 20000,
140 note: "no_std Rust, compiled by rustc for riscv32im: mint, send and burn, applied " +
141 "from the call input. It links Rust's real core and compiler_builtins, so the " +
142 "balances being 64-bit means a divide runs __udivdi3 here, because RV32 has no " +
143 "64-bit divide instruction. With the default input it prints alice 70 and bob 80. " +
144 "Source: tools/riscv-guests/ledger.",
145 builtin: "ledger",
146 asm: `match cmd {
147 b"send" => {
148 let (from, rest) = field(rest);
149 let (to, rest) = field(rest);
150 let (amount, _) = field(rest);
151 let n = parse_u32(amount).ok_or("bad amount")?;
152 let fi = led.find(from).ok_or("unknown sender")?;
153 if led.balances[fi] < n {
154 return Err("insufficient balance");
155 }
156 let ti = led.intern(to).ok_or("ledger full or bad name")?;
157 led.balances[fi] -= n;
158 led.balances[ti] += n;
159 Ok(())
160 }
161 _ => Err("unknown command"),
162}`,
163 },
164}
165
166// image is what gets uploaded: either the words assembled here, or a program
167// the library ships because a compiler produced it.
168func (s *sample) image() []byte {
169 switch s.builtin {
170 case "fnv":
171 return riscv.GuestFNV()
172 case "ledger":
173 return riscv.GuestLedger()
174 }
175 return riscv.Image(s.words)
176}
177
178// lang is the fence tag for what the sample shows. The compiled guest shows
179// its C, because showing a disassembly of what LLVM did with it would bury the
180// only interesting fact: nobody wrote the machine code.
181func (s *sample) lang() string {
182 switch s.builtin {
183 case "fnv":
184 return "c"
185 case "ledger":
186 return "rust"
187 }
188 return "asm"
189}
190
191func sampleByName(slug string) *sample {
192 for i := range samples {
193 if samples[i].slug == slug {
194 return &samples[i]
195 }
196 }
197 return nil
198}