github.com/tetratelabs/wazero@v1.2.1/internal/wasm/memory.go (about)

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