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