package wazevo
import (
"bytes"
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"hash/crc32"
"io"
"unsafe"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
"github.com/tetratelabs/wazero/internal/filecache"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/u32"
"github.com/tetratelabs/wazero/internal/u64"
"github.com/tetratelabs/wazero/internal/wasm"
)
var crc = crc32 .MakeTable (crc32 .Castagnoli )
func fileCacheKey(m *wasm .Module ) (ret filecache .Key ) {
s := sha256 .New ()
s .Write (m .ID [:])
s .Write (magic )
cpu := platform .CpuFeatures .Raw ()
binary .LittleEndian .PutUint64 (ret [:8 ], cpu )
s .Write (ret [:8 ])
s .Sum (ret [:0 ])
return
}
func (e *engine ) addCompiledModule (module *wasm .Module , cm *compiledModule ) (c *compiledModule , err error ) {
c = e .addCompiledModuleToMemory (module , cm )
if !module .IsHostModule && e .fileCache != nil {
err = e .addCompiledModuleToCache (module , c )
}
return
}
func (e *engine ) getCompiledModule (module *wasm .Module , listeners []experimental .FunctionListener , ensureTermination bool ) (cm *compiledModule , ok bool , err error ) {
cm , ok = e .getCompiledModuleFromMemory (module , true )
if ok {
return
}
cm , ok , err = e .getCompiledModuleFromCache (module )
if ok {
cm .parent = e
cm .module = module
cm .sharedFunctions = e .sharedFunctions
cm .ensureTermination = ensureTermination
cm .offsets = wazevoapi .NewModuleContextOffsetData (module , len (listeners ) > 0 )
if len (listeners ) > 0 {
cm .listeners = listeners
cm .listenerBeforeTrampolines = make ([]*byte , len (module .TypeSection ))
cm .listenerAfterTrampolines = make ([]*byte , len (module .TypeSection ))
for i := range module .TypeSection {
typ := &module .TypeSection [i ]
before , after := e .getListenerTrampolineForType (typ )
cm .listenerBeforeTrampolines [i ] = before
cm .listenerAfterTrampolines [i ] = after
}
}
e .addCompiledModuleToMemory (module , cm )
ssaBuilder := ssa .NewBuilder ()
machine := newMachine ()
be := backend .NewCompiler (context .Background (), machine , ssaBuilder )
cm .executables .compileEntryPreambles (module , machine , be )
e .setFinalizer (cm .executables , executablesFinalizer )
}
return
}
func (e *engine ) addCompiledModuleToMemory (m *wasm .Module , cm *compiledModule ) *compiledModule {
e .mux .Lock ()
defer e .mux .Unlock ()
if c , ok := e .compiledModules [m .ID ]; ok {
c .refCount ++
return c .compiledModule
}
e .compiledModules [m .ID ] = &compiledModuleWithCount {compiledModule : cm , refCount : 1 }
if len (cm .executable ) > 0 {
e .addCompiledModuleToSortedList (cm )
}
return cm
}
func (e *engine ) getCompiledModuleFromMemory (module *wasm .Module , increaseRefCount bool ) (cm *compiledModule , ok bool ) {
e .mux .Lock ()
defer e .mux .Unlock ()
cmWithCount , ok := e .compiledModules [module .ID ]
if ok {
cm = cmWithCount .compiledModule
if increaseRefCount {
cmWithCount .refCount ++
}
}
return
}
func (e *engine ) addCompiledModuleToCache (module *wasm .Module , cm *compiledModule ) (err error ) {
if e .fileCache == nil || module .IsHostModule {
return
}
err = e .fileCache .Add (fileCacheKey (module ), serializeCompiledModule (e .wazeroVersion , cm ))
return
}
func (e *engine ) getCompiledModuleFromCache (module *wasm .Module ) (cm *compiledModule , hit bool , err error ) {
if e .fileCache == nil || module .IsHostModule {
return
}
var cached io .ReadCloser
cached , hit , err = e .fileCache .Get (fileCacheKey (module ))
if !hit || err != nil {
return
}
var staleCache bool
cm , staleCache , err = deserializeCompiledModule (e .wazeroVersion , cached )
if err != nil {
hit = false
return
} else if staleCache {
return nil , false , e .fileCache .Delete (fileCacheKey (module ))
}
return
}
var magic = []byte {'W' , 'A' , 'Z' , 'E' , 'V' , 'O' }
func serializeCompiledModule(wazeroVersion string , cm *compiledModule ) io .Reader {
buf := bytes .NewBuffer (nil )
buf .Write (magic )
buf .WriteByte (byte (len (wazeroVersion )))
buf .WriteString (wazeroVersion )
buf .Write (u32 .LeBytes (uint32 (len (cm .functionOffsets ))))
for _ , offset := range cm .functionOffsets {
buf .Write (u64 .LeBytes (uint64 (offset )))
}
buf .Write (u64 .LeBytes (uint64 (len (cm .executable ))))
buf .Write (cm .executable )
checksum := crc32 .Checksum (cm .executable , crc )
buf .Write (u32 .LeBytes (checksum ))
if sm := cm .sourceMap ; len (sm .executableOffsets ) > 0 {
buf .WriteByte (1 )
l := len (sm .wasmBinaryOffsets )
buf .Write (u64 .LeBytes (uint64 (l )))
executableAddr := uintptr (unsafe .Pointer (&cm .executable [0 ]))
for i := 0 ; i < l ; i ++ {
buf .Write (u64 .LeBytes (sm .wasmBinaryOffsets [i ]))
buf .Write (u64 .LeBytes (uint64 (sm .executableOffsets [i ] - executableAddr )))
}
} else {
buf .WriteByte (0 )
}
return bytes .NewReader (buf .Bytes ())
}
func deserializeCompiledModule(wazeroVersion string , reader io .ReadCloser ) (cm *compiledModule , staleCache bool , err error ) {
defer reader .Close ()
cacheHeaderSize := len (magic ) + 1 + len (wazeroVersion ) + 4
header := make ([]byte , cacheHeaderSize )
n , err := reader .Read (header )
if err != nil {
return nil , false , fmt .Errorf ("compilationcache: error reading header: %v" , err )
}
if n != cacheHeaderSize {
return nil , false , fmt .Errorf ("compilationcache: invalid header length: %d" , n )
}
if !bytes .Equal (header [:len (magic )], magic ) {
return nil , false , fmt .Errorf (
"compilationcache: invalid magic number: got %s but want %s" , magic , header [:len (magic )])
}
versionSize := int (header [len (magic )])
cachedVersionBegin , cachedVersionEnd := len (magic )+1 , len (magic )+1 +versionSize
if cachedVersionEnd >= len (header ) {
staleCache = true
return
} else if cachedVersion := string (header [cachedVersionBegin :cachedVersionEnd ]); cachedVersion != wazeroVersion {
staleCache = true
return
}
functionsNum := binary .LittleEndian .Uint32 (header [len (header )-4 :])
cm = &compiledModule {functionOffsets : make ([]int , functionsNum ), executables : &executables {}}
var eightBytes [8 ]byte
for i := uint32 (0 ); i < functionsNum ; i ++ {
var offset uint64
if offset , err = readUint64 (reader , &eightBytes ); err != nil {
err = fmt .Errorf ("compilationcache: error reading func[%d] executable offset: %v" , i , err )
return
}
cm .functionOffsets [i ] = int (offset )
}
executableLen , err := readUint64 (reader , &eightBytes )
if err != nil {
err = fmt .Errorf ("compilationcache: error reading executable size: %v" , err )
return
}
if executableLen > 0 {
executable , err := platform .MmapCodeSegment (int (executableLen ))
if err != nil {
err = fmt .Errorf ("compilationcache: error mmapping executable (len=%d): %v" , executableLen , err )
return nil , false , err
}
_, err = io .ReadFull (reader , executable )
if err != nil {
err = fmt .Errorf ("compilationcache: error reading executable (len=%d): %v" , executableLen , err )
return nil , false , err
}
expected := crc32 .Checksum (executable , crc )
if _, err = io .ReadFull (reader , eightBytes [:4 ]); err != nil {
return nil , false , fmt .Errorf ("compilationcache: could not read checksum: %v" , err )
} else if checksum := binary .LittleEndian .Uint32 (eightBytes [:4 ]); expected != checksum {
return nil , false , fmt .Errorf ("compilationcache: checksum mismatch (expected %d, got %d)" , expected , checksum )
}
if err = platform .MprotectCodeSegment (executable ); err != nil {
return nil , false , err
}
cm .executable = executable
}
if _ , err := io .ReadFull (reader , eightBytes [:1 ]); err != nil {
return nil , false , fmt .Errorf ("compilationcache: error reading source map presence: %v" , err )
}
if eightBytes [0 ] == 1 {
sm := &cm .sourceMap
sourceMapLen , err := readUint64 (reader , &eightBytes )
if err != nil {
err = fmt .Errorf ("compilationcache: error reading source map length: %v" , err )
return nil , false , err
}
executableOffset := uintptr (unsafe .Pointer (&cm .executable [0 ]))
for i := uint64 (0 ); i < sourceMapLen ; i ++ {
wasmBinaryOffset , err := readUint64 (reader , &eightBytes )
if err != nil {
err = fmt .Errorf ("compilationcache: error reading source map[%d] wasm binary offset: %v" , i , err )
return nil , false , err
}
executableRelativeOffset , err := readUint64 (reader , &eightBytes )
if err != nil {
err = fmt .Errorf ("compilationcache: error reading source map[%d] executable offset: %v" , i , err )
return nil , false , err
}
sm .wasmBinaryOffsets = append (sm .wasmBinaryOffsets , wasmBinaryOffset )
sm .executableOffsets = append (sm .executableOffsets , uintptr (executableRelativeOffset )+executableOffset )
}
}
return
}
func readUint64(reader io .Reader , b *[8 ]byte ) (uint64 , error ) {
s := b [0 :8 ]
n , err := reader .Read (s )
if err != nil {
return 0 , err
} else if n < 8 {
return 0 , io .EOF
}
ret := binary .LittleEndian .Uint64 (s )
return ret , nil
}
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 .