package wasm
import (
"context"
"encoding/binary"
"errors"
"fmt"
"sync"
"sync/atomic"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/expctxkeys"
"github.com/tetratelabs/wazero/internal/internalapi"
"github.com/tetratelabs/wazero/internal/leb128"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/sys"
)
const nameToModuleShrinkThreshold = 100
type (
Store struct {
moduleList *ModuleInstance
nameToModule map [string ]*ModuleInstance
nameToModuleCap int
EnabledFeatures api .CoreFeatures
Engine Engine
typeIDs map [string ]FunctionTypeID
functionMaxTypes uint32
mux sync .RWMutex
}
ModuleInstance struct {
internalapi .WazeroOnlyType
ModuleName string
Exports map [string ]*Export
Globals []*GlobalInstance
MemoryInstance *MemoryInstance
Tables []*TableInstance
Engine ModuleEngine
TypeIDs []FunctionTypeID
DataInstances []DataInstance
ElementInstances []ElementInstance
Sys *internalsys .Context
Closed atomic .Uint64
CodeCloser api .Closer
s *Store
prev, next *ModuleInstance
Source *Module
CloseNotifier experimental .CloseNotifier
}
DataInstance = []byte
GlobalInstance struct {
Type GlobalType
Val uint64
ValHi uint64
Me ModuleEngine
Index Index
}
FunctionTypeID uint32
)
const maximumFunctionTypes = 1 << 27
func (m *ModuleInstance ) GetFunctionTypeID (t *FunctionType ) FunctionTypeID {
id , err := m .s .GetFunctionTypeID (t )
if err != nil {
panic (err )
}
return id
}
func (m *ModuleInstance ) buildElementInstances (elements []ElementSegment ) {
m .ElementInstances = make ([][]Reference , len (elements ))
for i , elm := range elements {
if elm .Type == RefTypeFuncref && elm .Mode == ElementModePassive {
inits := elm .Init
inst := make ([]Reference , len (inits ))
m .ElementInstances [i ] = inst
for j , idx := range inits {
if index , ok := unwrapElementInitGlobalReference (idx ); ok {
global := m .Globals [index ]
inst [j ] = Reference (global .Val )
} else {
if idx != ElementInitNullReference {
inst [j ] = m .Engine .FunctionInstanceReference (idx )
}
}
}
}
}
}
func (m *ModuleInstance ) applyElements (elems []ElementSegment ) {
for elemI := range elems {
elem := &elems [elemI ]
if !elem .IsActive () ||
len (elem .Init ) == 0 {
continue
}
var offset uint32
if elem .OffsetExpr .Opcode == OpcodeGlobalGet {
globalIdx , _ , _ := leb128 .LoadUint32 (elem .OffsetExpr .Data )
global := m .Globals [globalIdx ]
offset = uint32 (global .Val )
} else {
o , _ , _ := leb128 .LoadInt32 (elem .OffsetExpr .Data )
offset = uint32 (o )
}
table := m .Tables [elem .TableIndex ]
references := table .References
if int (offset )+len (elem .Init ) > len (references ) {
return
}
if table .Type == RefTypeExternref {
for i := 0 ; i < len (elem .Init ); i ++ {
references [offset +uint32 (i )] = Reference (0 )
}
} else {
for i , init := range elem .Init {
if init == ElementInitNullReference {
continue
}
var ref Reference
if index , ok := unwrapElementInitGlobalReference (init ); ok {
global := m .Globals [index ]
ref = Reference (global .Val )
} else {
ref = m .Engine .FunctionInstanceReference (index )
}
references [offset +uint32 (i )] = ref
}
}
}
}
func (m *ModuleInstance ) validateData (data []DataSegment ) (err error ) {
for i := range data {
d := &data [i ]
if !d .IsPassive () {
offset := int (executeConstExpressionI32 (m .Globals , &d .OffsetExpression ))
ceil := offset + len (d .Init )
if offset < 0 || ceil > len (m .MemoryInstance .Buffer ) {
return fmt .Errorf ("%s[%d]: out of bounds memory access" , SectionIDName (SectionIDData ), i )
}
}
}
return
}
func (m *ModuleInstance ) applyData (data []DataSegment ) error {
m .DataInstances = make ([][]byte , len (data ))
for i := range data {
d := &data [i ]
m .DataInstances [i ] = d .Init
if !d .IsPassive () {
offset := executeConstExpressionI32 (m .Globals , &d .OffsetExpression )
if offset < 0 || int (offset )+len (d .Init ) > len (m .MemoryInstance .Buffer ) {
return fmt .Errorf ("%s[%d]: out of bounds memory access" , SectionIDName (SectionIDData ), i )
}
copy (m .MemoryInstance .Buffer [offset :], d .Init )
}
}
return nil
}
func (m *ModuleInstance ) getExport (name string , et ExternType ) (*Export , error ) {
exp , ok := m .Exports [name ]
if !ok {
return nil , fmt .Errorf ("%q is not exported in module %q" , name , m .ModuleName )
}
if exp .Type != et {
return nil , fmt .Errorf ("export %q in module %q is a %s, not a %s" , name , m .ModuleName , ExternTypeName (exp .Type ), ExternTypeName (et ))
}
return exp , nil
}
func NewStore (enabledFeatures api .CoreFeatures , engine Engine ) *Store {
return &Store {
nameToModule : map [string ]*ModuleInstance {},
nameToModuleCap : nameToModuleShrinkThreshold ,
EnabledFeatures : enabledFeatures ,
Engine : engine ,
typeIDs : map [string ]FunctionTypeID {},
functionMaxTypes : maximumFunctionTypes ,
}
}
func (s *Store ) Instantiate (
ctx context .Context ,
module *Module ,
name string ,
sys *internalsys .Context ,
typeIDs []FunctionTypeID ,
) (*ModuleInstance , error ) {
m , err := s .instantiate (ctx , module , name , sys , typeIDs )
if err != nil {
return nil , err
}
if err = s .registerModule (m ); err != nil {
_ = m .Close (ctx )
return nil , err
}
return m , nil
}
func (s *Store ) instantiate (
ctx context .Context ,
module *Module ,
name string ,
sysCtx *internalsys .Context ,
typeIDs []FunctionTypeID ,
) (m *ModuleInstance , err error ) {
m = &ModuleInstance {ModuleName : name , TypeIDs : typeIDs , Sys : sysCtx , s : s , Source : module }
m .Tables = make ([]*TableInstance , int (module .ImportTableCount )+len (module .TableSection ))
m .Globals = make ([]*GlobalInstance , int (module .ImportGlobalCount )+len (module .GlobalSection ))
m .Engine , err = s .Engine .NewModuleEngine (module , m )
if err != nil {
return nil , err
}
if err = m .resolveImports (ctx , module ); err != nil {
return nil , err
}
err = m .buildTables (module ,
s .EnabledFeatures .IsEnabled (api .CoreFeatureReferenceTypes ))
if err != nil {
return nil , err
}
allocator , _ := ctx .Value (expctxkeys .MemoryAllocatorKey {}).(experimental .MemoryAllocator )
m .buildGlobals (module , m .Engine .FunctionInstanceReference )
m .buildMemory (module , allocator )
m .Exports = module .Exports
for _ , exp := range m .Exports {
if exp .Type == ExternTypeTable {
t := m .Tables [exp .Index ]
t .involvingModuleInstances = append (t .involvingModuleInstances , m )
}
}
if !s .EnabledFeatures .IsEnabled (api .CoreFeatureReferenceTypes ) {
if err = m .validateData (module .DataSection ); err != nil {
return nil , err
}
}
m .buildElementInstances (module .ElementSection )
if err = m .applyData (module .DataSection ); err != nil {
return nil , err
}
m .applyElements (module .ElementSection )
m .Engine .DoneInstantiation ()
if module .StartSection != nil {
funcIdx := *module .StartSection
ce := m .Engine .NewFunction (funcIdx )
_, err = ce .Call (ctx )
if exitErr , ok := err .(*sys .ExitError ); ok {
return nil , exitErr
} else if err != nil {
return nil , fmt .Errorf ("start %s failed: %w" , module .funcDesc (SectionIDFunction , funcIdx ), err )
}
}
return
}
func (m *ModuleInstance ) resolveImports (ctx context .Context , module *Module ) (err error ) {
resolveImport , _ := ctx .Value (expctxkeys .ImportResolverKey {}).(experimental .ImportResolver )
for moduleName , imports := range module .ImportPerModule {
var importedModule *ModuleInstance
if resolveImport != nil {
if v := resolveImport (moduleName ); v != nil {
importedModule = v .(*ModuleInstance )
}
}
if importedModule == nil {
importedModule , err = m .s .module (moduleName )
if err != nil {
return err
}
}
for _ , i := range imports {
var imported *Export
imported , err = importedModule .getExport (i .Name , i .Type )
if err != nil {
return
}
switch i .Type {
case ExternTypeFunc :
expectedType := &module .TypeSection [i .DescFunc ]
src := importedModule .Source
actual := src .typeOfFunction (imported .Index )
if !actual .EqualsSignature (expectedType .Params , expectedType .Results ) {
err = errorInvalidImport (i , fmt .Errorf ("signature mismatch: %s != %s" , expectedType , actual ))
return
}
m .Engine .ResolveImportedFunction (i .IndexPerType , i .DescFunc , imported .Index , importedModule .Engine )
case ExternTypeTable :
expected := i .DescTable
importedTable := importedModule .Tables [imported .Index ]
if expected .Type != importedTable .Type {
err = errorInvalidImport (i , fmt .Errorf ("table type mismatch: %s != %s" ,
RefTypeName (expected .Type ), RefTypeName (importedTable .Type )))
return
}
if expected .Min > importedTable .Min {
err = errorMinSizeMismatch (i , expected .Min , importedTable .Min )
return
}
if expected .Max != nil {
expectedMax := *expected .Max
if importedTable .Max == nil {
err = errorNoMax (i , expectedMax )
return
} else if expectedMax < *importedTable .Max {
err = errorMaxSizeMismatch (i , expectedMax , *importedTable .Max )
return
}
}
m .Tables [i .IndexPerType ] = importedTable
importedTable .involvingModuleInstancesMutex .Lock ()
if len (importedTable .involvingModuleInstances ) == 0 {
panic ("BUG: involvingModuleInstances must not be nil when it's imported" )
}
importedTable .involvingModuleInstances = append (importedTable .involvingModuleInstances , m )
importedTable .involvingModuleInstancesMutex .Unlock ()
case ExternTypeMemory :
expected := i .DescMem
importedMemory := importedModule .MemoryInstance
if expected .Min > memoryBytesNumToPages (uint64 (len (importedMemory .Buffer ))) {
err = errorMinSizeMismatch (i , expected .Min , importedMemory .Min )
return
}
if expected .Max < importedMemory .Max {
err = errorMaxSizeMismatch (i , expected .Max , importedMemory .Max )
return
}
m .MemoryInstance = importedMemory
m .Engine .ResolveImportedMemory (importedModule .Engine )
case ExternTypeGlobal :
expected := i .DescGlobal
importedGlobal := importedModule .Globals [imported .Index ]
if expected .Mutable != importedGlobal .Type .Mutable {
err = errorInvalidImport (i , fmt .Errorf ("mutability mismatch: %t != %t" ,
expected .Mutable , importedGlobal .Type .Mutable ))
return
}
if expected .ValType != importedGlobal .Type .ValType {
err = errorInvalidImport (i , fmt .Errorf ("value type mismatch: %s != %s" ,
ValueTypeName (expected .ValType ), ValueTypeName (importedGlobal .Type .ValType )))
return
}
m .Globals [i .IndexPerType ] = importedGlobal
}
}
}
return
}
func errorMinSizeMismatch(i *Import , expected , actual uint32 ) error {
return errorInvalidImport (i , fmt .Errorf ("minimum size mismatch: %d > %d" , expected , actual ))
}
func errorNoMax(i *Import , expected uint32 ) error {
return errorInvalidImport (i , fmt .Errorf ("maximum size mismatch: %d, but actual has no max" , expected ))
}
func errorMaxSizeMismatch(i *Import , expected , actual uint32 ) error {
return errorInvalidImport (i , fmt .Errorf ("maximum size mismatch: %d < %d" , expected , actual ))
}
func errorInvalidImport(i *Import , err error ) error {
return fmt .Errorf ("import %s[%s.%s]: %w" , ExternTypeName (i .Type ), i .Module , i .Name , err )
}
func executeConstExpressionI32(importedGlobals []*GlobalInstance , expr *ConstantExpression ) (ret int32 ) {
switch expr .Opcode {
case OpcodeI32Const :
ret , _, _ = leb128 .LoadInt32 (expr .Data )
case OpcodeGlobalGet :
id , _ , _ := leb128 .LoadUint32 (expr .Data )
g := importedGlobals [id ]
ret = int32 (g .Val )
}
return
}
func (g *GlobalInstance ) initialize (importedGlobals []*GlobalInstance , expr *ConstantExpression , funcRefResolver func (funcIndex Index ) Reference ) {
switch expr .Opcode {
case OpcodeI32Const :
v , _ , _ := leb128 .LoadInt32 (expr .Data )
g .Val = uint64 (uint32 (v ))
case OpcodeI64Const :
v , _ , _ := leb128 .LoadInt64 (expr .Data )
g .Val = uint64 (v )
case OpcodeF32Const :
g .Val = uint64 (binary .LittleEndian .Uint32 (expr .Data ))
case OpcodeF64Const :
g .Val = binary .LittleEndian .Uint64 (expr .Data )
case OpcodeGlobalGet :
id , _ , _ := leb128 .LoadUint32 (expr .Data )
importedG := importedGlobals [id ]
switch importedG .Type .ValType {
case ValueTypeI32 :
g .Val = uint64 (uint32 (importedG .Val ))
case ValueTypeI64 :
g .Val = importedG .Val
case ValueTypeF32 :
g .Val = importedG .Val
case ValueTypeF64 :
g .Val = importedG .Val
case ValueTypeV128 :
g .Val , g .ValHi = importedG .Val , importedG .ValHi
case ValueTypeFuncref , ValueTypeExternref :
g .Val = importedG .Val
}
case OpcodeRefNull :
switch expr .Data [0 ] {
case ValueTypeExternref , ValueTypeFuncref :
g .Val = 0
}
case OpcodeRefFunc :
v , _ , _ := leb128 .LoadUint32 (expr .Data )
g .Val = uint64 (funcRefResolver (v ))
case OpcodeVecV128Const :
g .Val , g .ValHi = binary .LittleEndian .Uint64 (expr .Data [0 :8 ]), binary .LittleEndian .Uint64 (expr .Data [8 :16 ])
}
}
func (g *GlobalInstance ) String () string {
switch g .Type .ValType {
case ValueTypeI32 , ValueTypeI64 :
return fmt .Sprintf ("global(%d)" , g .Val )
case ValueTypeF32 :
return fmt .Sprintf ("global(%f)" , api .DecodeF32 (g .Val ))
case ValueTypeF64 :
return fmt .Sprintf ("global(%f)" , api .DecodeF64 (g .Val ))
default :
panic (fmt .Errorf ("BUG: unknown value type %X" , g .Type .ValType ))
}
}
func (g *GlobalInstance ) Value () (uint64 , uint64 ) {
if g .Me != nil {
return g .Me .GetGlobalValue (g .Index )
}
return g .Val , g .ValHi
}
func (g *GlobalInstance ) SetValue (lo , hi uint64 ) {
if g .Me != nil {
g .Me .SetGlobalValue (g .Index , lo , hi )
} else {
g .Val , g .ValHi = lo , hi
}
}
func (s *Store ) GetFunctionTypeIDs (ts []FunctionType ) ([]FunctionTypeID , error ) {
ret := make ([]FunctionTypeID , len (ts ))
for i := range ts {
t := &ts [i ]
inst , err := s .GetFunctionTypeID (t )
if err != nil {
return nil , err
}
ret [i ] = inst
}
return ret , nil
}
func (s *Store ) GetFunctionTypeID (t *FunctionType ) (FunctionTypeID , error ) {
s .mux .RLock ()
key := t .key ()
id , ok := s .typeIDs [key ]
s .mux .RUnlock ()
if !ok {
s .mux .Lock ()
defer s .mux .Unlock ()
if id , ok = s .typeIDs [key ]; ok {
return id , nil
}
l := len (s .typeIDs )
if uint32 (l ) >= s .functionMaxTypes {
return 0 , fmt .Errorf ("too many function types in a store" )
}
id = FunctionTypeID (l )
s .typeIDs [key ] = id
}
return id , nil
}
func (s *Store ) CloseWithExitCode (ctx context .Context , exitCode uint32 ) error {
s .mux .Lock ()
defer s .mux .Unlock ()
var errs []error
for m := s .moduleList ; m != nil ; m = m .next {
if err := m .closeWithExitCode (ctx , exitCode ); err != nil {
errs = append (errs , err )
}
}
s .moduleList = nil
s .nameToModule = nil
s .nameToModuleCap = 0
s .typeIDs = nil
return errors .Join (errs ...)
}
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 .