github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/wasm/memory.go (about)

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