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 }