github.com/taubyte/vm-wasm-utils@v1.0.2/store.go (about)

     1  package wasm
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/binary"
     7  	"errors"
     8  	"fmt"
     9  	"reflect"
    10  	"sync"
    11  
    12  	"github.com/taubyte/vm-wasm-utils/ieee754"
    13  	"github.com/taubyte/vm-wasm-utils/leb128"
    14  	internalsys "github.com/taubyte/vm-wasm-utils/sys"
    15  	"github.com/tetratelabs/wazero/api"
    16  	experimentalapi "github.com/tetratelabs/wazero/experimental"
    17  	"github.com/tetratelabs/wazero/sys"
    18  )
    19  
    20  type (
    21  	// Store is the runtime representation of "instantiated" Wasm module and objects.
    22  	// Multiple modules can be instantiated within a single store, and each instance,
    23  	// (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection.
    24  	//
    25  	// Every type whose name ends with "Instance" suffix belongs to exactly one store.
    26  	//
    27  	// Note that store is not thread (concurrency) safe, meaning that using single Store
    28  	// via multiple goroutines might result in race conditions. In that case, the invocation
    29  	// and access to any methods and field of Store must be guarded by mutex.
    30  	//
    31  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
    32  	Store struct {
    33  		// EnabledFeatures are read-only to allow optimizations.
    34  		EnabledFeatures Features
    35  
    36  		// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
    37  		Engine Engine
    38  
    39  		// typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
    40  		// do type-checks on indirect function calls.
    41  		typeIDs map[string]FunctionTypeID
    42  
    43  		// functionMaxTypes represents the limit on the number of function types in a store.
    44  		// Note: this is fixed to 2^27 but have this a field for testability.
    45  		functionMaxTypes uint32
    46  
    47  		// namespaces are all Namespace instances for this store including the default one.
    48  		namespaces []*Namespace // guarded by mux
    49  
    50  		// mux is used to guard the fields from concurrent access.
    51  		mux sync.RWMutex
    52  	}
    53  
    54  	// ModuleInstance represents instantiated wasm module.
    55  	// The difference from the spec is that in wazero, a ModuleInstance holds pointers
    56  	// to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience.
    57  	//
    58  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst
    59  	ModuleInstance struct {
    60  		Name      string
    61  		Exports   map[string]*ExportInstance
    62  		Functions []*FunctionInstance
    63  		Globals   []*GlobalInstance
    64  		// Memory is set when Module.MemorySection had a memory, regardless of whether it was exported.
    65  		Memory *MemoryInstance
    66  		Tables []*TableInstance
    67  		Types  []*FunctionType
    68  
    69  		// CallCtx holds default function call context from this function instance.
    70  		CallCtx *CallContext
    71  
    72  		// Engine implements function calls for this module.
    73  		Engine ModuleEngine
    74  
    75  		// TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store.
    76  		// This is necessary to achieve fast runtime type checking for indirect function calls at runtime.
    77  		TypeIDs []FunctionTypeID
    78  
    79  		// DataInstances holds data segments bytes of the module.
    80  		// This is only used by bulk memory operations.
    81  		//
    82  		// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
    83  		DataInstances []DataInstance
    84  
    85  		// ElementInstances holds the element instance, and each holds the references to either functions
    86  		// or external objects (unimplemented).
    87  		ElementInstances []ElementInstance
    88  	}
    89  
    90  	// DataInstance holds bytes corresponding to the data segment in a module.
    91  	//
    92  	// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
    93  	DataInstance = []byte
    94  
    95  	// ExportInstance represents an exported instance in a Store.
    96  	// The difference from the spec is that in wazero, a ExportInstance holds pointers
    97  	// to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience.
    98  	//
    99  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-exportinst
   100  	ExportInstance struct {
   101  		Type     ExternType
   102  		Function *FunctionInstance
   103  		Global   *GlobalInstance
   104  		Memory   *MemoryInstance
   105  		Table    *TableInstance
   106  	}
   107  
   108  	// FunctionInstance represents a function instance in a Store.
   109  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-instances%E2%91%A0
   110  	FunctionInstance struct {
   111  		// IsHostFunction is the data returned by the same field documented on
   112  		// wasm.Code.
   113  		IsHostFunction bool
   114  
   115  		// Kind describes how this function should be called.
   116  		Kind FunctionKind
   117  
   118  		// Type is the signature of this function.
   119  		Type *FunctionType
   120  
   121  		// LocalTypes holds types of locals, set when Kind == FunctionKindWasm
   122  		LocalTypes []ValueType
   123  
   124  		// Body is the function body in WebAssembly Binary Format, set when Kind == FunctionKindWasm
   125  		Body []byte
   126  
   127  		// GoFunc holds the runtime representation of host functions.
   128  		// This is nil when Kind == FunctionKindWasm. Otherwise, all the above fields are ignored as they are
   129  		// specific to Wasm functions.
   130  		GoFunc *reflect.Value
   131  
   132  		// Fields above here are settable prior to instantiation. Below are set by the Store during instantiation.
   133  
   134  		// ModuleInstance holds the pointer to the module instance to which this function belongs.
   135  		Module *ModuleInstance
   136  
   137  		// TypeID is assigned by a store for FunctionType.
   138  		TypeID FunctionTypeID
   139  
   140  		// Idx holds the index of this function instance in the function index namespace (beginning with imports).
   141  		Idx Index
   142  
   143  		// definition is known at compile time.
   144  		definition api.FunctionDefinition
   145  
   146  		// FunctionListener holds a listener to notify when this function is called.
   147  		FunctionListener experimentalapi.FunctionListener
   148  	}
   149  
   150  	// GlobalInstance represents a global instance in a store.
   151  	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0
   152  	GlobalInstance struct {
   153  		Type *GlobalType
   154  		// Val holds a 64-bit representation of the actual value.
   155  		Val uint64
   156  		// ValHi is only used for vector type globals, and holds the higher bits of the vector.
   157  		ValHi uint64
   158  		// ^^ TODO: this should be guarded with atomics when mutable
   159  	}
   160  
   161  	// FunctionTypeID is a uniquely assigned integer for a function type.
   162  	// This is wazero specific runtime object and specific to a store,
   163  	// and used at runtime to do type-checks on indirect function calls.
   164  	FunctionTypeID uint32
   165  )
   166  
   167  // Definition implements the same method as documented on api.FunctionDefinition.
   168  func (f *FunctionInstance) Definition() api.FunctionDefinition {
   169  	return f.definition
   170  }
   171  
   172  // The wazero specific limitations described at RATIONALE.md.
   173  const maximumFunctionTypes = 1 << 27
   174  
   175  // addSections adds section elements to the ModuleInstance
   176  func (m *ModuleInstance) addSections(module *Module, importedFunctions, functions []*FunctionInstance,
   177  	importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance,
   178  	types []*FunctionType) {
   179  
   180  	m.Types = types
   181  	m.Functions = append(importedFunctions, functions...)
   182  	m.Globals = append(importedGlobals, globals...)
   183  	m.Tables = tables
   184  
   185  	if importedMemory != nil {
   186  		m.Memory = importedMemory
   187  	} else {
   188  		m.Memory = memory
   189  	}
   190  
   191  	m.BuildExports(module.ExportSection)
   192  	m.buildDataInstances(module.DataSection)
   193  }
   194  
   195  func (m *ModuleInstance) buildDataInstances(segments []*DataSegment) {
   196  	for _, d := range segments {
   197  		m.DataInstances = append(m.DataInstances, d.Init)
   198  	}
   199  }
   200  
   201  func (m *ModuleInstance) buildElementInstances(elements []*ElementSegment) {
   202  	m.ElementInstances = make([]ElementInstance, len(elements))
   203  	for i, elm := range elements {
   204  		if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive {
   205  			// Only passive elements can be access as element instances.
   206  			// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
   207  			m.ElementInstances[i] = *m.Engine.CreateFuncElementInstance(elm.Init)
   208  		}
   209  	}
   210  }
   211  
   212  func (m *ModuleInstance) BuildExports(exports []*Export) {
   213  	m.Exports = make(map[string]*ExportInstance, len(exports))
   214  	for _, exp := range exports {
   215  		index := exp.Index
   216  		var ei *ExportInstance
   217  		switch exp.Type {
   218  		case ExternTypeFunc:
   219  			ei = &ExportInstance{Type: exp.Type, Function: m.Functions[index]}
   220  		case ExternTypeGlobal:
   221  			ei = &ExportInstance{Type: exp.Type, Global: m.Globals[index]}
   222  		case ExternTypeMemory:
   223  			ei = &ExportInstance{Type: exp.Type, Memory: m.Memory}
   224  		case ExternTypeTable:
   225  			ei = &ExportInstance{Type: exp.Type, Table: m.Tables[index]}
   226  		}
   227  
   228  		// We already validated the duplicates during module validation phase.
   229  		m.Exports[exp.Name] = ei
   230  	}
   231  }
   232  
   233  // validateData ensures that data segments are valid in terms of memory boundary.
   234  // Note: this is used only when bulk-memory/reference type feature is disabled.
   235  func (m *ModuleInstance) validateData(data []*DataSegment) (err error) {
   236  	for i, d := range data {
   237  		if !d.IsPassive() {
   238  			offset := int(executeConstExpression(m.Globals, d.OffsetExpression).(int32))
   239  			ceil := offset + len(d.Init)
   240  			if offset < 0 || ceil > len(m.Memory.Buffer) {
   241  				return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
   242  			}
   243  		}
   244  	}
   245  	return
   246  }
   247  
   248  // applyData uses the given data segments and mutate the memory according to the initial contents on it.
   249  // This is called after all the validation phase passes and out of bounds memory access error here is
   250  // not a validation error, but rather a runtime error.
   251  func (m *ModuleInstance) applyData(data []*DataSegment) error {
   252  	for i, d := range data {
   253  		if !d.IsPassive() {
   254  			offset := executeConstExpression(m.Globals, d.OffsetExpression).(int32)
   255  			if offset < 0 || int(offset)+len(d.Init) > len(m.Memory.Buffer) {
   256  				return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
   257  			}
   258  			copy(m.Memory.Buffer[offset:], d.Init)
   259  		}
   260  	}
   261  	return nil
   262  }
   263  
   264  // GetExport returns an export of the given name and type or errs if not exported or the wrong type.
   265  func (m *ModuleInstance) getExport(name string, et ExternType) (*ExportInstance, error) {
   266  	exp, ok := m.Exports[name]
   267  	if !ok {
   268  		return nil, fmt.Errorf("%q is not exported in module %q", name, m.Name)
   269  	}
   270  	if exp.Type != et {
   271  		return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.Name, ExternTypeName(exp.Type), ExternTypeName(et))
   272  	}
   273  	return exp, nil
   274  }
   275  
   276  func NewStore(enabledFeatures Features, engine Engine) (*Store, *Namespace) {
   277  	ns := newNamespace()
   278  	return &Store{
   279  		EnabledFeatures:  enabledFeatures,
   280  		Engine:           engine,
   281  		namespaces:       []*Namespace{ns},
   282  		typeIDs:          map[string]FunctionTypeID{},
   283  		functionMaxTypes: maximumFunctionTypes,
   284  	}, ns
   285  }
   286  
   287  // NewNamespace implements the same method as documented on wazero.Runtime.
   288  func (s *Store) NewNamespace(_ context.Context) *Namespace {
   289  	// TODO: The above context isn't yet used. If it is, ensure it defaults to context.Background.
   290  
   291  	ns := newNamespace()
   292  	s.mux.Lock()
   293  	defer s.mux.Unlock()
   294  	s.namespaces = append(s.namespaces, ns)
   295  	return ns
   296  }
   297  
   298  // Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under
   299  // different names safely and concurrently.
   300  //
   301  // * ctx: the default context used for function calls.
   302  // * name: the name of the module.
   303  // * sys: the system context, which will be closed (SysContext.Close) on CallContext.Close.
   304  //
   305  // Note: Module.Validate must be called prior to instantiation.
   306  func (s *Store) Instantiate(
   307  	ctx context.Context,
   308  	ns *Namespace,
   309  	module *Module,
   310  	name string,
   311  	sys *internalsys.Context,
   312  	listeners []experimentalapi.FunctionListener,
   313  ) (*CallContext, error) {
   314  	if ctx == nil {
   315  		ctx = context.Background()
   316  	}
   317  
   318  	// Collect any imported modules to avoid locking the namespace too long.
   319  	importedModuleNames := map[string]struct{}{}
   320  	for _, i := range module.ImportSection {
   321  		importedModuleNames[i.Module] = struct{}{}
   322  	}
   323  
   324  	// Read-Lock the namespace and ensure imports needed are present.
   325  	importedModules, err := ns.requireModules(importedModuleNames)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	// Write-Lock the namespace and claim the name of the current module.
   331  	if err = ns.requireModuleName(name); err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	// Instantiate the module and add it to the namespace so that other modules can import it.
   336  	if callCtx, err := s.instantiate(ctx, ns, module, name, sys, listeners, importedModules); err != nil {
   337  		ns.deleteModule(name)
   338  		return nil, err
   339  	} else {
   340  		// Now that the instantiation is complete without error, add it.
   341  		// This makes the module visible for import, and ensures it is closed when the namespace is.
   342  		ns.addModule(callCtx.module)
   343  		return callCtx, nil
   344  	}
   345  }
   346  
   347  func (s *Store) instantiate(
   348  	ctx context.Context,
   349  	ns *Namespace,
   350  	module *Module,
   351  	name string,
   352  	sysCtx *internalsys.Context,
   353  	listeners []experimentalapi.FunctionListener,
   354  	modules map[string]*ModuleInstance,
   355  ) (*CallContext, error) {
   356  	typeIDs, err := s.getFunctionTypeIDs(module.TypeSection)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  
   361  	importedFunctions, importedGlobals, importedTables, importedMemory, err := resolveImports(module, modules)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  
   366  	tables, tableInit, err := module.buildTables(importedTables, importedGlobals,
   367  		// As of reference-types proposal, boundary check must be done after instantiation.
   368  		s.EnabledFeatures.Get(FeatureReferenceTypes))
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  	globals, memory := module.buildGlobals(importedGlobals), module.buildMemory()
   373  
   374  	m := &ModuleInstance{Name: name, TypeIDs: typeIDs}
   375  	functions := m.BuildFunctions(module, listeners)
   376  
   377  	// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
   378  	m.addSections(module, importedFunctions, functions, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection)
   379  
   380  	// As of reference types proposal, data segment validation must happen after instantiation,
   381  	// and the side effect must persist even if there's out of bounds error after instantiation.
   382  	// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405
   383  	if !s.EnabledFeatures.Get(FeatureReferenceTypes) {
   384  		if err = m.validateData(module.DataSection); err != nil {
   385  			return nil, err
   386  		}
   387  	}
   388  
   389  	// Plus, we are ready to compile functions.
   390  	m.Engine, err = s.Engine.NewModuleEngine(name, module, importedFunctions, functions, tables, tableInit)
   391  	if err != nil && !errors.Is(err, ErrElementOffsetOutOfBounds) {
   392  		// ErrElementOffsetOutOfBounds is not an instantiation error, but rather runtime error, so we ignore it as
   393  		// in anyway the instantiated module and engines are fine and can be used for function invocations.
   394  		// See comments on ErrElementOffsetOutOfBounds.
   395  		return nil, fmt.Errorf("compilation failed: %w", err)
   396  	}
   397  
   398  	// After engine creation, we can create the funcref element instances and initialize funcref type globals.
   399  	m.buildElementInstances(module.ElementSection)
   400  	m.Engine.InitializeFuncrefGlobals(globals)
   401  
   402  	// Now all the validation passes, we are safe to mutate memory instances (possibly imported ones).
   403  	if err = m.applyData(module.DataSection); err != nil {
   404  		return nil, err
   405  	}
   406  
   407  	// Compile the default context for calls to this module.
   408  	m.CallCtx = NewCallContext(ns, m, sysCtx)
   409  
   410  	// Execute the start function.
   411  	if module.StartSection != nil {
   412  		funcIdx := *module.StartSection
   413  		f := m.Functions[funcIdx]
   414  		_, err = f.Module.Engine.Call(ctx, m.CallCtx, f)
   415  
   416  		if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error!
   417  			return nil, exitErr
   418  		} else if err != nil {
   419  			return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err)
   420  		}
   421  	}
   422  
   423  	return m.CallCtx, nil
   424  }
   425  
   426  func resolveImports(module *Module, modules map[string]*ModuleInstance) (
   427  	importedFunctions []*FunctionInstance,
   428  	importedGlobals []*GlobalInstance,
   429  	importedTables []*TableInstance,
   430  	importedMemory *MemoryInstance,
   431  	err error,
   432  ) {
   433  	for idx, i := range module.ImportSection {
   434  		m, ok := modules[i.Module]
   435  		if !ok {
   436  			err = fmt.Errorf("module[%s] not instantiated", i.Module)
   437  			return
   438  		}
   439  
   440  		var imported *ExportInstance
   441  		imported, err = m.getExport(i.Name, i.Type)
   442  		if err != nil {
   443  			return
   444  		}
   445  
   446  		switch i.Type {
   447  		case ExternTypeFunc:
   448  			typeIndex := i.DescFunc
   449  			// TODO: this shouldn't be possible as invalid should fail validate
   450  			if int(typeIndex) >= len(module.TypeSection) {
   451  				err = errorInvalidImport(i, idx, fmt.Errorf("function type out of range"))
   452  				return
   453  			}
   454  			expectedType := module.TypeSection[i.DescFunc]
   455  			importedFunction := imported.Function
   456  
   457  			d := importedFunction.Definition()
   458  			if !expectedType.EqualsSignature(d.ParamTypes(), d.ResultTypes()) {
   459  				actualType := &FunctionType{Params: d.ParamTypes(), Results: d.ResultTypes()}
   460  				err = errorInvalidImport(i, idx, fmt.Errorf("signature mismatch: %s != %s", expectedType, actualType))
   461  				return
   462  			}
   463  
   464  			importedFunctions = append(importedFunctions, importedFunction)
   465  		case ExternTypeTable:
   466  			expected := i.DescTable
   467  			importedTable := imported.Table
   468  			if expected.Type != importedTable.Type {
   469  				err = errorInvalidImport(i, idx, fmt.Errorf("table type mismatch: %s != %s",
   470  					RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
   471  			}
   472  
   473  			if expected.Min > importedTable.Min {
   474  				err = errorMinSizeMismatch(i, idx, expected.Min, importedTable.Min)
   475  				return
   476  			}
   477  
   478  			if expected.Max != nil {
   479  				expectedMax := *expected.Max
   480  				if importedTable.Max == nil {
   481  					err = errorNoMax(i, idx, expectedMax)
   482  					return
   483  				} else if expectedMax < *importedTable.Max {
   484  					err = errorMaxSizeMismatch(i, idx, expectedMax, *importedTable.Max)
   485  					return
   486  				}
   487  			}
   488  			importedTables = append(importedTables, importedTable)
   489  		case ExternTypeMemory:
   490  			expected := i.DescMem
   491  			importedMemory = imported.Memory
   492  
   493  			if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
   494  				err = errorMinSizeMismatch(i, idx, expected.Min, importedMemory.Min)
   495  				return
   496  			}
   497  
   498  			if expected.Max < importedMemory.Max {
   499  				err = errorMaxSizeMismatch(i, idx, expected.Max, importedMemory.Max)
   500  				return
   501  			}
   502  		case ExternTypeGlobal:
   503  			expected := i.DescGlobal
   504  			importedGlobal := imported.Global
   505  
   506  			if expected.Mutable != importedGlobal.Type.Mutable {
   507  				err = errorInvalidImport(i, idx, fmt.Errorf("mutability mismatch: %t != %t",
   508  					expected.Mutable, importedGlobal.Type.Mutable))
   509  				return
   510  			}
   511  
   512  			if expected.ValType != importedGlobal.Type.ValType {
   513  				err = errorInvalidImport(i, idx, fmt.Errorf("value type mismatch: %s != %s",
   514  					ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
   515  				return
   516  			}
   517  			importedGlobals = append(importedGlobals, importedGlobal)
   518  		}
   519  	}
   520  	return
   521  }
   522  
   523  func errorMinSizeMismatch(i *Import, idx int, expected, actual uint32) error {
   524  	return errorInvalidImport(i, idx, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
   525  }
   526  
   527  func errorNoMax(i *Import, idx int, expected uint32) error {
   528  	return errorInvalidImport(i, idx, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
   529  }
   530  
   531  func errorMaxSizeMismatch(i *Import, idx int, expected, actual uint32) error {
   532  	return errorInvalidImport(i, idx, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
   533  }
   534  
   535  func errorInvalidImport(i *Import, idx int, err error) error {
   536  	return fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, ExternTypeName(i.Type), i.Module, i.Name, err)
   537  }
   538  
   539  // Global initialization constant expression can only reference the imported globals.
   540  // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
   541  func executeConstExpression(importedGlobals []*GlobalInstance, expr *ConstantExpression) (v interface{}) {
   542  	r := bytes.NewReader(expr.Data)
   543  	switch expr.Opcode {
   544  	case OpcodeI32Const:
   545  		// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
   546  		v, _, _ = leb128.DecodeInt32(r)
   547  	case OpcodeI64Const:
   548  		// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
   549  		v, _, _ = leb128.DecodeInt64(r)
   550  	case OpcodeF32Const:
   551  		v, _ = ieee754.DecodeFloat32(r)
   552  	case OpcodeF64Const:
   553  		v, _ = ieee754.DecodeFloat64(r)
   554  	case OpcodeGlobalGet:
   555  		id, _, _ := leb128.DecodeUint32(r)
   556  		g := importedGlobals[id]
   557  		switch g.Type.ValType {
   558  		case ValueTypeI32:
   559  			v = int32(g.Val)
   560  		case ValueTypeI64:
   561  			v = int64(g.Val)
   562  		case ValueTypeF32:
   563  			v = api.DecodeF32(g.Val)
   564  		case ValueTypeF64:
   565  			v = api.DecodeF64(g.Val)
   566  		case ValueTypeV128:
   567  			v = [2]uint64{g.Val, g.ValHi}
   568  		}
   569  	case OpcodeRefNull:
   570  		switch expr.Data[0] {
   571  		case ValueTypeExternref:
   572  			v = int64(0) // Extern reference types are opaque 64bit pointer at runtime.
   573  		case ValueTypeFuncref:
   574  			// For funcref types, the pointer value will be set by Engines, so
   575  			// here we set the "invalid function index" (-1) to indicate that this should be null reference.
   576  			v = GlobalInstanceNullFuncRefValue
   577  		}
   578  	case OpcodeRefFunc:
   579  		// For ref.func const expression, we temporarily store the index as value,
   580  		// and if this is the const expr for global, the value will be further downed to
   581  		// opaque pointer of the engine-specific compiled function.
   582  		v, _, _ = leb128.DecodeUint32(r)
   583  	case OpcodeVecV128Const:
   584  		v = [2]uint64{binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])}
   585  	}
   586  	return
   587  }
   588  
   589  // GlobalInstanceNullFuncRefValue is the temporary value for ValueTypeFuncref globals which are initialized via ref.null.
   590  const GlobalInstanceNullFuncRefValue int64 = -1
   591  
   592  func (s *Store) getFunctionTypeIDs(ts []*FunctionType) ([]FunctionTypeID, error) {
   593  	// We take write-lock here as the following might end up mutating typeIDs map.
   594  	s.mux.Lock()
   595  	defer s.mux.Unlock()
   596  	ret := make([]FunctionTypeID, len(ts))
   597  	for i, t := range ts {
   598  		inst, err := s.getFunctionTypeID(t)
   599  		if err != nil {
   600  			return nil, err
   601  		}
   602  		ret[i] = inst
   603  	}
   604  	return ret, nil
   605  }
   606  
   607  func (s *Store) getFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
   608  	key := t.String()
   609  	id, ok := s.typeIDs[key]
   610  	if !ok {
   611  		l := uint32(len(s.typeIDs))
   612  		if l >= s.functionMaxTypes {
   613  			return 0, fmt.Errorf("too many function types in a store")
   614  		}
   615  		id = FunctionTypeID(len(s.typeIDs))
   616  		s.typeIDs[key] = id
   617  	}
   618  	return id, nil
   619  }
   620  
   621  // CloseWithExitCode implements the same method as documented on wazero.Runtime.
   622  func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
   623  	s.mux.Lock()
   624  	defer s.mux.Unlock()
   625  	// Close modules in reverse initialization order.
   626  	for i := len(s.namespaces) - 1; i >= 0; i-- {
   627  		// If closing this namespace errs, proceed anyway to close the others.
   628  		if e := s.namespaces[i].CloseWithExitCode(ctx, exitCode); e != nil && err == nil {
   629  			err = e // first error
   630  		}
   631  	}
   632  	s.namespaces = nil
   633  	s.typeIDs = nil
   634  	return
   635  }