mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-30 15:42:46 +00:00
669 lines
23 KiB
Go
669 lines
23 KiB
Go
|
package wasm
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/binary"
|
||
|
"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"
|
||
|
)
|
||
|
|
||
|
// nameToModuleShrinkThreshold is the size the nameToModule map can grow to
|
||
|
// before it starts to be monitored for shrinking.
|
||
|
// The capacity will never be smaller than this once the threshold is met.
|
||
|
const nameToModuleShrinkThreshold = 100
|
||
|
|
||
|
type (
|
||
|
// Store is the runtime representation of "instantiated" Wasm module and objects.
|
||
|
// Multiple modules can be instantiated within a single store, and each instance,
|
||
|
// (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection.
|
||
|
//
|
||
|
// Every type whose name ends with "Instance" suffix belongs to exactly one store.
|
||
|
//
|
||
|
// Note that store is not thread (concurrency) safe, meaning that using single Store
|
||
|
// via multiple goroutines might result in race conditions. In that case, the invocation
|
||
|
// and access to any methods and field of Store must be guarded by mutex.
|
||
|
//
|
||
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
|
||
|
Store struct {
|
||
|
// moduleList ensures modules are closed in reverse initialization order.
|
||
|
moduleList *ModuleInstance // guarded by mux
|
||
|
|
||
|
// nameToModule holds the instantiated Wasm modules by module name from Instantiate.
|
||
|
// It ensures no race conditions instantiating two modules of the same name.
|
||
|
nameToModule map[string]*ModuleInstance // guarded by mux
|
||
|
|
||
|
// nameToModuleCap tracks the growth of the nameToModule map in order to
|
||
|
// track when to shrink it.
|
||
|
nameToModuleCap int // guarded by mux
|
||
|
|
||
|
// EnabledFeatures are read-only to allow optimizations.
|
||
|
EnabledFeatures api.CoreFeatures
|
||
|
|
||
|
// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
|
||
|
Engine Engine
|
||
|
|
||
|
// typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
|
||
|
// do type-checks on indirect function calls.
|
||
|
typeIDs map[string]FunctionTypeID
|
||
|
|
||
|
// functionMaxTypes represents the limit on the number of function types in a store.
|
||
|
// Note: this is fixed to 2^27 but have this a field for testability.
|
||
|
functionMaxTypes uint32
|
||
|
|
||
|
// mux is used to guard the fields from concurrent access.
|
||
|
mux sync.RWMutex
|
||
|
}
|
||
|
|
||
|
// ModuleInstance represents instantiated wasm module.
|
||
|
// The difference from the spec is that in wazero, a ModuleInstance holds pointers
|
||
|
// to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience.
|
||
|
//
|
||
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst
|
||
|
//
|
||
|
// This implements api.Module.
|
||
|
ModuleInstance struct {
|
||
|
internalapi.WazeroOnlyType
|
||
|
|
||
|
ModuleName string
|
||
|
Exports map[string]*Export
|
||
|
Globals []*GlobalInstance
|
||
|
MemoryInstance *MemoryInstance
|
||
|
Tables []*TableInstance
|
||
|
|
||
|
// Engine implements function calls for this module.
|
||
|
Engine ModuleEngine
|
||
|
|
||
|
// TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store.
|
||
|
// This is necessary to achieve fast runtime type checking for indirect function calls at runtime.
|
||
|
TypeIDs []FunctionTypeID
|
||
|
|
||
|
// DataInstances holds data segments bytes of the module.
|
||
|
// This is only used by bulk memory operations.
|
||
|
//
|
||
|
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
|
||
|
DataInstances []DataInstance
|
||
|
|
||
|
// ElementInstances holds the element instance, and each holds the references to either functions
|
||
|
// or external objects (unimplemented).
|
||
|
ElementInstances []ElementInstance
|
||
|
|
||
|
// Sys is exposed for use in special imports such as WASI, assemblyscript.
|
||
|
//
|
||
|
// # Notes
|
||
|
//
|
||
|
// - This is a part of ModuleInstance so that scope and Close is coherent.
|
||
|
// - This is not exposed outside this repository (as a host function
|
||
|
// parameter) because we haven't thought through capabilities based
|
||
|
// security implications.
|
||
|
Sys *internalsys.Context
|
||
|
|
||
|
// Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
|
||
|
//
|
||
|
// The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
|
||
|
//
|
||
|
// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
|
||
|
// See /RATIONALE.md
|
||
|
Closed atomic.Uint64
|
||
|
|
||
|
// CodeCloser is non-nil when the code should be closed after this module.
|
||
|
CodeCloser api.Closer
|
||
|
|
||
|
// s is the Store on which this module is instantiated.
|
||
|
s *Store
|
||
|
// prev and next hold the nodes in the linked list of ModuleInstance held by Store.
|
||
|
prev, next *ModuleInstance
|
||
|
// Source is a pointer to the Module from which this ModuleInstance derives.
|
||
|
Source *Module
|
||
|
|
||
|
// CloseNotifier is an experimental hook called once on close.
|
||
|
CloseNotifier experimental.CloseNotifier
|
||
|
}
|
||
|
|
||
|
// DataInstance holds bytes corresponding to the data segment in a module.
|
||
|
//
|
||
|
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
|
||
|
DataInstance = []byte
|
||
|
|
||
|
// GlobalInstance represents a global instance in a store.
|
||
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0
|
||
|
GlobalInstance struct {
|
||
|
Type GlobalType
|
||
|
// Val holds a 64-bit representation of the actual value.
|
||
|
// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
|
||
|
Val uint64
|
||
|
// ValHi is only used for vector type globals, and holds the higher bits of the vector.
|
||
|
// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
|
||
|
ValHi uint64
|
||
|
// Me is the module engine that owns this global instance.
|
||
|
// The .Val and .ValHi fields are only valid when me is nil.
|
||
|
// If me is non-nil, the value is stored in the module engine.
|
||
|
Me ModuleEngine
|
||
|
Index Index
|
||
|
}
|
||
|
|
||
|
// FunctionTypeID is a uniquely assigned integer for a function type.
|
||
|
// This is wazero specific runtime object and specific to a store,
|
||
|
// and used at runtime to do type-checks on indirect function calls.
|
||
|
FunctionTypeID uint32
|
||
|
)
|
||
|
|
||
|
// The wazero specific limitations described at RATIONALE.md.
|
||
|
const maximumFunctionTypes = 1 << 27
|
||
|
|
||
|
// GetFunctionTypeID is used by emscripten.
|
||
|
func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID {
|
||
|
id, err := m.s.GetFunctionTypeID(t)
|
||
|
if err != nil {
|
||
|
// This is not recoverable in practice since the only error GetFunctionTypeID returns is
|
||
|
// when there's too many function types in the store.
|
||
|
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 {
|
||
|
// Only passive elements can be access as element instances.
|
||
|
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
|
||
|
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() ||
|
||
|
// Per https://github.com/WebAssembly/spec/issues/1427 init can be no-op.
|
||
|
len(elem.Init) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
var offset uint32
|
||
|
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
|
||
|
// Ignore error as it's already validated.
|
||
|
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||
|
global := m.Globals[globalIdx]
|
||
|
offset = uint32(global.Val)
|
||
|
} else {
|
||
|
// Ignore error as it's already validated.
|
||
|
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) {
|
||
|
// ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length.
|
||
|
// Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal,
|
||
|
// this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error.
|
||
|
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
|
||
|
//
|
||
|
// In wazero, we ignore it since in any way, the instantiated module and engines are fine and can be used
|
||
|
// for function invocations.
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// validateData ensures that data segments are valid in terms of memory boundary.
|
||
|
// Note: this is used only when bulk-memory/reference type feature is disabled.
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// applyData uses the given data segments and mutate the memory according to the initial contents on it
|
||
|
// and populate the `DataInstances`. This is called after all the validation phase passes and out of
|
||
|
// bounds memory access error here is not a validation error, but rather a runtime error.
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// GetExport returns an export of the given name and type or errs if not exported or the wrong type.
|
||
|
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,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under
|
||
|
// different names safely and concurrently.
|
||
|
//
|
||
|
// * ctx: the default context used for function calls.
|
||
|
// * name: the name of the module.
|
||
|
// * sys: the system context, which will be closed (SysContext.Close) on ModuleInstance.Close.
|
||
|
//
|
||
|
// Note: Module.Validate must be called prior to instantiation.
|
||
|
func (s *Store) Instantiate(
|
||
|
ctx context.Context,
|
||
|
module *Module,
|
||
|
name string,
|
||
|
sys *internalsys.Context,
|
||
|
typeIDs []FunctionTypeID,
|
||
|
) (*ModuleInstance, error) {
|
||
|
// Instantiate the module and add it to the store so that other modules can import it.
|
||
|
m, err := s.instantiate(ctx, module, name, sys, typeIDs)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Now that the instantiation is complete without error, add it.
|
||
|
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(module); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
err = m.buildTables(module,
|
||
|
// As of reference-types proposal, boundary check must be done after instantiation.
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// As of reference types proposal, data segment validation must happen after instantiation,
|
||
|
// and the side effect must persist even if there's out of bounds error after instantiation.
|
||
|
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405
|
||
|
if !s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) {
|
||
|
if err = m.validateData(module.DataSection); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// After engine creation, we can create the funcref element instances and initialize funcref type globals.
|
||
|
m.buildElementInstances(module.ElementSection)
|
||
|
|
||
|
// Now all the validation passes, we are safe to mutate memory instances (possibly imported ones).
|
||
|
if err = m.applyData(module.DataSection); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
m.applyElements(module.ElementSection)
|
||
|
|
||
|
m.Engine.DoneInstantiation()
|
||
|
|
||
|
// Execute the start function.
|
||
|
if module.StartSection != nil {
|
||
|
funcIdx := *module.StartSection
|
||
|
ce := m.Engine.NewFunction(funcIdx)
|
||
|
_, err = ce.Call(ctx)
|
||
|
if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error!
|
||
|
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(module *Module) (err error) {
|
||
|
for moduleName, imports := range module.ImportPerModule {
|
||
|
var importedModule *ModuleInstance
|
||
|
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, 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)
|
||
|
}
|
||
|
|
||
|
// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
|
||
|
// The validity of the expression is ensured when calling this function as this is only called
|
||
|
// during instantiation phrase, and the validation happens in compilation (validateConstExpression).
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// initialize initializes the value of this global instance given the const expr and imported globals.
|
||
|
// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr.
|
||
|
//
|
||
|
// Global initialization constant expression can only reference the imported globals.
|
||
|
// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
|
||
|
func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) {
|
||
|
switch expr.Opcode {
|
||
|
case OpcodeI32Const:
|
||
|
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||
|
v, _, _ := leb128.LoadInt32(expr.Data)
|
||
|
g.Val = uint64(uint32(v))
|
||
|
case OpcodeI64Const:
|
||
|
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||
|
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 // Reference types are opaque 64bit pointer at runtime.
|
||
|
}
|
||
|
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])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// String implements api.Global.
|
||
|
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()
|
||
|
// Check again in case another goroutine has already added the type.
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// CloseWithExitCode implements the same method as documented on wazero.Runtime.
|
||
|
func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
|
||
|
s.mux.Lock()
|
||
|
defer s.mux.Unlock()
|
||
|
// Close modules in reverse initialization order.
|
||
|
for m := s.moduleList; m != nil; m = m.next {
|
||
|
// If closing this module errs, proceed anyway to close the others.
|
||
|
if e := m.closeWithExitCode(ctx, exitCode); e != nil && err == nil {
|
||
|
// TODO: use multiple errors handling in Go 1.20.
|
||
|
err = e // first error
|
||
|
}
|
||
|
}
|
||
|
s.moduleList = nil
|
||
|
s.nameToModule = nil
|
||
|
s.nameToModuleCap = 0
|
||
|
s.typeIDs = nil
|
||
|
return
|
||
|
}
|