package wasm
import (
"container/list"
"encoding/binary"
"fmt"
"math"
"reflect"
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/internalapi"
"github.com/tetratelabs/wazero/internal/wasmruntime"
)
const (
MemoryPageSize = uint32 (65536 )
MemoryLimitPages = uint32 (65536 )
MemoryPageSizeInBits = 16
)
var _ api .Memory = &MemoryInstance {}
type waiters struct {
mux sync .Mutex
l *list .List
}
type MemoryInstance struct {
internalapi .WazeroOnlyType
Buffer []byte
Min, Cap, Max uint32
Shared bool
definition api .MemoryDefinition
Mux sync .Mutex
waiters sync .Map
ownerModuleEngine ModuleEngine
expBuffer experimental .LinearMemory
}
func NewMemoryInstance (memSec *Memory , allocator experimental .MemoryAllocator , moduleEngine ModuleEngine ) *MemoryInstance {
minBytes := MemoryPagesToBytesNum (memSec .Min )
capBytes := MemoryPagesToBytesNum (memSec .Cap )
maxBytes := MemoryPagesToBytesNum (memSec .Max )
var buffer []byte
var expBuffer experimental .LinearMemory
if allocator != nil {
expBuffer = allocator .Allocate (capBytes , maxBytes )
buffer = expBuffer .Reallocate (minBytes )
_ = buffer [:minBytes ]
} else if memSec .IsShared {
buffer = make ([]byte , minBytes , maxBytes )
} else {
buffer = make ([]byte , minBytes , capBytes )
}
return &MemoryInstance {
Buffer : buffer ,
Min : memSec .Min ,
Cap : memoryBytesNumToPages (uint64 (cap (buffer ))),
Max : memSec .Max ,
Shared : memSec .IsShared ,
expBuffer : expBuffer ,
ownerModuleEngine : moduleEngine ,
}
}
func (m *MemoryInstance ) Definition () api .MemoryDefinition {
return m .definition
}
func (m *MemoryInstance ) Size () uint32 {
return uint32 (len (m .Buffer ))
}
func (m *MemoryInstance ) ReadByte (offset uint32 ) (byte , bool ) {
if !m .hasSize (offset , 1 ) {
return 0 , false
}
return m .Buffer [offset ], true
}
func (m *MemoryInstance ) ReadUint16Le (offset uint32 ) (uint16 , bool ) {
if !m .hasSize (offset , 2 ) {
return 0 , false
}
return binary .LittleEndian .Uint16 (m .Buffer [offset : offset +2 ]), true
}
func (m *MemoryInstance ) ReadUint32Le (offset uint32 ) (uint32 , bool ) {
return m .readUint32Le (offset )
}
func (m *MemoryInstance ) ReadFloat32Le (offset uint32 ) (float32 , bool ) {
v , ok := m .readUint32Le (offset )
if !ok {
return 0 , false
}
return math .Float32frombits (v ), true
}
func (m *MemoryInstance ) ReadUint64Le (offset uint32 ) (uint64 , bool ) {
return m .readUint64Le (offset )
}
func (m *MemoryInstance ) ReadFloat64Le (offset uint32 ) (float64 , bool ) {
v , ok := m .readUint64Le (offset )
if !ok {
return 0 , false
}
return math .Float64frombits (v ), true
}
func (m *MemoryInstance ) Read (offset , byteCount uint32 ) ([]byte , bool ) {
if !m .hasSize (offset , uint64 (byteCount )) {
return nil , false
}
return m .Buffer [offset : offset +byteCount : offset +byteCount ], true
}
func (m *MemoryInstance ) WriteByte (offset uint32 , v byte ) bool {
if !m .hasSize (offset , 1 ) {
return false
}
m .Buffer [offset ] = v
return true
}
func (m *MemoryInstance ) WriteUint16Le (offset uint32 , v uint16 ) bool {
if !m .hasSize (offset , 2 ) {
return false
}
binary .LittleEndian .PutUint16 (m .Buffer [offset :], v )
return true
}
func (m *MemoryInstance ) WriteUint32Le (offset , v uint32 ) bool {
return m .writeUint32Le (offset , v )
}
func (m *MemoryInstance ) WriteFloat32Le (offset uint32 , v float32 ) bool {
return m .writeUint32Le (offset , math .Float32bits (v ))
}
func (m *MemoryInstance ) WriteUint64Le (offset uint32 , v uint64 ) bool {
return m .writeUint64Le (offset , v )
}
func (m *MemoryInstance ) WriteFloat64Le (offset uint32 , v float64 ) bool {
return m .writeUint64Le (offset , math .Float64bits (v ))
}
func (m *MemoryInstance ) Write (offset uint32 , val []byte ) bool {
if !m .hasSize (offset , uint64 (len (val ))) {
return false
}
copy (m .Buffer [offset :], val )
return true
}
func (m *MemoryInstance ) WriteString (offset uint32 , val string ) bool {
if !m .hasSize (offset , uint64 (len (val ))) {
return false
}
copy (m .Buffer [offset :], val )
return true
}
func MemoryPagesToBytesNum (pages uint32 ) (bytesNum uint64 ) {
return uint64 (pages ) << MemoryPageSizeInBits
}
func (m *MemoryInstance ) Grow (delta uint32 ) (result uint32 , ok bool ) {
if m .Shared {
m .Mux .Lock ()
defer m .Mux .Unlock ()
}
currentPages := m .Pages ()
if delta == 0 {
return currentPages , true
}
newPages := currentPages + delta
if newPages > m .Max || int32 (delta ) < 0 {
return 0 , false
} else if m .expBuffer != nil {
buffer := m .expBuffer .Reallocate (MemoryPagesToBytesNum (newPages ))
if buffer == nil {
return 0 , false
}
if m .Shared {
if unsafe .SliceData (buffer ) != unsafe .SliceData (m .Buffer ) {
panic ("shared memory cannot move, this is a bug in the memory allocator" )
}
atomicStoreLengthAndCap (&m .Buffer , uintptr (len (buffer )), uintptr (cap (buffer )))
m .Cap = memoryBytesNumToPages (uint64 (cap (buffer )))
} else {
m .Buffer = buffer
m .Cap = newPages
}
} else if newPages > m .Cap {
if m .Shared {
panic ("shared memory cannot be grown, this is a bug in wazero" )
}
m .Buffer = append (m .Buffer , make ([]byte , MemoryPagesToBytesNum (delta ))...)
m .Cap = newPages
} else {
if m .Shared {
atomicStoreLength (&m .Buffer , uintptr (MemoryPagesToBytesNum (newPages )))
} else {
m .Buffer = m .Buffer [:MemoryPagesToBytesNum (newPages )]
}
}
m .ownerModuleEngine .MemoryGrown ()
return currentPages , true
}
func (m *MemoryInstance ) Pages () (result uint32 ) {
return memoryBytesNumToPages (uint64 (len (m .Buffer )))
}
func PagesToUnitOfBytes (pages uint32 ) string {
k := pages * 64
if k < 1024 {
return fmt .Sprintf ("%d Ki" , k )
}
m := k / 1024
if m < 1024 {
return fmt .Sprintf ("%d Mi" , m )
}
g := m / 1024
if g < 1024 {
return fmt .Sprintf ("%d Gi" , g )
}
return fmt .Sprintf ("%d Ti" , g /1024 )
}
func atomicStoreLengthAndCap(slice *[]byte , length uintptr , cap uintptr ) {
slicePtr := (*reflect .SliceHeader )(unsafe .Pointer (slice ))
capPtr := (*uintptr )(unsafe .Pointer (&slicePtr .Cap ))
atomic .StoreUintptr (capPtr , cap )
lenPtr := (*uintptr )(unsafe .Pointer (&slicePtr .Len ))
atomic .StoreUintptr (lenPtr , length )
}
func atomicStoreLength(slice *[]byte , length uintptr ) {
slicePtr := (*reflect .SliceHeader )(unsafe .Pointer (slice ))
lenPtr := (*uintptr )(unsafe .Pointer (&slicePtr .Len ))
atomic .StoreUintptr (lenPtr , length )
}
func memoryBytesNumToPages(bytesNum uint64 ) (pages uint32 ) {
return uint32 (bytesNum >> MemoryPageSizeInBits )
}
func (m *MemoryInstance ) hasSize (offset uint32 , byteCount uint64 ) bool {
return uint64 (offset )+byteCount <= uint64 (len (m .Buffer ))
}
func (m *MemoryInstance ) readUint32Le (offset uint32 ) (uint32 , bool ) {
if !m .hasSize (offset , 4 ) {
return 0 , false
}
return binary .LittleEndian .Uint32 (m .Buffer [offset : offset +4 ]), true
}
func (m *MemoryInstance ) readUint64Le (offset uint32 ) (uint64 , bool ) {
if !m .hasSize (offset , 8 ) {
return 0 , false
}
return binary .LittleEndian .Uint64 (m .Buffer [offset : offset +8 ]), true
}
func (m *MemoryInstance ) writeUint32Le (offset uint32 , v uint32 ) bool {
if !m .hasSize (offset , 4 ) {
return false
}
binary .LittleEndian .PutUint32 (m .Buffer [offset :], v )
return true
}
func (m *MemoryInstance ) writeUint64Le (offset uint32 , v uint64 ) bool {
if !m .hasSize (offset , 8 ) {
return false
}
binary .LittleEndian .PutUint64 (m .Buffer [offset :], v )
return true
}
func (m *MemoryInstance ) Wait32 (offset uint32 , exp uint32 , timeout int64 , reader func (mem *MemoryInstance , offset uint32 ) uint32 ) uint64 {
w := m .getWaiters (offset )
w .mux .Lock ()
cur := reader (m , offset )
if cur != exp {
w .mux .Unlock ()
return 1
}
return m .wait (w , timeout )
}
func (m *MemoryInstance ) Wait64 (offset uint32 , exp uint64 , timeout int64 , reader func (mem *MemoryInstance , offset uint32 ) uint64 ) uint64 {
w := m .getWaiters (offset )
w .mux .Lock ()
cur := reader (m , offset )
if cur != exp {
w .mux .Unlock ()
return 1
}
return m .wait (w , timeout )
}
func (m *MemoryInstance ) wait (w *waiters , timeout int64 ) uint64 {
if w .l == nil {
w .l = list .New ()
}
if uint64 (w .l .Len ()+1 ) == 1 <<32 {
w .mux .Unlock ()
panic (wasmruntime .ErrRuntimeTooManyWaiters )
}
ready := make (chan struct {})
elem := w .l .PushBack (ready )
w .mux .Unlock ()
if timeout < 0 {
<-ready
return 0
} else {
select {
case <- ready :
return 0
case <- time .After (time .Duration (timeout )):
w .mux .Lock ()
w .l .Remove (elem )
w .mux .Unlock ()
return 2
}
}
}
func (m *MemoryInstance ) getWaiters (offset uint32 ) *waiters {
wAny , ok := m .waiters .Load (offset )
if !ok {
wAny , _ = m .waiters .LoadOrStore (offset , &waiters {})
}
return wAny .(*waiters )
}
func (m *MemoryInstance ) Notify (offset uint32 , count uint32 ) uint32 {
wAny , ok := m .waiters .Load (offset )
if !ok {
return 0
}
w := wAny .(*waiters )
w .mux .Lock ()
defer w .mux .Unlock ()
if w .l == nil {
return 0
}
res := uint32 (0 )
for num := w .l .Len (); num > 0 && res < count ; num = w .l .Len () {
w := w .l .Remove (w .l .Front ()).(chan struct {})
close (w )
res ++
}
return res
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .