wa-lang.org/wazero@v1.0.2/internal/wasm/memory.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "math" 8 "reflect" 9 "sync" 10 "unsafe" 11 12 "wa-lang.org/wazero/api" 13 ) 14 15 const ( 16 // MemoryPageSize is the unit of memory length in WebAssembly, 17 // and is defined as 2^16 = 65536. 18 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 19 MemoryPageSize = uint32(65536) 20 // MemoryLimitPages is maximum number of pages defined (2^16). 21 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem 22 MemoryLimitPages = uint32(65536) 23 // MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize". 24 MemoryPageSizeInBits = 16 25 ) 26 27 // compile-time check to ensure MemoryInstance implements api.Memory 28 var _ api.Memory = &MemoryInstance{} 29 30 // MemoryInstance represents a memory instance in a store, and implements api.Memory. 31 // 32 // Note: In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means the precise memory is always 33 // wasm.Store Memories index zero: `store.Memories[0]` 34 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0. 35 type MemoryInstance struct { 36 Buffer []byte 37 Min, Cap, Max uint32 38 // mux is used to prevent overlapping calls to Grow. 39 mux sync.RWMutex 40 // definition is known at compile time. 41 definition api.MemoryDefinition 42 } 43 44 // NewMemoryInstance creates a new instance based on the parameters in the SectionIDMemory. 45 func NewMemoryInstance(memSec *Memory) *MemoryInstance { 46 min := MemoryPagesToBytesNum(memSec.Min) 47 capacity := MemoryPagesToBytesNum(memSec.Cap) 48 return &MemoryInstance{ 49 Buffer: make([]byte, min, capacity), 50 Min: memSec.Min, 51 Cap: memSec.Cap, 52 Max: memSec.Max, 53 } 54 } 55 56 // Definition implements the same method as documented on api.Memory. 57 func (m *MemoryInstance) Definition() api.MemoryDefinition { 58 return m.definition 59 } 60 61 // Size implements the same method as documented on api.Memory. 62 func (m *MemoryInstance) Size(context.Context) uint32 { 63 return m.size() 64 } 65 66 // ReadByte implements the same method as documented on api.Memory. 67 func (m *MemoryInstance) ReadByte(_ context.Context, offset uint32) (byte, bool) { 68 if offset >= m.size() { 69 return 0, false 70 } 71 return m.Buffer[offset], true 72 } 73 74 // ReadUint16Le implements the same method as documented on api.Memory. 75 func (m *MemoryInstance) ReadUint16Le(_ context.Context, offset uint32) (uint16, bool) { 76 if !m.hasSize(offset, 2) { 77 return 0, false 78 } 79 return binary.LittleEndian.Uint16(m.Buffer[offset : offset+2]), true 80 } 81 82 // ReadUint32Le implements the same method as documented on api.Memory. 83 func (m *MemoryInstance) ReadUint32Le(_ context.Context, offset uint32) (uint32, bool) { 84 return m.readUint32Le(offset) 85 } 86 87 // ReadFloat32Le implements the same method as documented on api.Memory. 88 func (m *MemoryInstance) ReadFloat32Le(_ context.Context, offset uint32) (float32, bool) { 89 v, ok := m.readUint32Le(offset) 90 if !ok { 91 return 0, false 92 } 93 return math.Float32frombits(v), true 94 } 95 96 // ReadUint64Le implements the same method as documented on api.Memory. 97 func (m *MemoryInstance) ReadUint64Le(_ context.Context, offset uint32) (uint64, bool) { 98 return m.readUint64Le(offset) 99 } 100 101 // ReadFloat64Le implements the same method as documented on api.Memory. 102 func (m *MemoryInstance) ReadFloat64Le(_ context.Context, offset uint32) (float64, bool) { 103 v, ok := m.readUint64Le(offset) 104 if !ok { 105 return 0, false 106 } 107 return math.Float64frombits(v), true 108 } 109 110 // Read implements the same method as documented on api.Memory. 111 func (m *MemoryInstance) Read(_ context.Context, offset, byteCount uint32) ([]byte, bool) { 112 if !m.hasSize(offset, byteCount) { 113 return nil, false 114 } 115 return m.Buffer[offset : offset+byteCount : offset+byteCount], true 116 } 117 118 // WriteByte implements the same method as documented on api.Memory. 119 func (m *MemoryInstance) WriteByte(_ context.Context, offset uint32, v byte) bool { 120 if offset >= m.size() { 121 return false 122 } 123 m.Buffer[offset] = v 124 return true 125 } 126 127 // WriteUint16Le implements the same method as documented on api.Memory. 128 func (m *MemoryInstance) WriteUint16Le(_ context.Context, offset uint32, v uint16) bool { 129 if !m.hasSize(offset, 2) { 130 return false 131 } 132 binary.LittleEndian.PutUint16(m.Buffer[offset:], v) 133 return true 134 } 135 136 // WriteUint32Le implements the same method as documented on api.Memory. 137 func (m *MemoryInstance) WriteUint32Le(_ context.Context, offset, v uint32) bool { 138 return m.writeUint32Le(offset, v) 139 } 140 141 // WriteFloat32Le implements the same method as documented on api.Memory. 142 func (m *MemoryInstance) WriteFloat32Le(_ context.Context, offset uint32, v float32) bool { 143 return m.writeUint32Le(offset, math.Float32bits(v)) 144 } 145 146 // WriteUint64Le implements the same method as documented on api.Memory. 147 func (m *MemoryInstance) WriteUint64Le(_ context.Context, offset uint32, v uint64) bool { 148 return m.writeUint64Le(offset, v) 149 } 150 151 // WriteFloat64Le implements the same method as documented on api.Memory. 152 func (m *MemoryInstance) WriteFloat64Le(_ context.Context, offset uint32, v float64) bool { 153 return m.writeUint64Le(offset, math.Float64bits(v)) 154 } 155 156 // Write implements the same method as documented on api.Memory. 157 func (m *MemoryInstance) Write(_ context.Context, offset uint32, val []byte) bool { 158 if !m.hasSize(offset, uint32(len(val))) { 159 return false 160 } 161 copy(m.Buffer[offset:], val) 162 return true 163 } 164 165 // WriteString implements the same method as documented on api.Memory. 166 func (m *MemoryInstance) WriteString(_ context.Context, offset uint32, val string) bool { 167 if !m.hasSize(offset, uint32(len(val))) { 168 return false 169 } 170 copy(m.Buffer[offset:], val) 171 return true 172 } 173 174 // MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages. 175 func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) { 176 return uint64(pages) << MemoryPageSizeInBits 177 } 178 179 // Grow implements the same method as documented on api.Memory. 180 func (m *MemoryInstance) Grow(_ context.Context, delta uint32) (result uint32, ok bool) { 181 // We take write-lock here as the following might result in a new slice 182 m.mux.Lock() 183 defer m.mux.Unlock() 184 185 currentPages := memoryBytesNumToPages(uint64(len(m.Buffer))) 186 if delta == 0 { 187 return currentPages, true 188 } 189 190 // If exceeds the max of memory size, we push -1 according to the spec. 191 newPages := currentPages + delta 192 if newPages > m.Max { 193 return 0, false 194 } else if newPages > m.Cap { // grow the memory. 195 m.Buffer = append(m.Buffer, make([]byte, MemoryPagesToBytesNum(delta))...) 196 m.Cap = newPages 197 return currentPages, true 198 } else { // We already have the capacity we need. 199 sp := (*reflect.SliceHeader)(unsafe.Pointer(&m.Buffer)) 200 sp.Len = int(MemoryPagesToBytesNum(newPages)) 201 return currentPages, true 202 } 203 } 204 205 // PageSize returns the current memory buffer size in pages. 206 func (m *MemoryInstance) PageSize(context.Context) (result uint32) { 207 return memoryBytesNumToPages(uint64(len(m.Buffer))) 208 } 209 210 // PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. e.g. 1 -> "64Ki" 211 // 212 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0 213 func PagesToUnitOfBytes(pages uint32) string { 214 k := pages * 64 215 if k < 1024 { 216 return fmt.Sprintf("%d Ki", k) 217 } 218 m := k / 1024 219 if m < 1024 { 220 return fmt.Sprintf("%d Mi", m) 221 } 222 g := m / 1024 223 if g < 1024 { 224 return fmt.Sprintf("%d Gi", g) 225 } 226 return fmt.Sprintf("%d Ti", g/1024) 227 } 228 229 // Below are raw functions used to implement the api.Memory API: 230 231 // memoryBytesNumToPages converts the given number of bytes into the number of pages. 232 func memoryBytesNumToPages(bytesNum uint64) (pages uint32) { 233 return uint32(bytesNum >> MemoryPageSizeInBits) 234 } 235 236 // size returns the size in bytes of the buffer. 237 func (m *MemoryInstance) size() uint32 { 238 return uint32(len(m.Buffer)) // We don't lock here because size can't become smaller. 239 } 240 241 // hasSize returns true if Len is sufficient for byteCount at the given offset. 242 // 243 // Note: This is always fine, because memory can grow, but never shrink. 244 func (m *MemoryInstance) hasSize(offset uint32, byteCount uint32) bool { 245 return uint64(offset)+uint64(byteCount) <= uint64(len(m.Buffer)) // uint64 prevents overflow on add 246 } 247 248 // readUint32Le implements ReadUint32Le without using a context. This is extracted as both ints and floats are stored in 249 // memory as uint32le. 250 func (m *MemoryInstance) readUint32Le(offset uint32) (uint32, bool) { 251 if !m.hasSize(offset, 4) { 252 return 0, false 253 } 254 return binary.LittleEndian.Uint32(m.Buffer[offset : offset+4]), true 255 } 256 257 // readUint64Le implements ReadUint64Le without using a context. This is extracted as both ints and floats are stored in 258 // memory as uint64le. 259 func (m *MemoryInstance) readUint64Le(offset uint32) (uint64, bool) { 260 if !m.hasSize(offset, 8) { 261 return 0, false 262 } 263 return binary.LittleEndian.Uint64(m.Buffer[offset : offset+8]), true 264 } 265 266 // writeUint32Le implements WriteUint32Le without using a context. This is extracted as both ints and floats are stored 267 // in memory as uint32le. 268 func (m *MemoryInstance) writeUint32Le(offset uint32, v uint32) bool { 269 if !m.hasSize(offset, 4) { 270 return false 271 } 272 binary.LittleEndian.PutUint32(m.Buffer[offset:], v) 273 return true 274 } 275 276 // writeUint64Le implements WriteUint64Le without using a context. This is extracted as both ints and floats are stored 277 // in memory as uint64le. 278 func (m *MemoryInstance) writeUint64Le(offset uint32, v uint64) bool { 279 if !m.hasSize(offset, 8) { 280 return false 281 } 282 binary.LittleEndian.PutUint64(m.Buffer[offset:], v) 283 return true 284 }