snapshot.gno
2.59 Kb · 83 lines
1package riscv
2
3import "gno.land/p/moul/x/vm/vmkit/v0"
4
5// Snapshot serializes the hart: registers, pc, status, and only the memory
6// pages the guest has written.
7//
8// A 1 MiB address space serialized on every pause would make continuations
9// cost more than re-running, which is exactly the kill criterion vmkit set for
10// them. The dirty bitmap is what avoids it: a guest that has touched two pages
11// pauses in about 8 KiB regardless of how much memory it was given.
12func (m *Machine) Snapshot() []byte {
13 if m.code == nil {
14 m.code = &code{}
15 }
16 w := vmkit.NewWriter(4096)
17 w.Uint32(snapMagic)
18 w.Byte(snapVersion)
19 for i := 0; i < 32; i++ {
20 w.Uint32(m.reg[i])
21 }
22 w.Uint32(m.pc)
23 w.Byte(byte(m.status))
24 w.String(m.trap)
25 w.Int(int64(m.outLen))
26 // The text segment's extent, so a restored hart knows which of its pages
27 // are code and can predecode them again. The code itself is not written:
28 // it is already in the memory pages, which WriteImage marked dirty.
29 w.Uint32(m.code.base)
30 w.Uint32(m.code.words)
31 m.mem.Snapshot(w)
32 return w.Out()
33}
34
35// Restore loads a snapshot. The program image is not carried separately: it
36// lives in the memory pages, which were marked dirty when it was written, so a
37// restored hart has its code without the instance storing it twice.
38func (m *Machine) Restore(b []byte) error {
39 r := vmkit.NewReader(b)
40 if r.Uint32() != snapMagic {
41 return vmkit.ErrBadSnapshot
42 }
43 if r.Byte() != snapVersion {
44 return vmkit.ErrBadSnapshot
45 }
46 var reg [32]uint32
47 for i := 0; i < 32; i++ {
48 reg[i] = r.Uint32()
49 }
50 pc := r.Uint32()
51 status := vmkit.Status(r.Byte())
52 trap := r.String()
53 outLen := int(r.Int())
54 base := r.Uint32()
55 words := r.Uint32()
56 if err := r.Err(); err != nil {
57 return err
58 }
59 // A text segment that does not fit in the address space is a snapshot this
60 // machine did not write, and predecoding it would index past the memory.
61 if base%4 != 0 || uint64(base)+uint64(words)*4 > uint64(MemSize) {
62 return vmkit.ErrBadSnapshot
63 }
64
65 mem := NewMemory()
66 if err := mem.Restore(r); err != nil {
67 return err
68 }
69 if err := r.Err(); err != nil {
70 return err
71 }
72
73 m.reg, m.pc, m.status, m.trap, m.outLen, m.mem = reg, pc, status, trap, outLen, mem
74 // Predecode again from the restored pages. This is the work a resume pays
75 // for, once, in exchange for not paying a fetch on every instruction after
76 // it, and it is why stores into the text segment trap: the arrays and the
77 // memory would otherwise be free to disagree.
78 m.code = predecode(base, mem.b[base:base+words*4])
79 // x0 is hardwired, and a snapshot that claims otherwise is not one this
80 // machine wrote.
81 m.reg[0] = 0
82 return nil
83}