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

mem.gno

4.62 Kb · 169 lines
  1package riscv
  2
  3import "gno.land/p/moul/x/vm/vmkit/v0"
  4
  5// Memory is the guest's flat address space, with a snapshot that carries only
  6// the pages it actually touched.
  7//
  8// The shape is the one the bf ladder argued for. Running memory is a flat
  9// []byte because a load or a store happens on a large fraction of instructions
 10// and an avl lookup per access would dominate the interpreter the way
 11// vmkit.Meter.Charge did there. Snapshot cost is solved separately, by a dirty
 12// bitmap: a program that writes one page pauses in one page, not in a megabyte,
 13// which is the same trick that lets a bf hello world pause in 43 bytes.
 14//
 15// That answers the open question on the design issue ("memory size versus
 16// snapshot cost") without paying for it on every instruction.
 17const (
 18	// PageSize is the snapshot granularity, not an MMU page: there is no
 19	// address translation here, only bookkeeping about what changed.
 20	PageSize = 4096
 21
 22	// MemSize is the address space. 1 MiB is far more than a no_std guest
 23	// needs and small enough that the flat allocation is not the cost.
 24	MemSize = 1 << 20
 25
 26	pageCount = MemSize / PageSize
 27)
 28
 29// Memory is a flat address space plus the record of which pages were written.
 30type Memory struct {
 31	b     []byte
 32	dirty []bool
 33}
 34
 35// NewMemory returns a zeroed address space.
 36func NewMemory() *Memory {
 37	return &Memory{b: make([]byte, MemSize), dirty: make([]bool, pageCount)}
 38}
 39
 40// Size returns the address space size.
 41func (m *Memory) Size() int { return len(m.b) }
 42
 43// DirtyPages reports how many pages the guest has written, which is what a
 44// snapshot costs.
 45func (m *Memory) DirtyPages() int {
 46	n := 0
 47	for _, d := range m.dirty {
 48		if d {
 49			n++
 50		}
 51	}
 52	return n
 53}
 54
 55// inRange reports whether [addr, addr+size) is inside the address space.
 56//
 57// Out of range is a trap rather than a wrap. RISC-V leaves the behaviour of an
 58// access outside physical memory to the platform, and a chain has to pick the
 59// one that cannot differ between nodes: refusing is deterministic, wrapping
 60// invites a guest to alias two addresses and get different answers from
 61// different memory sizes.
 62func (m *Memory) inRange(addr uint32, size int) bool {
 63	end := uint64(addr) + uint64(size)
 64	return end <= uint64(len(m.b))
 65}
 66
 67// Load8, Load16 and Load32 read little-endian, which is what RV32 is.
 68func (m *Memory) Load8(addr uint32) (uint8, bool) {
 69	if !m.inRange(addr, 1) {
 70		return 0, false
 71	}
 72	return m.b[addr], true
 73}
 74
 75func (m *Memory) Load16(addr uint32) (uint16, bool) {
 76	if !m.inRange(addr, 2) {
 77		return 0, false
 78	}
 79	return uint16(m.b[addr]) | uint16(m.b[addr+1])<<8, true
 80}
 81
 82func (m *Memory) Load32(addr uint32) (uint32, bool) {
 83	if !m.inRange(addr, 4) {
 84		return 0, false
 85	}
 86	return uint32(m.b[addr]) | uint32(m.b[addr+1])<<8 |
 87		uint32(m.b[addr+2])<<16 | uint32(m.b[addr+3])<<24, true
 88}
 89
 90func (m *Memory) Store8(addr uint32, v uint8) bool {
 91	if !m.inRange(addr, 1) {
 92		return false
 93	}
 94	m.b[addr] = v
 95	m.dirty[addr/PageSize] = true
 96	return true
 97}
 98
 99func (m *Memory) Store16(addr uint32, v uint16) bool {
100	if !m.inRange(addr, 2) {
101		return false
102	}
103	m.b[addr] = uint8(v)
104	m.b[addr+1] = uint8(v >> 8)
105	m.dirty[addr/PageSize] = true
106	m.dirty[(addr+1)/PageSize] = true
107	return true
108}
109
110func (m *Memory) Store32(addr uint32, v uint32) bool {
111	if !m.inRange(addr, 4) {
112		return false
113	}
114	m.b[addr] = uint8(v)
115	m.b[addr+1] = uint8(v >> 8)
116	m.b[addr+2] = uint8(v >> 16)
117	m.b[addr+3] = uint8(v >> 24)
118	m.dirty[addr/PageSize] = true
119	m.dirty[(addr+3)/PageSize] = true
120	return true
121}
122
123// WriteImage places a program image at addr and marks the pages it covers, so
124// a snapshot taken before the first store still carries the program.
125func (m *Memory) WriteImage(addr uint32, img []byte) bool {
126	if !m.inRange(addr, len(img)) {
127		return false
128	}
129	copy(m.b[addr:], img)
130	for i := 0; i < len(img); i++ {
131		m.dirty[(uint64(addr)+uint64(i))/PageSize] = true
132	}
133	return true
134}
135
136// Snapshot writes only the dirty pages, each prefixed by its index.
137func (m *Memory) Snapshot(w *vmkit.Writer) {
138	w.Uint32(uint32(m.DirtyPages()))
139	for i, d := range m.dirty {
140		if !d {
141			continue
142		}
143		w.Uint32(uint32(i))
144		w.Bytes(m.b[i*PageSize : (i+1)*PageSize])
145	}
146}
147
148// Restore reads a snapshot written by Snapshot into a zeroed address space.
149func (m *Memory) Restore(r *vmkit.Reader) error {
150	m.b = make([]byte, MemSize)
151	m.dirty = make([]bool, pageCount)
152	n := int(r.Uint32())
153	if n < 0 || n > pageCount {
154		return vmkit.ErrBadSnapshot
155	}
156	for i := 0; i < n; i++ {
157		idx := int(r.Uint32())
158		page := r.Bytes()
159		if err := r.Err(); err != nil {
160			return err
161		}
162		if idx < 0 || idx >= pageCount || len(page) != PageSize {
163			return vmkit.ErrBadSnapshot
164		}
165		copy(m.b[idx*PageSize:], page)
166		m.dirty[idx] = true
167	}
168	return r.Err()
169}