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