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.

mem.gno

4.66 Kb · 169 lines
  1package riscv
  2
  3import "gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/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}