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

render.gno

7.80 Kb · 244 lines
  1package riscvdemo
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/moul/kit/ui/v0"
  7	"gno.land/p/moul/x/vm/riscv/v0"
  8	"gno.land/p/moul/x/vm/vmkit/v0"
  9	"gno.land/p/nt/ufmt/v0"
 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/moul/x/vm/riscv/v0) (the hart) and ")
 37	sb.WriteString("[`p/moul/x/vm/vmkit`](/p/moul/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/moul/x/vm/riscvdemo/v0:"+i.ID+")",
 61			i.Status.String(),
 62			ufmt.Sprintf("%d", i.FuelUsed),
 63			ufmt.Sprintf("%d", i.Slices),
 64			"`"+ui.Cell(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/moul/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/moul/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}