store.go

  1package wasm
  2
  3import (
  4	"context"
  5	"encoding/binary"
  6	"errors"
  7	"fmt"
  8	"sync"
  9	"sync/atomic"
 10
 11	"github.com/tetratelabs/wazero/api"
 12	"github.com/tetratelabs/wazero/experimental"
 13	"github.com/tetratelabs/wazero/internal/expctxkeys"
 14	"github.com/tetratelabs/wazero/internal/internalapi"
 15	"github.com/tetratelabs/wazero/internal/leb128"
 16	internalsys "github.com/tetratelabs/wazero/internal/sys"
 17	"github.com/tetratelabs/wazero/sys"
 18)
 19
 20// nameToModuleShrinkThreshold is the size the nameToModule map can grow to
 21// before it starts to be monitored for shrinking.
 22// The capacity will never be smaller than this once the threshold is met.
 23const nameToModuleShrinkThreshold = 100
 24
 25type (
 26	// Store is the runtime representation of "instantiated" Wasm module and objects.
 27	// Multiple modules can be instantiated within a single store, and each instance,
 28	// (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection.
 29	//
 30	// Every type whose name ends with "Instance" suffix belongs to exactly one store.
 31	//
 32	// Note that store is not thread (concurrency) safe, meaning that using single Store
 33	// via multiple goroutines might result in race conditions. In that case, the invocation
 34	// and access to any methods and field of Store must be guarded by mutex.
 35	//
 36	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
 37	Store struct {
 38		// moduleList ensures modules are closed in reverse initialization order.
 39		moduleList *ModuleInstance // guarded by mux
 40
 41		// nameToModule holds the instantiated Wasm modules by module name from Instantiate.
 42		// It ensures no race conditions instantiating two modules of the same name.
 43		nameToModule map[string]*ModuleInstance // guarded by mux
 44
 45		// nameToModuleCap tracks the growth of the nameToModule map in order to
 46		// track when to shrink it.
 47		nameToModuleCap int // guarded by mux
 48
 49		// EnabledFeatures are read-only to allow optimizations.
 50		EnabledFeatures api.CoreFeatures
 51
 52		// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
 53		Engine Engine
 54
 55		// typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
 56		// do type-checks on indirect function calls.
 57		typeIDs map[string]FunctionTypeID
 58
 59		// functionMaxTypes represents the limit on the number of function types in a store.
 60		// Note: this is fixed to 2^27 but have this a field for testability.
 61		functionMaxTypes uint32
 62
 63		// mux is used to guard the fields from concurrent access.
 64		mux sync.RWMutex
 65	}
 66
 67	// ModuleInstance represents instantiated wasm module.
 68	// The difference from the spec is that in wazero, a ModuleInstance holds pointers
 69	// to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience.
 70	//
 71	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst
 72	//
 73	// This implements api.Module.
 74	ModuleInstance struct {
 75		internalapi.WazeroOnlyType
 76
 77		ModuleName     string
 78		Exports        map[string]*Export
 79		Globals        []*GlobalInstance
 80		MemoryInstance *MemoryInstance
 81		Tables         []*TableInstance
 82
 83		// Engine implements function calls for this module.
 84		Engine ModuleEngine
 85
 86		// TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store.
 87		// This is necessary to achieve fast runtime type checking for indirect function calls at runtime.
 88		TypeIDs []FunctionTypeID
 89
 90		// DataInstances holds data segments bytes of the module.
 91		// This is only used by bulk memory operations.
 92		//
 93		// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
 94		DataInstances []DataInstance
 95
 96		// ElementInstances holds the element instance, and each holds the references to either functions
 97		// or external objects (unimplemented).
 98		ElementInstances []ElementInstance
 99
100		// Sys is exposed for use in special imports such as WASI, assemblyscript.
101		//
102		// # Notes
103		//
104		//   - This is a part of ModuleInstance so that scope and Close is coherent.
105		//   - This is not exposed outside this repository (as a host function
106		//	  parameter) because we haven't thought through capabilities based
107		//	  security implications.
108		Sys *internalsys.Context
109
110		// Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
111		//
112		// The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
113		//
114		// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
115		// See /RATIONALE.md
116		Closed atomic.Uint64
117
118		// CodeCloser is non-nil when the code should be closed after this module.
119		CodeCloser api.Closer
120
121		// s is the Store on which this module is instantiated.
122		s *Store
123		// prev and next hold the nodes in the linked list of ModuleInstance held by Store.
124		prev, next *ModuleInstance
125		// Source is a pointer to the Module from which this ModuleInstance derives.
126		Source *Module
127
128		// CloseNotifier is an experimental hook called once on close.
129		CloseNotifier experimental.CloseNotifier
130	}
131
132	// DataInstance holds bytes corresponding to the data segment in a module.
133	//
134	// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
135	DataInstance = []byte
136
137	// GlobalInstance represents a global instance in a store.
138	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0
139	GlobalInstance struct {
140		Type GlobalType
141		// Val holds a 64-bit representation of the actual value.
142		// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
143		Val uint64
144		// ValHi is only used for vector type globals, and holds the higher bits of the vector.
145		// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
146		ValHi uint64
147		// Me is the module engine that owns this global instance.
148		// The .Val and .ValHi fields are only valid when me is nil.
149		// If me is non-nil, the value is stored in the module engine.
150		Me    ModuleEngine
151		Index Index
152	}
153
154	// FunctionTypeID is a uniquely assigned integer for a function type.
155	// This is wazero specific runtime object and specific to a store,
156	// and used at runtime to do type-checks on indirect function calls.
157	FunctionTypeID uint32
158)
159
160// The wazero specific limitations described at RATIONALE.md.
161const maximumFunctionTypes = 1 << 27
162
163// GetFunctionTypeID is used by emscripten.
164func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID {
165	id, err := m.s.GetFunctionTypeID(t)
166	if err != nil {
167		// This is not recoverable in practice since the only error GetFunctionTypeID returns is
168		// when there's too many function types in the store.
169		panic(err)
170	}
171	return id
172}
173
174func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) {
175	m.ElementInstances = make([][]Reference, len(elements))
176	for i, elm := range elements {
177		if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive {
178			// Only passive elements can be access as element instances.
179			// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
180			inits := elm.Init
181			inst := make([]Reference, len(inits))
182			m.ElementInstances[i] = inst
183			for j, idx := range inits {
184				if index, ok := unwrapElementInitGlobalReference(idx); ok {
185					global := m.Globals[index]
186					inst[j] = Reference(global.Val)
187				} else {
188					if idx != ElementInitNullReference {
189						inst[j] = m.Engine.FunctionInstanceReference(idx)
190					}
191				}
192			}
193		}
194	}
195}
196
197func (m *ModuleInstance) applyElements(elems []ElementSegment) {
198	for elemI := range elems {
199		elem := &elems[elemI]
200		if !elem.IsActive() ||
201			// Per https://github.com/WebAssembly/spec/issues/1427 init can be no-op.
202			len(elem.Init) == 0 {
203			continue
204		}
205		var offset uint32
206		if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
207			// Ignore error as it's already validated.
208			globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
209			global := m.Globals[globalIdx]
210			offset = uint32(global.Val)
211		} else {
212			// Ignore error as it's already validated.
213			o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
214			offset = uint32(o)
215		}
216
217		table := m.Tables[elem.TableIndex]
218		references := table.References
219		if int(offset)+len(elem.Init) > len(references) {
220			// ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length.
221			// Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal,
222			// this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error.
223			// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
224			//
225			// In wazero, we ignore it since in any way, the instantiated module and engines are fine and can be used
226			// for function invocations.
227			return
228		}
229
230		if table.Type == RefTypeExternref {
231			for i := 0; i < len(elem.Init); i++ {
232				references[offset+uint32(i)] = Reference(0)
233			}
234		} else {
235			for i, init := range elem.Init {
236				if init == ElementInitNullReference {
237					continue
238				}
239
240				var ref Reference
241				if index, ok := unwrapElementInitGlobalReference(init); ok {
242					global := m.Globals[index]
243					ref = Reference(global.Val)
244				} else {
245					ref = m.Engine.FunctionInstanceReference(index)
246				}
247				references[offset+uint32(i)] = ref
248			}
249		}
250	}
251}
252
253// validateData ensures that data segments are valid in terms of memory boundary.
254// Note: this is used only when bulk-memory/reference type feature is disabled.
255func (m *ModuleInstance) validateData(data []DataSegment) (err error) {
256	for i := range data {
257		d := &data[i]
258		if !d.IsPassive() {
259			offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression))
260			ceil := offset + len(d.Init)
261			if offset < 0 || ceil > len(m.MemoryInstance.Buffer) {
262				return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
263			}
264		}
265	}
266	return
267}
268
269// applyData uses the given data segments and mutate the memory according to the initial contents on it
270// and populate the `DataInstances`. This is called after all the validation phase passes and out of
271// bounds memory access error here is not a validation error, but rather a runtime error.
272func (m *ModuleInstance) applyData(data []DataSegment) error {
273	m.DataInstances = make([][]byte, len(data))
274	for i := range data {
275		d := &data[i]
276		m.DataInstances[i] = d.Init
277		if !d.IsPassive() {
278			offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression)
279			if offset < 0 || int(offset)+len(d.Init) > len(m.MemoryInstance.Buffer) {
280				return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
281			}
282			copy(m.MemoryInstance.Buffer[offset:], d.Init)
283		}
284	}
285	return nil
286}
287
288// GetExport returns an export of the given name and type or errs if not exported or the wrong type.
289func (m *ModuleInstance) getExport(name string, et ExternType) (*Export, error) {
290	exp, ok := m.Exports[name]
291	if !ok {
292		return nil, fmt.Errorf("%q is not exported in module %q", name, m.ModuleName)
293	}
294	if exp.Type != et {
295		return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.ModuleName, ExternTypeName(exp.Type), ExternTypeName(et))
296	}
297	return exp, nil
298}
299
300func NewStore(enabledFeatures api.CoreFeatures, engine Engine) *Store {
301	return &Store{
302		nameToModule:     map[string]*ModuleInstance{},
303		nameToModuleCap:  nameToModuleShrinkThreshold,
304		EnabledFeatures:  enabledFeatures,
305		Engine:           engine,
306		typeIDs:          map[string]FunctionTypeID{},
307		functionMaxTypes: maximumFunctionTypes,
308	}
309}
310
311// Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under
312// different names safely and concurrently.
313//
314// * ctx: the default context used for function calls.
315// * name: the name of the module.
316// * sys: the system context, which will be closed (SysContext.Close) on ModuleInstance.Close.
317//
318// Note: Module.Validate must be called prior to instantiation.
319func (s *Store) Instantiate(
320	ctx context.Context,
321	module *Module,
322	name string,
323	sys *internalsys.Context,
324	typeIDs []FunctionTypeID,
325) (*ModuleInstance, error) {
326	// Instantiate the module and add it to the store so that other modules can import it.
327	m, err := s.instantiate(ctx, module, name, sys, typeIDs)
328	if err != nil {
329		return nil, err
330	}
331
332	// Now that the instantiation is complete without error, add it.
333	if err = s.registerModule(m); err != nil {
334		_ = m.Close(ctx)
335		return nil, err
336	}
337	return m, nil
338}
339
340func (s *Store) instantiate(
341	ctx context.Context,
342	module *Module,
343	name string,
344	sysCtx *internalsys.Context,
345	typeIDs []FunctionTypeID,
346) (m *ModuleInstance, err error) {
347	m = &ModuleInstance{ModuleName: name, TypeIDs: typeIDs, Sys: sysCtx, s: s, Source: module}
348
349	m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection))
350	m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection))
351	m.Engine, err = s.Engine.NewModuleEngine(module, m)
352	if err != nil {
353		return nil, err
354	}
355
356	if err = m.resolveImports(ctx, module); err != nil {
357		return nil, err
358	}
359
360	err = m.buildTables(module,
361		// As of reference-types proposal, boundary check must be done after instantiation.
362		s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes))
363	if err != nil {
364		return nil, err
365	}
366
367	allocator, _ := ctx.Value(expctxkeys.MemoryAllocatorKey{}).(experimental.MemoryAllocator)
368
369	m.buildGlobals(module, m.Engine.FunctionInstanceReference)
370	m.buildMemory(module, allocator)
371	m.Exports = module.Exports
372	for _, exp := range m.Exports {
373		if exp.Type == ExternTypeTable {
374			t := m.Tables[exp.Index]
375			t.involvingModuleInstances = append(t.involvingModuleInstances, m)
376		}
377	}
378
379	// As of reference types proposal, data segment validation must happen after instantiation,
380	// and the side effect must persist even if there's out of bounds error after instantiation.
381	// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405
382	if !s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) {
383		if err = m.validateData(module.DataSection); err != nil {
384			return nil, err
385		}
386	}
387
388	// After engine creation, we can create the funcref element instances and initialize funcref type globals.
389	m.buildElementInstances(module.ElementSection)
390
391	// Now all the validation passes, we are safe to mutate memory instances (possibly imported ones).
392	if err = m.applyData(module.DataSection); err != nil {
393		return nil, err
394	}
395
396	m.applyElements(module.ElementSection)
397
398	m.Engine.DoneInstantiation()
399
400	// Execute the start function.
401	if module.StartSection != nil {
402		funcIdx := *module.StartSection
403		ce := m.Engine.NewFunction(funcIdx)
404		_, err = ce.Call(ctx)
405		if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error!
406			return nil, exitErr
407		} else if err != nil {
408			return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err)
409		}
410	}
411	return
412}
413
414func (m *ModuleInstance) resolveImports(ctx context.Context, module *Module) (err error) {
415	// Check if ctx contains an ImportResolver.
416	resolveImport, _ := ctx.Value(expctxkeys.ImportResolverKey{}).(experimental.ImportResolver)
417
418	for moduleName, imports := range module.ImportPerModule {
419		var importedModule *ModuleInstance
420		if resolveImport != nil {
421			if v := resolveImport(moduleName); v != nil {
422				importedModule = v.(*ModuleInstance)
423			}
424		}
425		if importedModule == nil {
426			importedModule, err = m.s.module(moduleName)
427			if err != nil {
428				return err
429			}
430		}
431
432		for _, i := range imports {
433			var imported *Export
434			imported, err = importedModule.getExport(i.Name, i.Type)
435			if err != nil {
436				return
437			}
438
439			switch i.Type {
440			case ExternTypeFunc:
441				expectedType := &module.TypeSection[i.DescFunc]
442				src := importedModule.Source
443				actual := src.typeOfFunction(imported.Index)
444				if !actual.EqualsSignature(expectedType.Params, expectedType.Results) {
445					err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual))
446					return
447				}
448
449				m.Engine.ResolveImportedFunction(i.IndexPerType, i.DescFunc, imported.Index, importedModule.Engine)
450			case ExternTypeTable:
451				expected := i.DescTable
452				importedTable := importedModule.Tables[imported.Index]
453				if expected.Type != importedTable.Type {
454					err = errorInvalidImport(i, fmt.Errorf("table type mismatch: %s != %s",
455						RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
456					return
457				}
458
459				if expected.Min > importedTable.Min {
460					err = errorMinSizeMismatch(i, expected.Min, importedTable.Min)
461					return
462				}
463
464				if expected.Max != nil {
465					expectedMax := *expected.Max
466					if importedTable.Max == nil {
467						err = errorNoMax(i, expectedMax)
468						return
469					} else if expectedMax < *importedTable.Max {
470						err = errorMaxSizeMismatch(i, expectedMax, *importedTable.Max)
471						return
472					}
473				}
474				m.Tables[i.IndexPerType] = importedTable
475				importedTable.involvingModuleInstancesMutex.Lock()
476				if len(importedTable.involvingModuleInstances) == 0 {
477					panic("BUG: involvingModuleInstances must not be nil when it's imported")
478				}
479				importedTable.involvingModuleInstances = append(importedTable.involvingModuleInstances, m)
480				importedTable.involvingModuleInstancesMutex.Unlock()
481			case ExternTypeMemory:
482				expected := i.DescMem
483				importedMemory := importedModule.MemoryInstance
484
485				if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
486					err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min)
487					return
488				}
489
490				if expected.Max < importedMemory.Max {
491					err = errorMaxSizeMismatch(i, expected.Max, importedMemory.Max)
492					return
493				}
494				m.MemoryInstance = importedMemory
495				m.Engine.ResolveImportedMemory(importedModule.Engine)
496			case ExternTypeGlobal:
497				expected := i.DescGlobal
498				importedGlobal := importedModule.Globals[imported.Index]
499
500				if expected.Mutable != importedGlobal.Type.Mutable {
501					err = errorInvalidImport(i, fmt.Errorf("mutability mismatch: %t != %t",
502						expected.Mutable, importedGlobal.Type.Mutable))
503					return
504				}
505
506				if expected.ValType != importedGlobal.Type.ValType {
507					err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s",
508						ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
509					return
510				}
511				m.Globals[i.IndexPerType] = importedGlobal
512			}
513		}
514	}
515	return
516}
517
518func errorMinSizeMismatch(i *Import, expected, actual uint32) error {
519	return errorInvalidImport(i, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
520}
521
522func errorNoMax(i *Import, expected uint32) error {
523	return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
524}
525
526func errorMaxSizeMismatch(i *Import, expected, actual uint32) error {
527	return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
528}
529
530func errorInvalidImport(i *Import, err error) error {
531	return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err)
532}
533
534// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
535// The validity of the expression is ensured when calling this function as this is only called
536// during instantiation phrase, and the validation happens in compilation (validateConstExpression).
537func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) {
538	switch expr.Opcode {
539	case OpcodeI32Const:
540		ret, _, _ = leb128.LoadInt32(expr.Data)
541	case OpcodeGlobalGet:
542		id, _, _ := leb128.LoadUint32(expr.Data)
543		g := importedGlobals[id]
544		ret = int32(g.Val)
545	}
546	return
547}
548
549// initialize initializes the value of this global instance given the const expr and imported globals.
550// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr.
551//
552// Global initialization constant expression can only reference the imported globals.
553// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
554func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) {
555	switch expr.Opcode {
556	case OpcodeI32Const:
557		// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
558		v, _, _ := leb128.LoadInt32(expr.Data)
559		g.Val = uint64(uint32(v))
560	case OpcodeI64Const:
561		// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
562		v, _, _ := leb128.LoadInt64(expr.Data)
563		g.Val = uint64(v)
564	case OpcodeF32Const:
565		g.Val = uint64(binary.LittleEndian.Uint32(expr.Data))
566	case OpcodeF64Const:
567		g.Val = binary.LittleEndian.Uint64(expr.Data)
568	case OpcodeGlobalGet:
569		id, _, _ := leb128.LoadUint32(expr.Data)
570		importedG := importedGlobals[id]
571		switch importedG.Type.ValType {
572		case ValueTypeI32:
573			g.Val = uint64(uint32(importedG.Val))
574		case ValueTypeI64:
575			g.Val = importedG.Val
576		case ValueTypeF32:
577			g.Val = importedG.Val
578		case ValueTypeF64:
579			g.Val = importedG.Val
580		case ValueTypeV128:
581			g.Val, g.ValHi = importedG.Val, importedG.ValHi
582		case ValueTypeFuncref, ValueTypeExternref:
583			g.Val = importedG.Val
584		}
585	case OpcodeRefNull:
586		switch expr.Data[0] {
587		case ValueTypeExternref, ValueTypeFuncref:
588			g.Val = 0 // Reference types are opaque 64bit pointer at runtime.
589		}
590	case OpcodeRefFunc:
591		v, _, _ := leb128.LoadUint32(expr.Data)
592		g.Val = uint64(funcRefResolver(v))
593	case OpcodeVecV128Const:
594		g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])
595	}
596}
597
598// String implements api.Global.
599func (g *GlobalInstance) String() string {
600	switch g.Type.ValType {
601	case ValueTypeI32, ValueTypeI64:
602		return fmt.Sprintf("global(%d)", g.Val)
603	case ValueTypeF32:
604		return fmt.Sprintf("global(%f)", api.DecodeF32(g.Val))
605	case ValueTypeF64:
606		return fmt.Sprintf("global(%f)", api.DecodeF64(g.Val))
607	default:
608		panic(fmt.Errorf("BUG: unknown value type %X", g.Type.ValType))
609	}
610}
611
612func (g *GlobalInstance) Value() (uint64, uint64) {
613	if g.Me != nil {
614		return g.Me.GetGlobalValue(g.Index)
615	}
616	return g.Val, g.ValHi
617}
618
619func (g *GlobalInstance) SetValue(lo, hi uint64) {
620	if g.Me != nil {
621		g.Me.SetGlobalValue(g.Index, lo, hi)
622	} else {
623		g.Val, g.ValHi = lo, hi
624	}
625}
626
627func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) {
628	ret := make([]FunctionTypeID, len(ts))
629	for i := range ts {
630		t := &ts[i]
631		inst, err := s.GetFunctionTypeID(t)
632		if err != nil {
633			return nil, err
634		}
635		ret[i] = inst
636	}
637	return ret, nil
638}
639
640func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
641	s.mux.RLock()
642	key := t.key()
643	id, ok := s.typeIDs[key]
644	s.mux.RUnlock()
645	if !ok {
646		s.mux.Lock()
647		defer s.mux.Unlock()
648		// Check again in case another goroutine has already added the type.
649		if id, ok = s.typeIDs[key]; ok {
650			return id, nil
651		}
652		l := len(s.typeIDs)
653		if uint32(l) >= s.functionMaxTypes {
654			return 0, fmt.Errorf("too many function types in a store")
655		}
656		id = FunctionTypeID(l)
657		s.typeIDs[key] = id
658	}
659	return id, nil
660}
661
662// CloseWithExitCode implements the same method as documented on wazero.Runtime.
663func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) error {
664	s.mux.Lock()
665	defer s.mux.Unlock()
666	// Close modules in reverse initialization order.
667	var errs []error
668	for m := s.moduleList; m != nil; m = m.next {
669		// If closing this module errs, proceed anyway to close the others.
670		if err := m.closeWithExitCode(ctx, exitCode); err != nil {
671			errs = append(errs, err)
672		}
673	}
674	s.moduleList = nil
675	s.nameToModule = nil
676	s.nameToModuleCap = 0
677	s.typeIDs = nil
678	return errors.Join(errs...)
679}