This commit implements the v128.const, i32x4.add and i64x2.add in interpreter mode and this adds support for the vector value types in the locals and globals. Notably, the vector type values can be passed and returned by exported functions as well as host functions via two-uint64 encodings as described in #484 (comment). Note: implementation of these instructions on JIT will be done in subsequent PR. part of #484 Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
698 lines
23 KiB
Go
698 lines
23 KiB
Go
package wasm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
experimentalapi "github.com/tetratelabs/wazero/experimental"
|
|
"github.com/tetratelabs/wazero/internal/ieee754"
|
|
"github.com/tetratelabs/wazero/internal/leb128"
|
|
)
|
|
|
|
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 {
|
|
// EnabledFeatures are read-only to allow optimizations.
|
|
EnabledFeatures Features
|
|
|
|
// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
|
|
Engine Engine
|
|
|
|
// moduleNames ensures no race conditions instantiating two modules of the same name
|
|
moduleNames []string // guarded by mux
|
|
|
|
// modules holds the instantiated Wasm modules by module name from Instantiate.
|
|
modules map[string]*ModuleInstance // guarded by mux
|
|
|
|
// 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
|
|
ModuleInstance struct {
|
|
Name string
|
|
Exports map[string]*ExportInstance
|
|
Functions []*FunctionInstance
|
|
Globals []*GlobalInstance
|
|
// Memory is set when Module.MemorySection had a memory, regardless of whether it was exported.
|
|
Memory *MemoryInstance
|
|
Tables []*TableInstance
|
|
Types []*FunctionType
|
|
|
|
// CallCtx holds default function call context from this function instance.
|
|
CallCtx *CallContext
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
|
|
// ExportInstance represents an exported instance in a Store.
|
|
// The difference from the spec is that in wazero, a ExportInstance 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-exportinst
|
|
ExportInstance struct {
|
|
Type ExternType
|
|
Function *FunctionInstance
|
|
Global *GlobalInstance
|
|
Memory *MemoryInstance
|
|
Table *TableInstance
|
|
}
|
|
|
|
// FunctionInstance represents a function instance in a Store.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-instances%E2%91%A0
|
|
FunctionInstance struct {
|
|
// DebugName is for debugging purpose, and is used to augment stack traces.
|
|
DebugName string
|
|
|
|
// Kind describes how this function should be called.
|
|
Kind FunctionKind
|
|
|
|
// Type is the signature of this function.
|
|
Type *FunctionType
|
|
|
|
// LocalTypes holds types of locals, set when Kind == FunctionKindWasm
|
|
LocalTypes []ValueType
|
|
|
|
// Body is the function body in WebAssembly Binary Format, set when Kind == FunctionKindWasm
|
|
Body []byte
|
|
|
|
// GoFunc holds the runtime representation of host functions.
|
|
// This is nil when Kind == FunctionKindWasm. Otherwise, all the above fields are ignored as they are
|
|
// specific to Wasm functions.
|
|
GoFunc *reflect.Value
|
|
|
|
// Fields above here are settable prior to instantiation. Below are set by the Store during instantiation.
|
|
|
|
// ModuleInstance holds the pointer to the module instance to which this function belongs.
|
|
Module *ModuleInstance
|
|
|
|
// TypeID is assigned by a store for FunctionType.
|
|
TypeID FunctionTypeID
|
|
|
|
// Idx holds the index of this function instance in the function index namespace (beginning with imports).
|
|
Idx Index
|
|
|
|
// The below metadata are used in function listeners, prior to instantiation:
|
|
|
|
// moduleName is the defining module's name
|
|
moduleName string
|
|
|
|
// name is the module-defined name of this function
|
|
name string
|
|
|
|
// paramNames is non-nil when all parameters have names.
|
|
paramNames []string
|
|
|
|
// exportNames is non-nil when the function is exported.
|
|
exportNames []string
|
|
|
|
// FunctionListener holds a listener to notify when this function is called.
|
|
FunctionListener experimentalapi.FunctionListener
|
|
}
|
|
|
|
// 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.
|
|
Val uint64
|
|
// ValHi is only used for vector type globals, and holds the higher bits of the vector.
|
|
ValHi uint64
|
|
// ^^ TODO: this should be guarded with atomics when mutable
|
|
}
|
|
|
|
// 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
|
|
)
|
|
|
|
// Index implements the same method as documented on experimental.FunctionDefinition.
|
|
func (f *FunctionInstance) Index() uint32 {
|
|
return f.Idx
|
|
}
|
|
|
|
// Name implements the same method as documented on experimental.FunctionDefinition.
|
|
func (f *FunctionInstance) Name() string {
|
|
return f.name
|
|
}
|
|
|
|
// ModuleName implements the same method as documented on experimental.FunctionDefinition.
|
|
func (f *FunctionInstance) ModuleName() string {
|
|
return f.moduleName
|
|
}
|
|
|
|
// ExportNames implements the same method as documented on experimental.FunctionDefinition.
|
|
func (f *FunctionInstance) ExportNames() []string {
|
|
return f.exportNames
|
|
}
|
|
|
|
// ParamNames implements the same method as documented on experimental.FunctionDefinition.
|
|
func (f *FunctionInstance) ParamNames() []string {
|
|
return f.paramNames
|
|
}
|
|
|
|
// The wazero specific limitations described at RATIONALE.md.
|
|
const (
|
|
maximumFunctionTypes = 1 << 27
|
|
)
|
|
|
|
// addSections adds section elements to the ModuleInstance
|
|
func (m *ModuleInstance) addSections(module *Module, importedFunctions, functions []*FunctionInstance,
|
|
importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance,
|
|
types []*FunctionType, typeIDs []FunctionTypeID) {
|
|
|
|
m.Types = types
|
|
m.TypeIDs = typeIDs
|
|
|
|
m.Functions = append(m.Functions, importedFunctions...)
|
|
for i, f := range functions {
|
|
// Associate each function with the type instance and the module instance's pointer.
|
|
f.Module = m
|
|
f.TypeID = typeIDs[module.FunctionSection[i]]
|
|
m.Functions = append(m.Functions, f)
|
|
}
|
|
|
|
m.Globals = append(m.Globals, importedGlobals...)
|
|
m.Globals = append(m.Globals, globals...)
|
|
|
|
m.Tables = tables
|
|
|
|
if importedMemory != nil {
|
|
m.Memory = importedMemory
|
|
} else {
|
|
m.Memory = memory
|
|
}
|
|
|
|
m.buildExports(module.ExportSection)
|
|
m.buildDataInstances(module.DataSection)
|
|
}
|
|
|
|
func (m *ModuleInstance) buildDataInstances(segments []*DataSegment) {
|
|
for _, d := range segments {
|
|
m.DataInstances = append(m.DataInstances, d.Init)
|
|
}
|
|
}
|
|
|
|
func (m *ModuleInstance) buildElementInstances(elements []*ElementSegment) {
|
|
m.ElementInstances = make([]ElementInstance, 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
|
|
m.ElementInstances[i] = *m.Engine.CreateFuncElementInstance(elm.Init)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *ModuleInstance) buildExports(exports []*Export) {
|
|
m.Exports = make(map[string]*ExportInstance, len(exports))
|
|
for _, exp := range exports {
|
|
index := exp.Index
|
|
var ei *ExportInstance
|
|
switch exp.Type {
|
|
case ExternTypeFunc:
|
|
ei = &ExportInstance{Type: exp.Type, Function: m.Functions[index]}
|
|
case ExternTypeGlobal:
|
|
ei = &ExportInstance{Type: exp.Type, Global: m.Globals[index]}
|
|
case ExternTypeMemory:
|
|
ei = &ExportInstance{Type: exp.Type, Memory: m.Memory}
|
|
case ExternTypeTable:
|
|
ei = &ExportInstance{Type: exp.Type, Table: m.Tables[index]}
|
|
}
|
|
|
|
// We already validated the duplicates during module validation phase.
|
|
m.Exports[exp.Name] = ei
|
|
}
|
|
}
|
|
|
|
func (m *ModuleInstance) validateData(data []*DataSegment) (err error) {
|
|
for i, d := range data {
|
|
if !d.IsPassive() {
|
|
offset := int(executeConstExpression(m.Globals, d.OffsetExpression).(int32))
|
|
ceil := offset + len(d.Init)
|
|
if offset < 0 || ceil > len(m.Memory.Buffer) {
|
|
return fmt.Errorf("%s[%d] out of bounds memory access", SectionIDName(SectionIDElement), i)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *ModuleInstance) applyData(data []*DataSegment) error {
|
|
for i, d := range data {
|
|
if !d.IsPassive() {
|
|
offset := executeConstExpression(m.Globals, d.OffsetExpression).(int32)
|
|
if offset < 0 || int(offset)+len(d.Init) > len(m.Memory.Buffer) {
|
|
return fmt.Errorf("%s[%d] out of bounds memory access", SectionIDName(SectionIDElement), i)
|
|
}
|
|
copy(m.Memory.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) (*ExportInstance, error) {
|
|
exp, ok := m.Exports[name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("%q is not exported in module %q", name, m.Name)
|
|
}
|
|
if exp.Type != et {
|
|
return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.Name, ExternTypeName(exp.Type), ExternTypeName(et))
|
|
}
|
|
return exp, nil
|
|
}
|
|
|
|
func NewStore(enabledFeatures Features, engine Engine) *Store {
|
|
return &Store{
|
|
EnabledFeatures: enabledFeatures,
|
|
Engine: engine,
|
|
moduleNames: nil,
|
|
modules: map[string]*ModuleInstance{},
|
|
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 CallContext.Close.
|
|
//
|
|
// Note: Module.Validate must be called prior to instantiation.
|
|
func (s *Store) Instantiate(
|
|
ctx context.Context,
|
|
module *Module,
|
|
name string,
|
|
sys *SysContext,
|
|
functionListenerFactory experimentalapi.FunctionListenerFactory,
|
|
) (*CallContext, error) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
if err := s.requireModuleName(name); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
typeIDs, err := s.getFunctionTypeIDs(module.TypeSection)
|
|
if err != nil {
|
|
s.deleteModule(name)
|
|
return nil, err
|
|
}
|
|
|
|
importedFunctions, importedGlobals, importedTables, importedMemory, err := s.resolveImports(module)
|
|
if err != nil {
|
|
s.deleteModule(name)
|
|
return nil, err
|
|
}
|
|
|
|
tables, tableInit, err := module.buildTables(importedTables, importedGlobals,
|
|
// As of reference-types proposal, boundary check must be done after instantiation.
|
|
s.EnabledFeatures.Get(FeatureReferenceTypes))
|
|
if err != nil {
|
|
s.deleteModule(name)
|
|
return nil, err
|
|
}
|
|
globals, memory := module.buildGlobals(importedGlobals), module.buildMemory()
|
|
|
|
// If there are no module-defined functions, assume this is a host module.
|
|
var functions []*FunctionInstance
|
|
var funcSection SectionID
|
|
if module.HostFunctionSection == nil {
|
|
funcSection = SectionIDFunction
|
|
functions = module.buildFunctions(name, functionListenerFactory)
|
|
} else {
|
|
funcSection = SectionIDHostFunction
|
|
functions = module.buildHostFunctions(name, functionListenerFactory)
|
|
}
|
|
|
|
// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
|
|
m := &ModuleInstance{Name: name}
|
|
m.addSections(module, importedFunctions, functions, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection, typeIDs)
|
|
|
|
// 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.Get(FeatureReferenceTypes) {
|
|
if err = m.validateData(module.DataSection); err != nil {
|
|
s.deleteModule(name)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Plus, we are ready to compile functions.
|
|
m.Engine, err = s.Engine.NewModuleEngine(name, module, importedFunctions, functions, tables, tableInit)
|
|
if err != nil && !errors.Is(err, ErrElementOffsetOutOfBounds) {
|
|
// ErrElementOffsetOutOfBounds is not an instantiation error, but rather runtime error, so we ignore it as
|
|
// in anyway the instantiated module and engines are fine and can be used for function invocations.
|
|
// See comments on ErrElementOffsetOutOfBounds.
|
|
return nil, fmt.Errorf("compilation failed: %w", err)
|
|
}
|
|
|
|
// After engine creation, we can create the funcref element instances and initialize funcref type globals.
|
|
m.buildElementInstances(module.ElementSection)
|
|
m.Engine.InitializeFuncrefGlobals(globals)
|
|
|
|
// 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
|
|
}
|
|
|
|
// Compile the default context for calls to this module.
|
|
m.CallCtx = NewCallContext(s, m, sys)
|
|
|
|
// Execute the start function.
|
|
if module.StartSection != nil {
|
|
funcIdx := *module.StartSection
|
|
f := m.Functions[funcIdx]
|
|
if _, err = f.Module.Engine.Call(ctx, m.CallCtx, f); err != nil {
|
|
s.deleteModule(name)
|
|
return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(funcSection, funcIdx), err)
|
|
}
|
|
}
|
|
|
|
// Now that the instantiation is complete without error, add it. This makes it visible for import.
|
|
s.addModule(m)
|
|
return m.CallCtx, nil
|
|
}
|
|
|
|
// deleteModule makes the moduleName available for instantiation again.
|
|
func (s *Store) deleteModule(moduleName string) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
delete(s.modules, moduleName)
|
|
// remove this module name
|
|
for i, n := range s.moduleNames {
|
|
if n == moduleName {
|
|
s.moduleNames = append(s.moduleNames[:i], s.moduleNames[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// requireModuleName is a pre-flight check to reserve a module.
|
|
// This must be reverted on error with deleteModule if initialization fails.
|
|
func (s *Store) requireModuleName(moduleName string) error {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
for _, n := range s.moduleNames {
|
|
if n == moduleName {
|
|
return fmt.Errorf("module %s has already been instantiated", moduleName)
|
|
}
|
|
}
|
|
s.moduleNames = append(s.moduleNames, moduleName)
|
|
return nil
|
|
}
|
|
|
|
// addModule makes the module visible for import
|
|
func (s *Store) addModule(m *ModuleInstance) {
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
s.modules[m.Name] = m
|
|
}
|
|
|
|
// Module implements wazero.Runtime Module
|
|
func (s *Store) Module(moduleName string) api.Module {
|
|
if m := s.module(moduleName); m != nil {
|
|
return m.CallCtx
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *Store) module(moduleName string) *ModuleInstance {
|
|
s.mux.RLock()
|
|
defer s.mux.RUnlock()
|
|
return s.modules[moduleName]
|
|
}
|
|
|
|
func (s *Store) resolveImports(module *Module) (
|
|
importedFunctions []*FunctionInstance, importedGlobals []*GlobalInstance,
|
|
importedTables []*TableInstance, importedMemory *MemoryInstance,
|
|
err error,
|
|
) {
|
|
s.mux.RLock()
|
|
defer s.mux.RUnlock()
|
|
|
|
for idx, i := range module.ImportSection {
|
|
m, ok := s.modules[i.Module]
|
|
if !ok {
|
|
err = fmt.Errorf("module[%s] not instantiated", i.Module)
|
|
return
|
|
}
|
|
|
|
var imported *ExportInstance
|
|
imported, err = m.getExport(i.Name, i.Type)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch i.Type {
|
|
case ExternTypeFunc:
|
|
typeIndex := i.DescFunc
|
|
// TODO: this shouldn't be possible as invalid should fail validate
|
|
if int(typeIndex) >= len(module.TypeSection) {
|
|
err = errorInvalidImport(i, idx, fmt.Errorf("function type out of range"))
|
|
return
|
|
}
|
|
expectedType := module.TypeSection[i.DescFunc]
|
|
importedFunction := imported.Function
|
|
|
|
actualType := importedFunction.Type
|
|
if !expectedType.EqualsSignature(actualType.Params, actualType.Results) {
|
|
err = errorInvalidImport(i, idx, fmt.Errorf("signature mismatch: %s != %s", expectedType, actualType))
|
|
return
|
|
}
|
|
|
|
importedFunctions = append(importedFunctions, importedFunction)
|
|
case ExternTypeTable:
|
|
expected := i.DescTable
|
|
importedTable := imported.Table
|
|
if expected.Type != importedTable.Type {
|
|
err = errorInvalidImport(i, idx, fmt.Errorf("table type mismatch: %s != %s",
|
|
RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
|
|
}
|
|
|
|
if expected.Min > importedTable.Min {
|
|
err = errorMinSizeMismatch(i, idx, expected.Min, importedTable.Min)
|
|
return
|
|
}
|
|
|
|
if expected.Max != nil {
|
|
expectedMax := *expected.Max
|
|
if importedTable.Max == nil {
|
|
err = errorNoMax(i, idx, expectedMax)
|
|
return
|
|
} else if expectedMax < *importedTable.Max {
|
|
err = errorMaxSizeMismatch(i, idx, expectedMax, *importedTable.Max)
|
|
return
|
|
}
|
|
}
|
|
importedTables = append(importedTables, importedTable)
|
|
case ExternTypeMemory:
|
|
expected := i.DescMem
|
|
importedMemory = imported.Memory
|
|
|
|
if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
|
|
err = errorMinSizeMismatch(i, idx, expected.Min, importedMemory.Min)
|
|
return
|
|
}
|
|
|
|
if expected.Max < importedMemory.Max {
|
|
err = errorMaxSizeMismatch(i, idx, expected.Max, importedMemory.Max)
|
|
return
|
|
}
|
|
case ExternTypeGlobal:
|
|
expected := i.DescGlobal
|
|
importedGlobal := imported.Global
|
|
|
|
if expected.Mutable != importedGlobal.Type.Mutable {
|
|
err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t",
|
|
expected.Mutable, importedGlobal.Type.Mutable))
|
|
return
|
|
}
|
|
|
|
if expected.ValType != importedGlobal.Type.ValType {
|
|
err = errorInvalidImport(i, idx, fmt.Errorf("value type mismatch: %s != %s",
|
|
ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
|
|
return
|
|
}
|
|
importedGlobals = append(importedGlobals, importedGlobal)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func errorMinSizeMismatch(i *Import, idx int, expected, actual uint32) error {
|
|
return errorInvalidImport(i, idx, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
|
|
}
|
|
|
|
func errorNoMax(i *Import, idx int, expected uint32) error {
|
|
return errorInvalidImport(i, idx, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
|
|
}
|
|
|
|
func errorMaxSizeMismatch(i *Import, idx int, expected, actual uint32) error {
|
|
return errorInvalidImport(i, idx, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
|
|
}
|
|
|
|
func errorInvalidImport(i *Import, idx int, err error) error {
|
|
return fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, ExternTypeName(i.Type), i.Module, i.Name, err)
|
|
}
|
|
|
|
// 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 executeConstExpression(importedGlobals []*GlobalInstance, expr *ConstantExpression) (v interface{}) {
|
|
r := bytes.NewReader(expr.Data)
|
|
switch expr.Opcode {
|
|
case OpcodeI32Const:
|
|
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
|
v, _, _ = leb128.DecodeInt32(r)
|
|
case OpcodeI64Const:
|
|
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
|
v, _, _ = leb128.DecodeInt64(r)
|
|
case OpcodeF32Const:
|
|
v, _ = ieee754.DecodeFloat32(r)
|
|
case OpcodeF64Const:
|
|
v, _ = ieee754.DecodeFloat64(r)
|
|
case OpcodeGlobalGet:
|
|
id, _, _ := leb128.DecodeUint32(r)
|
|
g := importedGlobals[id]
|
|
switch g.Type.ValType {
|
|
case ValueTypeI32:
|
|
v = int32(g.Val)
|
|
case ValueTypeI64:
|
|
v = int64(g.Val)
|
|
case ValueTypeF32:
|
|
v = api.DecodeF32(g.Val)
|
|
case ValueTypeF64:
|
|
v = api.DecodeF64(g.Val)
|
|
case ValueTypeV128:
|
|
v = [2]uint64{g.Val, g.ValHi}
|
|
}
|
|
case OpcodeRefNull:
|
|
switch expr.Data[0] {
|
|
case ValueTypeExternref:
|
|
v = int64(0) // Extern reference types are opaque 64bit pointer at runtime.
|
|
case ValueTypeFuncref:
|
|
// For funcref types, the pointer value will be set by Engines, so
|
|
// here we set the "invalid function index" (-1) to indicate that this should be null reference.
|
|
v = GlobalInstanceNullFuncRefValue
|
|
}
|
|
case OpcodeRefFunc:
|
|
// For ref.func const expresssion, we temporarily store the index as value,
|
|
// and if this is the const expr for global, the value will be further downed to
|
|
// opaque pointer of the engine-specific compiled function.
|
|
v, _, _ = leb128.DecodeInt32(r)
|
|
case OpcodeVecV128Const:
|
|
v = [2]uint64{binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])}
|
|
}
|
|
return
|
|
}
|
|
|
|
// GlobalInstanceNullFuncRefValue is the temporarly value for ValueTypeFuncref globals which are initialized via ref.null.
|
|
const GlobalInstanceNullFuncRefValue int64 = -1
|
|
|
|
func (s *Store) getFunctionTypeIDs(ts []*FunctionType) ([]FunctionTypeID, error) {
|
|
// We take write-lock here as the following might end up mutating typeIDs map.
|
|
s.mux.Lock()
|
|
defer s.mux.Unlock()
|
|
ret := make([]FunctionTypeID, len(ts))
|
|
for i, t := range ts {
|
|
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) {
|
|
key := t.String()
|
|
id, ok := s.typeIDs[key]
|
|
if !ok {
|
|
l := uint32(len(s.typeIDs))
|
|
if l >= s.functionMaxTypes {
|
|
return 0, fmt.Errorf("too many function types in a store")
|
|
}
|
|
id = FunctionTypeID(len(s.typeIDs))
|
|
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 i := len(s.moduleNames) - 1; i >= 0; i-- {
|
|
// If closing this module errs, proceed anyway to close the others.
|
|
if _, e := s.modules[s.moduleNames[i]].CallCtx.close(ctx, exitCode); e != nil && err == nil {
|
|
err = e // first error
|
|
}
|
|
}
|
|
s.moduleNames = nil
|
|
s.modules = map[string]*ModuleInstance{}
|
|
return err
|
|
}
|
|
|
|
// AliasModule aliases the instantiated module named `src` as `dst`.
|
|
//
|
|
// Note: This is only used for spectests.
|
|
func (s *Store) AliasModule(src, dst string) {
|
|
s.modules[dst] = s.modules[src]
|
|
}
|