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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

8.37 Kb · 253 lines
  1package riscvdemo
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/nym-moultest123/kit/ui/v0"
  7	"gno.land/p/nym-moultest123/x/vm/riscv/v0"
  8	"gno.land/p/nym-moultest123/x/vm/vmkit/v0"
  9	"gno.land/p/nym-moultest123/vendor/nt/ufmt/v2"
 10)
 11
 12// regNames are the ABI names, because nobody reading a register dump thinks in
 13// x-numbers and every compiler's output is annotated with these.
 14var regNames = [32]string{
 15	"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
 16	"s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
 17	"a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
 18	"s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6",
 19}
 20
 21// Render is the realm's gnoweb view.
 22//
 23//   - "/"      the samples, the instance list, and what the numbers mean.
 24//   - "/<id>"  one instance: its status, fuel, output and register file.
 25func Render(path string) string {
 26	id := strings.TrimSpace(strings.TrimPrefix(path, "/"))
 27	if id == "" {
 28		return renderHome()
 29	}
 30	return renderInstance(id)
 31}
 32
 33func renderHome() string {
 34	var sb strings.Builder
 35	sb.WriteString("# RV32IM, on chain, a slice at a time\n\n")
 36	sb.WriteString("Demo of [`p/moul/x/vm/riscv`](/p/nym-moultest123/x/vm/riscv/v0) (the hart) and ")
 37	sb.WriteString("[`p/moul/x/vm/vmkit`](/p/nym-moultest123/x/vm/vmkit/v0) (the host ABI, the fuel meter, ")
 38	sb.WriteString("the instance store). This realm holds no logic of its own.\n\n")
 39	sb.WriteString("The programs below are flat `.text` images: the same bytes a cross compiler ")
 40	sb.WriteString("emits for `riscv32im-unknown-none-elf`. Nothing here was written in a ")
 41	sb.WriteString("chain language, and nothing translated it.\n\n")
 42	sb.WriteString("A program does not run to completion. It runs until its fuel slice is spent, ")
 43	sb.WriteString("then pauses: the realm keeps the snapshot and the next caller pays for the ")
 44	sb.WriteString("next slice. Realm code cannot pause itself, and a guest can.\n\n")
 45
 46	sb.WriteString("## Samples\n\n")
 47	for i := range samples {
 48		s := &samples[i]
 49		sb.WriteString("**" + ui.Inline(s.name) + "**: " + s.note + "\n\n")
 50		sb.WriteString("```" + s.lang() + "\n" + s.asm + "\n```\n\n")
 51		sb.WriteString(ui.Action("Upload "+s.name, "UploadSample",
 52			"name", s.slug, "input", s.input,
 53			"budget", ufmt.Sprintf("%d", s.budget)) + "\n\n")
 54	}
 55
 56	sb.WriteString("## Instances\n\n")
 57	t := ui.NewTable("id", "status", "instructions", "slices", "output")
 58	store.ReverseIterate(func(i *vmkit.Instance) bool {
 59		t.Row(
 60			"["+i.ID+"](/r/nym-moultest123/x/vm/riscvdemo/v0:"+i.ID+")",
 61			i.Status.String(),
 62			ufmt.Sprintf("%d", i.FuelUsed),
 63			ufmt.Sprintf("%d", i.Slices),
 64			codeCell(printable(i.Output, 24)),
 65		)
 66		return false
 67	})
 68	sb.WriteString(t.OrEmpty("No instances yet. Upload one of the samples above."))
 69	sb.WriteString("\n\n")
 70
 71	sb.WriteString("## What a slice costs\n\n")
 72	sb.WriteString("An RV32IM instruction costs about **8,500 gas** here, measured rather than ")
 73	sb.WriteString("estimated, so a block buys roughly **350,000 guest instructions**. Loading ")
 74	sb.WriteString("the image costs about **19,600 gas per instruction word** and is paid again ")
 75	sb.WriteString("on every resume, which is why the image cap below is small. The numbers and ")
 76	sb.WriteString("how they were taken are in the [library README](/p/nym-moultest123/x/vm/riscv/v0).\n\n")
 77
 78	sb.WriteString("## Limits\n\n")
 79	lt := ui.NewTable("limit", "value")
 80	lt.Row("instances", ufmt.Sprintf("%d", MaxInstances))
 81	lt.Row("image bytes", ufmt.Sprintf("%d", MaxImage))
 82	lt.Row("input bytes", ufmt.Sprintf("%d", MaxInput))
 83	lt.Row("output bytes", ufmt.Sprintf("%d", MaxOutput))
 84	lt.Row("instructions per slice", ufmt.Sprintf("%d", MaxSliceFuel))
 85	lt.Row("address space", ufmt.Sprintf("%d", riscv.MemSize))
 86	sb.WriteString(lt.String())
 87	return sb.String()
 88}
 89
 90func renderInstance(id string) string {
 91	var sb strings.Builder
 92	sb.WriteString("# Instance " + ui.Inline(id) + "\n\n")
 93	sb.WriteString("[← all instances](/r/nym-moultest123/x/vm/riscvdemo/v0)\n\n")
 94
 95	inst := store.Get(id)
 96	if inst == nil {
 97		sb.WriteString("**No such instance.** It was never uploaded, or its owner removed it.\n")
 98		return sb.String()
 99	}
100
101	t := ui.NewTable("field", "value")
102	t.Row("owner", ui.Addr(inst.Owner))
103	t.Row("vm", ui.Cell(inst.VM))
104	t.Row("status", inst.Status.String())
105	if inst.Trap != "" {
106		t.Row("trap", ui.Cell(inst.Trap))
107	}
108	t.Row("instructions run", ufmt.Sprintf("%d", inst.FuelUsed))
109	t.Row("budget", fuelText(inst.FuelBudget))
110	t.Row("slices", ufmt.Sprintf("%d", inst.Slices))
111	t.Row("image bytes", ufmt.Sprintf("%d", len(inst.Program)))
112	t.Row("image words", ufmt.Sprintf("%d", len(inst.Program)/4))
113	t.Row("snapshot bytes", ufmt.Sprintf("%d", len(inst.Snapshot)))
114	sb.WriteString(t.String())
115	sb.WriteString("\n")
116
117	sb.WriteString("## Registers\n\n")
118	sb.WriteString(renderRegisters(inst))
119
120	sb.WriteString("## Output\n\n")
121	if len(inst.Output) == 0 {
122		sb.WriteString(ui.Empty("Nothing written yet."))
123	} else {
124		sb.WriteString("```\n" + printable(inst.Output, MaxOutput) + "\n```\n")
125	}
126	sb.WriteString("\n")
127
128	sb.WriteString("## Image\n\n```\n" + hexDump(inst.Program, 64) + "\n```\n\n")
129
130	if inst.Status == vmkit.Running {
131		sb.WriteString(ui.Action("Run 25000 instructions", "Step", "id", id, "fuel", "25000"))
132		sb.WriteString(" · ")
133		sb.WriteString(ui.Action("Run 100000 instructions", "Step", "id", id, "fuel", "100000"))
134		sb.WriteString("\n")
135	} else {
136		sb.WriteString("This instance is **" + inst.Status.String() + "** and cannot be stepped again.\n")
137	}
138	return sb.String()
139}
140
141// renderRegisters restores the snapshot to read the register file out of it.
142// The hart is not kept between transactions, only its snapshot is, so this is
143// the only place the state is legible, and it is the thing worth seeing: a
144// program that wrote nothing still computed something.
145func renderRegisters(inst *vmkit.Instance) string {
146	if len(inst.Snapshot) == 0 {
147		return ui.Empty("Not started yet: no snapshot to read.") + "\n\n"
148	}
149	var m riscv.Machine
150	if err := m.Restore(inst.Snapshot); err != nil {
151		return ui.Empty("The snapshot could not be read.") + "\n\n"
152	}
153	reg := m.Registers()
154
155	var sb strings.Builder
156	t := ui.NewTable("register", "hex", "unsigned", "signed")
157	t.Row("pc", ufmt.Sprintf("0x%08X", m.PC()), "", "")
158	shown := 0
159	for i := 1; i < 32; i++ {
160		if reg[i] == 0 {
161			continue
162		}
163		t.Row(
164			regNames[i],
165			ufmt.Sprintf("0x%08X", reg[i]),
166			ufmt.Sprintf("%d", reg[i]),
167			ufmt.Sprintf("%d", int64(int32(reg[i]))),
168		)
169		shown++
170	}
171	sb.WriteString(t.String())
172	if shown == 0 {
173		sb.WriteString("\nEvery register except `pc` is zero.\n")
174	}
175	sb.WriteString("\n")
176	return sb.String()
177}
178
179func fuelText(n int64) string {
180	if n == vmkit.Unmetered {
181		return "unmetered"
182	}
183	return ufmt.Sprintf("%d", n)
184}
185
186const hexDigits = "0123456789abcdef"
187
188// hexDump shows the image as words, which is how anyone reading RV32 reads it.
189func hexDump(b []byte, max int) string {
190	truncated := false
191	if len(b) > max {
192		b, truncated = b[:max], true
193	}
194	var sb strings.Builder
195	for i := 0; i+3 < len(b); i += 4 {
196		if i > 0 && i%16 == 0 {
197			sb.WriteString("\n")
198		} else if i > 0 {
199			sb.WriteString(" ")
200		}
201		w := uint32(b[i]) | uint32(b[i+1])<<8 | uint32(b[i+2])<<16 | uint32(b[i+3])<<24
202		sb.WriteString(ufmt.Sprintf("%08x", w))
203	}
204	if truncated {
205		sb.WriteString("\n...")
206	}
207	return sb.String()
208}
209
210// printable renders guest output for a code fence: printable ASCII as itself,
211// everything else as an escape. A guest writes arbitrary bytes, so this is the
212// only form of it that is safe to show.
213func printable(b []byte, max int) string {
214	if len(b) == 0 {
215		return ""
216	}
217	truncated := false
218	if len(b) > max {
219		b, truncated = b[:max], true
220	}
221	var sb strings.Builder
222	for _, c := range b {
223		switch {
224		case c == '\n':
225			sb.WriteString("\\n")
226		case c == '\t':
227			sb.WriteString("\\t")
228		case c == '\\':
229			sb.WriteString("\\\\")
230		case c == '`':
231			sb.WriteString("\\x60")
232		case c >= 0x20 && c < 0x7f:
233			sb.WriteByte(c)
234		default:
235			sb.WriteString("\\x")
236			sb.WriteByte(hexDigits[c>>4])
237			sb.WriteByte(hexDigits[c&0x0f])
238		}
239	}
240	if truncated {
241		sb.WriteString("…")
242	}
243	return sb.String()
244}
245
246// codeCell puts already-printable text in a code span inside a table cell.
247// Inside backticks markdown escapes are not applied, so ui.Cell's backslashes
248// would reach the reader verbatim ("Hello, gno\.land"); the one character a
249// table cell still needs escaped there is the pipe. printable has already
250// removed the backtick and the newline, the other two that could break out.
251func codeCell(s string) string {
252	return "`" + strings.ReplaceAll(s, "|", "\\|") + "`"
253}