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  }