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