package wasm import ( "bytes" "crypto/sha256" "encoding/binary" "errors" "fmt" "slices" "sort" "strings" "sync" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental" "github.com/tetratelabs/wazero/internal/wasmdebug" ) // Module is a WebAssembly binary representation. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A8 // // Differences from the specification: // * NameSection is the only key ("name") decoded from the SectionIDCustom. // * ExportSection is represented as a map for lookup convenience. // * Code.GoFunc is contains any go `func`. It may be present when Code.Body is not. type Module struct { // TypeSection contains the unique FunctionType of functions imported or defined in this module. // // Note: Currently, there is no type ambiguity in the index as WebAssembly 1.0 only defines function type. // In the future, other types may be introduced to support CoreFeatures such as module linking. // // Note: In the Binary Format, this is SectionIDType. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#types%E2%91%A0%E2%91%A0 TypeSection []FunctionType // ImportSection contains imported functions, tables, memories or globals required for instantiation // (Store.Instantiate). // // Note: there are no unique constraints relating to the two-level namespace of Import.Module and Import.Name. // // Note: In the Binary Format, this is SectionIDImport. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0 ImportSection []Import // ImportFunctionCount ImportGlobalCount ImportMemoryCount, and ImportTableCount are // the cached import count per ExternType set during decoding. ImportFunctionCount, ImportGlobalCount, ImportMemoryCount, ImportTableCount Index // ImportPerModule maps a module name to the list of Import to be imported from the module. // This is used to do fast import resolution during instantiation. ImportPerModule map[string][]*Import // FunctionSection contains the index in TypeSection of each function defined in this module. // // Note: The function Index space begins with imported functions and ends with those defined in this module. // For example, if there are two imported functions and one defined in this module, the function Index 3 is defined // in this module at FunctionSection[0]. // // Note: FunctionSection is index correlated with the CodeSection. If given the same position, e.g. 2, a function // type is at TypeSection[FunctionSection[2]], while its locals and body are at CodeSection[2]. // // Note: In the Binary Format, this is SectionIDFunction. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0 FunctionSection []Index // TableSection contains each table defined in this module. // // Note: The table Index space begins with imported tables and ends with those defined in this module. // For example, if there are two imported tables and one defined in this module, the table Index 3 is defined in // this module at TableSection[0]. // // Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one table definition per module, so the // length of the TableSection can be zero or one, and can only be one if there is no imported table. // // Note: In the Binary Format, this is SectionIDTable. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0 TableSection []Table // MemorySection contains each memory defined in this module. // // Note: The memory Index space begins with imported memories and ends with those defined in this module. // For example, if there are two imported memories and one defined in this module, the memory Index 3 is defined in // this module at TableSection[0]. // // Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one memory definition per module, so the // length of the MemorySection can be zero or one, and can only be one if there is no imported memory. // // Note: In the Binary Format, this is SectionIDMemory. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0 MemorySection *Memory // TagSection contains each tag defined in this module for exception handling. // // Tag indexes are offset by any imported tags because the tag index begins with imports, followed by // ones defined in this module. // // Note: In the Binary Format, this is SectionIDTag. // // See https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md TagSection []Tag // ImportTagCount is the cached count of imported tags set during decoding. ImportTagCount Index // GlobalSection contains each global defined in this module. // // Global indexes are offset by any imported globals because the global index begins with imports, followed by // ones defined in this module. For example, if there are two imported globals and three defined in this module, the // global at index 3 is defined in this module at GlobalSection[0]. // // Note: In the Binary Format, this is SectionIDGlobal. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0 GlobalSection []Global // ExportSection contains each export defined in this module. // // Note: In the Binary Format, this is SectionIDExport. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0 ExportSection []Export // Exports maps a name to Export, and is convenient for fast look up of exported instances at runtime. // Each item of this map points to an element of ExportSection. Exports map[string]*Export // StartSection is the index of a function to call before returning from Store.Instantiate. // // Note: The index here is not the position in the FunctionSection, rather in the function index, which // begins with imported functions. // // Note: In the Binary Format, this is SectionIDStart. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0 StartSection *Index // Note: In the Binary Format, this is SectionIDElement. ElementSection []ElementSegment // CodeSection is index-correlated with FunctionSection and contains each // function's locals and body. // // When present, the HostFunctionSection of the same index must be nil. // // Note: In the Binary Format, this is SectionIDCode. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0 CodeSection []Code // Note: In the Binary Format, this is SectionIDData. DataSection []DataSegment // NameSection is set when the SectionIDCustom "name" was successfully decoded from the binary format. // // Note: This is the only SectionIDCustom defined in the WebAssembly 1.0 (20191205) Binary Format. // Others are skipped as they are not used in wazero. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 NameSection *NameSection // CustomSections are set when the SectionIDCustom other than "name" were successfully decoded from the binary format. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0 CustomSections []*CustomSection // DataCountSection is the optional section and holds the number of data segments in the data section. // // Note: This may exist in WebAssembly 2.0 or WebAssembly 1.0 with CoreFeatureBulkMemoryOperations. // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions DataCountSection *uint32 // ID is the sha256 value of the source wasm plus the configurations which affect the runtime representation of // Wasm binary. This is only used for caching. ID ModuleID // IsHostModule true if this is the host module, false otherwise. IsHostModule bool // functionDefinitionSectionInitOnce guards FunctionDefinitionSection so that it is initialized exactly once. functionDefinitionSectionInitOnce sync.Once // FunctionDefinitionSection is a wazero-specific section. FunctionDefinitionSection []FunctionDefinition // MemoryDefinitionSection is a wazero-specific section. MemoryDefinitionSection []MemoryDefinition // DWARFLines is used to emit DWARF based stack trace. This is created from the multiple custom sections // as described in https://yurydelendik.github.io/webassembly-dwarf/, though it is not specified in the Wasm // specification: https://github.com/WebAssembly/debugging/issues/1 DWARFLines *wasmdebug.DWARFLines } // ModuleID represents sha256 hash value uniquely assigned to Module. type ModuleID = [sha256.Size]byte // The wazero specific limitation described at RATIONALE.md. // TL;DR; We multiply by 8 (to get offsets in bytes) and the multiplication result must be less than 32bit max const ( MaximumGlobals = uint32(1 << 27) MaximumFunctionIndex = uint32(1 << 27) MaximumTableIndex = uint32(1 << 27) ) // AssignModuleID calculates a sha256 checksum on `wasm` and other args, and set Module.ID to the result. // See the doc on Module.ID on what it's used for. func (m *Module) AssignModuleID(wasm []byte, listeners []experimental.FunctionListener, withEnsureTermination bool) { h := sha256.New() h.Write(wasm) // Use the pre-allocated space backed by m.ID below. // Write the existence of listeners to the checksum per function. for i, l := range listeners { binary.LittleEndian.PutUint32(m.ID[:], uint32(i)) m.ID[4] = boolToByte(l != nil) h.Write(m.ID[:5]) } // Write the flag of ensureTermination to the checksum. m.ID[0] = boolToByte(withEnsureTermination) h.Write(m.ID[:1]) // Get checksum by passing the slice underlying m.ID. h.Sum(m.ID[:0]) } func boolToByte(b bool) (ret byte) { if b { ret = 1 } return } // typeOfFunction returns the wasm.FunctionType for the given function space index or nil. func (m *Module) typeOfFunction(funcIdx Index) *FunctionType { typeIdx, ok := m.typeIndexOfFunction(funcIdx) if !ok { return nil } return &m.TypeSection[typeIdx] } // typeIndexOfFunction returns the type section index for the given function // space index, or false if the index is out of range. func (m *Module) typeIndexOfFunction(funcIdx Index) (Index, bool) { typeSectionLength, importedFunctionCount := uint32(len(m.TypeSection)), m.ImportFunctionCount if funcIdx < importedFunctionCount { // Imports are not exclusively functions. This is the current function index in the loop. cur := Index(0) for i := range m.ImportSection { imp := &m.ImportSection[i] if imp.Type != ExternTypeFunc { continue } if funcIdx == cur { if imp.DescFunc >= typeSectionLength { return 0, false } return imp.DescFunc, true } cur++ } } funcSectionIdx := funcIdx - m.ImportFunctionCount if funcSectionIdx >= uint32(len(m.FunctionSection)) { return 0, false } typeIdx := m.FunctionSection[funcSectionIdx] if typeIdx >= typeSectionLength { return 0, false } return typeIdx, true } func (m *Module) Validate(enabledFeatures api.CoreFeatures) error { for i := range m.TypeSection { tp := &m.TypeSection[i] tp.CacheNumInUint64() } if err := m.validateConcreteRefTypes(); err != nil { return err } if err := m.validateStartSection(); err != nil { return err } functions, globals, memory, tables, tags, err := m.AllDeclarations() if err != nil { return err } if err = m.validateTableInitExprs(globals, uint32(len(functions))); err != nil { return err } if err = m.validateImports(enabledFeatures); err != nil { return err } if err = m.validateGlobals(globals, uint32(len(functions)), MaximumGlobals); err != nil { return err } if err = m.validateMemory(memory, globals, enabledFeatures); err != nil { return err } if err = m.validateExports(enabledFeatures, functions, globals, memory, tables, tags); err != nil { return err } if m.CodeSection != nil { if err = m.validateFunctions(enabledFeatures, functions, globals, memory, tables, tags, MaximumFunctionIndex); err != nil { return err } } // No need to validate host functions as NewHostModule validates if err = m.validateTable(enabledFeatures, tables, MaximumTableIndex); err != nil { return err } if err = m.validateDataCountSection(); err != nil { return err } if err = m.validateTagSection(); err != nil { return err } return nil } func (m *Module) validateConcreteRefTypes() error { numTypes := uint32(len(m.TypeSection)) for i, g := range m.GlobalSection { if vt := g.Type.ValType; vt.IsConcreteRef() && vt.TypeIndex() >= numTypes { return fmt.Errorf("unknown type %d in global[%d]", vt.TypeIndex(), i) } } for i, t := range m.TableSection { if vt := t.Type; vt.IsConcreteRef() && vt.TypeIndex() >= numTypes { return fmt.Errorf("unknown type %d in table[%d]", vt.TypeIndex(), i) } } for i, c := range m.CodeSection { for j, lt := range c.LocalTypes { if lt.IsConcreteRef() && lt.TypeIndex() >= numTypes { return fmt.Errorf("unknown type %d in func[%d].local[%d]", lt.TypeIndex(), i, j) } } } for i, e := range m.ElementSection { if vt := e.Type; vt.IsConcreteRef() && vt.TypeIndex() >= numTypes { return fmt.Errorf("unknown type %d in element[%d]", vt.TypeIndex(), i) } } return nil } func (m *Module) validateTableInitExprs(globals []GlobalType, numFuncs uint32) error { importedGlobals := globals[:m.ImportGlobalCount] for i, t := range m.TableSection { if !t.Type.IsNullable() && t.InitExpr == nil { return fmt.Errorf("type mismatch: non-nullable table[%d] requires an init expression", i) } if t.InitExpr != nil { if err := m.validateConstExpression(importedGlobals, numFuncs, t.InitExpr, t.Type); err != nil { return fmt.Errorf("table[%d] init: %w", i, err) } } } return nil } func (m *Module) validateTagSection() error { for i, tag := range m.TagSection { if tag.Type >= uint32(len(m.TypeSection)) { return fmt.Errorf("tag[%d] type index out of range", i) } ft := &m.TypeSection[tag.Type] if len(ft.Results) > 0 { return fmt.Errorf("tag[%d] type must have empty results, got %v", i, ft.Results) } } return nil } func (m *Module) validateStartSection() error { // Check the start function is valid. // TODO: this should be verified during decode so that errors have the correct source positions if m.StartSection != nil { startIndex := *m.StartSection ft := m.typeOfFunction(startIndex) if ft == nil { // TODO: move this check to decoder so that a module can never be decoded invalidly return fmt.Errorf("invalid start function: func[%d] has an invalid type", startIndex) } if len(ft.Params) > 0 || len(ft.Results) > 0 { return fmt.Errorf("invalid start function: func[%d] must have an empty (nullary) signature: %s", startIndex, ft) } } return nil } func (m *Module) validateGlobals(globals []GlobalType, numFuncts, maxGlobals uint32) error { if uint32(len(globals)) > maxGlobals { return fmt.Errorf("too many globals in a module") } // Global initialization constant expression can only reference the imported globals. // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 importedGlobals := globals[:m.ImportGlobalCount] for i := range m.GlobalSection { g := &m.GlobalSection[i] if err := m.validateConstExpression(importedGlobals, numFuncts, &g.Init, g.Type.ValType); err != nil { return err } } return nil } func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, tags []Index, maximumFunctionIndex uint32) error { if uint32(len(functions)) > maximumFunctionIndex { return fmt.Errorf("too many functions (%d) in a module", len(functions)) } functionCount := m.SectionElementCount(SectionIDFunction) codeCount := m.SectionElementCount(SectionIDCode) if functionCount == 0 && codeCount == 0 { return nil } typeCount := m.SectionElementCount(SectionIDType) if codeCount != functionCount { return fmt.Errorf("code count (%d) != function count (%d)", codeCount, functionCount) } declaredFuncIndexes, err := m.declaredFunctionIndexes(enabledFeatures) if err != nil { return err } // Create bytes.Reader once as it causes allocation, and // we frequently need it (e.g. on every If instruction). br := bytes.NewReader(nil) // Also, we reuse the stacks across multiple function validations to reduce allocations. vs := &stacks{} for idx, typeIndex := range m.FunctionSection { if typeIndex >= typeCount { return fmt.Errorf("invalid %s: type section index %d out of range", m.funcDesc(SectionIDFunction, Index(idx)), typeIndex) } c := &m.CodeSection[idx] if c.GoFunc != nil { continue } if err = m.validateFunction(vs, enabledFeatures, Index(idx), functions, globals, memory, tables, tags, declaredFuncIndexes, br); err != nil { return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err) } } return nil } // declaredFunctionIndexes returns a set of function indexes that can be used as an immediate for OpcodeRefFunc instruction. // // The criteria for which function indexes can be available for that instruction is vague in the spec: // // - "References: the list of function indices that occur in the module outside functions and can hence be used to form references inside them." // - https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/valid/conventions.html#contexts // - "Ref is the set funcidx(module with functions=ε, start=ε) , i.e., the set of function indices occurring in the module, except in its functions or start function." // - https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/valid/modules.html#valid-module // // To clarify, we reverse-engineer logic required to pass the WebAssembly Core specification 2.0 test suite: // https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/ref_func.wast#L78-L115 // // To summarize, the function indexes OpcodeRefFunc can refer include: // - existing in an element section regardless of its mode (active, passive, declarative). // - defined as globals whose value type is ValueRefFunc. // - used as an exported function. // // See https://github.com/WebAssembly/reference-types/issues/31 // See https://github.com/WebAssembly/reference-types/issues/76 func (m *Module) declaredFunctionIndexes(enabledFeatures api.CoreFeatures) (ret map[Index]struct{}, err error) { ret = map[uint32]struct{}{} for i := range m.ExportSection { exp := &m.ExportSection[i] if exp.Type == ExternTypeFunc { ret[exp.Index] = struct{}{} } } for i := range m.GlobalSection { g := &m.GlobalSection[i] _, _, initErr := evaluateConstExpr( &g.Init, func(globalIndex Index) (ValueType, uint64, uint64, error) { vt, err := m.resolveConstExprGlobalType(enabledFeatures, SectionIDGlobal, Index(i), globalIndex) return vt, 0, 0, err }, func(funcIndex Index) (Reference, error) { ret[funcIndex] = struct{}{} return 0, nil }, ) if initErr != nil { err = fmt.Errorf("%s[%d] failed to initialize: %w", SectionIDName(SectionIDGlobal), i, initErr) return } } for i := range m.ElementSection { elem := &m.ElementSection[i] for _, initExpr := range elem.Init { _, _, _ = evaluateConstExpr( &initExpr, func(globalIndex Index) (ValueType, uint64, uint64, error) { vt, err := m.resolveConstExprGlobalType(enabledFeatures, SectionIDElement, Index(i), globalIndex) return vt, 0, 0, err }, func(funcIndex Index) (Reference, error) { ret[funcIndex] = struct{}{} return 0, nil }, ) } } return } func (m *Module) funcDesc(sectionID SectionID, sectionIndex Index) string { // Try to improve the error message by collecting any exports: var exportNames []string funcIdx := sectionIndex + m.ImportFunctionCount for i := range m.ExportSection { exp := &m.ExportSection[i] if exp.Index == funcIdx && exp.Type == ExternTypeFunc { exportNames = append(exportNames, fmt.Sprintf("%q", exp.Name)) } } sectionIDName := SectionIDName(sectionID) if exportNames == nil { return fmt.Sprintf("%s[%d]", sectionIDName, sectionIndex) } sort.Strings(exportNames) // go map keys do not iterate consistently return fmt.Sprintf("%s[%d] export[%s]", sectionIDName, sectionIndex, strings.Join(exportNames, ",")) } func (m *Module) validateMemory(memory *Memory, globals []GlobalType, _ api.CoreFeatures) error { var activeElementCount int for i := range m.DataSection { d := &m.DataSection[i] if !d.IsPassive() { activeElementCount++ } } if activeElementCount > 0 && memory == nil { return fmt.Errorf("unknown memory") } // Constant expression can only reference imported globals. // https://github.com/WebAssembly/spec/blob/5900d839f38641989a9d8df2df4aee0513365d39/test/core/data.wast#L84-L91 importedGlobals := globals[:m.ImportGlobalCount] for i := range m.DataSection { d := &m.DataSection[i] if !d.IsPassive() { if err := m.validateConstExpression(importedGlobals, 0, &d.OffsetExpression, ValueTypeI32); err != nil { return fmt.Errorf("calculate offset: %w", err) } } } return nil } func (m *Module) validateImports(enabledFeatures api.CoreFeatures) error { for i := range m.ImportSection { imp := &m.ImportSection[i] if imp.Module == "" { return fmt.Errorf("import[%d] has an empty module name", i) } switch imp.Type { case ExternTypeFunc: if int(imp.DescFunc) >= len(m.TypeSection) { return fmt.Errorf("invalid import[%q.%q] function: type index out of range", imp.Module, imp.Name) } case ExternTypeGlobal: if !imp.DescGlobal.Mutable { continue } if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil { return fmt.Errorf("invalid import[%q.%q] global: %w", imp.Module, imp.Name, err) } case ExternTypeTag: if int(imp.DescTag) >= len(m.TypeSection) { return fmt.Errorf("invalid import[%q.%q] tag: type index out of range", imp.Module, imp.Name) } if len(m.TypeSection[imp.DescTag].Results) > 0 { return fmt.Errorf("invalid import[%q.%q] tag: tag types must have no results", imp.Module, imp.Name) } } } return nil } func (m *Module) validateExports(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, tags []Index) error { for i := range m.ExportSection { exp := &m.ExportSection[i] index := exp.Index switch exp.Type { case ExternTypeFunc: if index >= uint32(len(functions)) { return fmt.Errorf("unknown function for export[%q]", exp.Name) } case ExternTypeGlobal: if index >= uint32(len(globals)) { return fmt.Errorf("unknown global for export[%q]", exp.Name) } if !globals[index].Mutable { continue } if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil { return fmt.Errorf("invalid export[%q] global[%d]: %w", exp.Name, index, err) } case ExternTypeMemory: if index > 0 || memory == nil { return fmt.Errorf("memory for export[%q] out of range", exp.Name) } case ExternTypeTable: if index >= uint32(len(tables)) { return fmt.Errorf("table for export[%q] out of range", exp.Name) } case ExternTypeTag: if index >= uint32(len(tags)) { return fmt.Errorf("tag for export[%q] out of range", exp.Name) } } } return nil } func (m *Module) validateConstExpression(globals []GlobalType, numFuncs uint32, expr *ConstantExpression, expectedType ValueType) (err error) { var lastRefFuncIdx Index _, typ, err := evaluateConstExpr( expr, func(globalIndex Index) (ValueType, uint64, uint64, error) { if uint32(len(globals)) <= globalIndex { return 0, 0, 0, fmt.Errorf("global index out of range") } return globals[globalIndex].ValType, 0, 0, nil }, func(funcIndex Index) (Reference, error) { if funcIndex >= numFuncs { return 0, fmt.Errorf("ref.func index out of range [%d] with length %d", funcIndex, numFuncs-1) } lastRefFuncIdx = funcIndex return 0, nil }, ) if err != nil { return err } if typ == ValueTypeFuncref { if typeIndex, ok := m.typeIndexOfFunction(lastRefFuncIdx); ok { typ = ValueTypeConcreteRef(typeIndex, false) } } if !isRefSubtypeOf(typ, expectedType) { return fmt.Errorf("const expression type mismatch expected %s but got %s", ValueTypeName(expectedType), ValueTypeName(typ)) } return nil } func (m *Module) validateDataCountSection() (err error) { if m.DataCountSection != nil && int(*m.DataCountSection) != len(m.DataSection) { err = fmt.Errorf("data count section (%d) doesn't match the length of data section (%d)", *m.DataCountSection, len(m.DataSection)) } return } func (m *ModuleInstance) buildTags(module *Module) { for i := range module.TagSection { tag := &module.TagSection[i] t := &TagInstance{ Type: &module.TypeSection[tag.Type], } m.Tags[i+int(module.ImportTagCount)] = t } } func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcIndex Index) Reference) { importedGlobals := m.Globals[:module.ImportGlobalCount] me := m.Engine engineOwnGlobal := me.OwnsGlobals() for i := Index(0); i < Index(len(module.GlobalSection)); i++ { gs := &module.GlobalSection[i] g := &GlobalInstance{} if engineOwnGlobal { g.Me = me g.Index = i + module.ImportGlobalCount } m.Globals[i+module.ImportGlobalCount] = g g.Type = gs.Type g.initialize(importedGlobals, &gs.Init, funcRefResolver) } } func (m *Module) resolveConstExprGlobalType(enabledFeatures api.CoreFeatures, sectionID SectionID, sectionIdx Index, idx Index) (ValueType, error) { if idx < m.ImportGlobalCount { // Imports are not exclusively globals. This is the current global index in the loop. cur := uint32(0) for i := range m.ImportSection { imp := &m.ImportSection[i] if imp.Type != ExternTypeGlobal { continue } if idx == cur { return imp.DescGlobal.ValType, nil } cur++ } // should not happen as idx < ImportGlobalCount return 0, fmt.Errorf("index %d not found in imported globals", idx) } // NOTE: in the <= 2.0 spec, global.get in a constant expression can only refer to imported globals. // In version 3.0, this restriction is removed, and all globals prior to the current one are allowed. // To avoid implementing too many flags, this relaxation is gated behind the CoreFeaturesExtendedConst flag, // which includes other related extensions in constant expressions. if !enabledFeatures.IsEnabled(experimental.CoreFeaturesExtendedConst) { return 0, fmt.Errorf("%s[%d] (global.get %d): out of range of imported globals", SectionIDName(sectionID), sectionIdx, idx) } idx -= uint32(m.ImportGlobalCount) // Check that the given global has been initialized. if sectionIdx == Index(SectionIDGlobal) && idx >= sectionIdx { return 0, fmt.Errorf("%s[%d] global %d out of range of initialized globals", SectionIDName(sectionID), sectionIdx, idx) } // Bounds check: if idx >= uint32(len(m.GlobalSection)) { return 0, fmt.Errorf("%s[%d] (global.get %d): out of range of initialized globals", SectionIDName(sectionID), sectionIdx, idx) } return m.GlobalSection[idx].Type.ValType, nil } func paramNames(localNames IndirectNameMap, funcIdx uint32, paramLen int) []string { for i := range localNames { nm := &localNames[i] // Only build parameter names if we have one for each. if nm.Index != funcIdx || len(nm.NameMap) < paramLen { continue } ret := make([]string, paramLen) for j := range nm.NameMap { p := &nm.NameMap[j] if int(p.Index) < paramLen { ret[p.Index] = p.Name } } return ret } return nil } func (m *ModuleInstance) buildMemory(module *Module, allocator experimental.MemoryAllocator) { memSec := module.MemorySection if memSec != nil { m.MemoryInstance = NewMemoryInstance(memSec, allocator, m.Engine) m.MemoryInstance.definition = &module.MemoryDefinitionSection[0] } } // Index is the offset in an index, not necessarily an absolute position in a Module section. This is because // indexs are often preceded by a corresponding type in the Module.ImportSection. // // For example, the function index starts with any ExternTypeFunc in the Module.ImportSection followed by // the Module.FunctionSection // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-index type Index = uint32 // FunctionType is a possibly empty function signature. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A0 type FunctionType struct { // Params are the possibly empty sequence of value types accepted by a function with this signature. Params []ValueType // Results are the possibly empty sequence of value types returned by a function with this signature. // // Note: In WebAssembly 1.0 (20191205), there can be at most one result. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0 Results []ValueType // string is cached as it is used both for String and key string string // ParamNumInUint64 is the number of uint64 values requires to represent the Wasm param type. ParamNumInUint64 int // ResultsNumInUint64 is the number of uint64 values requires to represent the Wasm result type. ResultNumInUint64 int // RecGroupSize is the size of the rec group this type belongs to. // Standalone types (not in an explicit rec group) have RecGroupSize 1. RecGroupSize int // RecGroupPosition is the 0-based position of this type within its rec group. RecGroupPosition int } func (f *FunctionType) CacheNumInUint64() { if f.ParamNumInUint64 == 0 { for _, tp := range f.Params { f.ParamNumInUint64++ if tp == ValueTypeV128 { f.ParamNumInUint64++ } } } if f.ResultNumInUint64 == 0 { for _, tp := range f.Results { f.ResultNumInUint64++ if tp == ValueTypeV128 { f.ResultNumInUint64++ } } } } // EqualsSignature returns true if the function type has the same parameters and results. func (f *FunctionType) EqualsSignature(params []ValueType, results []ValueType) bool { return slices.Equal(f.Params, params) && slices.Equal(f.Results, results) } // EqualsType returns true if the function types are structurally equal AND // belong to the same rec group position/size (GC proposal type identity). func (f *FunctionType) EqualsType(other *FunctionType) bool { if !f.EqualsSignature(other.Params, other.Results) { return false } return f.RecGroupSize == other.RecGroupSize && f.RecGroupPosition == other.RecGroupPosition } // key gets or generates the key for Store.typeIDs. e.g. "i32_v" for one i32 parameter and no (void) result. func (f *FunctionType) key() string { if f.string != "" { return f.string } var ret string for _, b := range f.Params { ret += ValueTypeName(b) } if len(f.Params) == 0 { ret += "v_" } else { ret += "_" } for _, b := range f.Results { ret += ValueTypeName(b) } if len(f.Results) == 0 { ret += "v" } if f.RecGroupSize > 1 { ret += fmt.Sprintf("|rec%d/%d", f.RecGroupPosition, f.RecGroupSize) } f.string = ret return ret } // String implements fmt.Stringer. func (f *FunctionType) String() string { return f.key() } // Import is the binary representation of an import indicated by Type // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import type Import struct { Type ExternType // Module is the possibly empty primary namespace of this import Module string // Module is the possibly empty secondary namespace of this import Name string // DescFunc is the index in Module.TypeSection when Type equals ExternTypeFunc DescFunc Index // DescTable is the inlined Table when Type equals ExternTypeTable DescTable Table // DescMem is the inlined Memory when Type equals ExternTypeMemory DescMem *Memory // DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal DescGlobal GlobalType // DescTag is the type index when Type equals ExternTypeTag DescTag Index // IndexPerType has the index of this import per ExternType. IndexPerType Index } // Memory describes the limits of pages (64KB) in a memory. type Memory struct { Min, Cap, Max uint32 // IsMaxEncoded true if the Max is encoded in the original binary. IsMaxEncoded bool // IsShared true if the memory is shared for access from multiple agents. IsShared bool } // Validate ensures values assigned to Min, Cap and Max are within valid thresholds. func (m *Memory) Validate(memoryLimitPages uint32) error { min, capacity, max := m.Min, m.Cap, m.Max if max > memoryLimitPages { return fmt.Errorf("max %d pages (%s) over limit of %d pages (%s)", max, PagesToUnitOfBytes(max), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) } else if min > memoryLimitPages { return fmt.Errorf("min %d pages (%s) over limit of %d pages (%s)", min, PagesToUnitOfBytes(min), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) } else if min > max { return fmt.Errorf("min %d pages (%s) > max %d pages (%s)", min, PagesToUnitOfBytes(min), max, PagesToUnitOfBytes(max)) } else if capacity < min { return fmt.Errorf("capacity %d pages (%s) less than minimum %d pages (%s)", capacity, PagesToUnitOfBytes(capacity), min, PagesToUnitOfBytes(min)) } else if capacity > memoryLimitPages { return fmt.Errorf("capacity %d pages (%s) over limit of %d pages (%s)", capacity, PagesToUnitOfBytes(capacity), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages)) } return nil } // Tag represents an exception tag defined in the tag section. // The Type field is an index into the TypeSection; the referenced function type // must have empty results (tags carry parameters but produce no results). type Tag struct { Type Index } type GlobalType struct { ValType ValueType Mutable bool } type Global struct { Type GlobalType Init ConstantExpression } // Export is the binary representation of an export indicated by Type // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-export type Export struct { Type ExternType // Name is what the host refers to this definition as. Name string // Index is the index of the definition to export, the index is by Type // e.g. If ExternTypeFunc, this is a position in the function index. Index Index } // Code is an entry in the Module.CodeSection containing the locals and body of the function. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code type Code struct { // LocalTypes are any function-scoped variables in insertion order. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-local LocalTypes []ValueType // Body is a sequence of expressions ending in OpcodeEnd // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-expr Body []byte // GoFunc is non-nil when IsHostFunction and defined in go, either // api.GoFunction or api.GoModuleFunction. When present, LocalTypes and Body must // be nil. // // Note: This has no serialization format, so is not encodable. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2 GoFunc interface{} // BodyOffsetInCodeSection is the offset of the beginning of the body in the code section. // This is used for DWARF based stack trace where a program counter represents an offset in code section. BodyOffsetInCodeSection uint64 } type DataSegment struct { OffsetExpression ConstantExpression Init []byte Passive bool } // IsPassive returns true if this data segment is "passive" in the sense that memory offset and // index is determined at runtime and used by OpcodeMemoryInitName instruction in the bulk memory // operations proposal. // // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions func (d *DataSegment) IsPassive() bool { return d.Passive } // NameSection represent the known custom name subsections defined in the WebAssembly Binary Format // // Note: This can be nil if no names were decoded for any reason including configuration. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0 type NameSection struct { // ModuleName is the symbolic identifier for a module. e.g. math // // Note: This can be empty for any reason including configuration. ModuleName string // FunctionNames is an association of a function index to its symbolic identifier. e.g. add // // * the key (idx) is in the function index, where module defined functions are preceded by imported ones. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#functions%E2%91%A7 // // For example, assuming the below text format is the second import, you would expect FunctionNames[1] = "mul" // (import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32))) // // Note: FunctionNames are only used for debugging. At runtime, functions are called based on raw numeric index. // Note: This can be nil for any reason including configuration. FunctionNames NameMap // LocalNames contains symbolic names for function parameters or locals that have one. // // Note: In the Text Format, function local names can inherit parameter // names from their type. Here are some examples: // * (module (import (func (param $x i32) (param i32))) (func (type 0))) = [{0, {x,0}}] // * (module (import (func (param i32) (param $y i32))) (func (type 0) (local $z i32))) = [0, [{y,1},{z,2}]] // * (module (func (param $x i32) (local $y i32) (local $z i32))) = [{x,0},{y,1},{z,2}] // // Note: LocalNames are only used for debugging. At runtime, locals are called based on raw numeric index. // Note: This can be nil for any reason including configuration. LocalNames IndirectNameMap // ResultNames is a wazero-specific mechanism to store result names. ResultNames IndirectNameMap } // CustomSection contains the name and raw data of a custom section. type CustomSection struct { Name string Data []byte } // NameMap associates an index with any associated names. // // Note: Often the index bridges multiple sections. For example, the function index starts with any // ExternTypeFunc in the Module.ImportSection followed by the Module.FunctionSection // // Note: NameMap is unique by NameAssoc.Index, but NameAssoc.Name needn't be unique. // Note: When encoding in the Binary format, this must be ordered by NameAssoc.Index // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap type NameMap []NameAssoc type NameAssoc struct { Index Index Name string } // IndirectNameMap associates an index with an association of names. // // Note: IndirectNameMap is unique by NameMapAssoc.Index, but NameMapAssoc.NameMap needn't be unique. // Note: When encoding in the Binary format, this must be ordered by NameMapAssoc.Index // https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-indirectnamemap type IndirectNameMap []NameMapAssoc type NameMapAssoc struct { Index Index NameMap NameMap } // AllDeclarations returns all declarations for functions, globals, memories, tables and tags in a module including imported ones. func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, memory *Memory, tables []Table, tags []Index, err error) { for i := range m.ImportSection { imp := &m.ImportSection[i] switch imp.Type { case ExternTypeFunc: functions = append(functions, imp.DescFunc) case ExternTypeGlobal: globals = append(globals, imp.DescGlobal) case ExternTypeMemory: memory = imp.DescMem case ExternTypeTable: tables = append(tables, imp.DescTable) case ExternTypeTag: tags = append(tags, imp.DescTag) } } functions = append(functions, m.FunctionSection...) for i := range m.GlobalSection { g := &m.GlobalSection[i] globals = append(globals, g.Type) } for i := range m.TagSection { t := &m.TagSection[i] tags = append(tags, t.Type) } if m.MemorySection != nil { if memory != nil { // shouldn't be possible due to Validate err = errors.New("at most one table allowed in module") return } memory = m.MemorySection } if m.TableSection != nil { tables = append(tables, m.TableSection...) } return } // SectionID identifies the sections of a Module in the WebAssembly 1.0 (20191205) Binary Format. // // Note: these are defined in the wasm package, instead of the binary package, as a key per section is needed regardless // of format, and deferring to the binary type avoids confusion. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 type SectionID = byte const ( // SectionIDCustom includes the standard defined NameSection and possibly others not defined in the standard. SectionIDCustom SectionID = iota // don't add anything not in https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 SectionIDType SectionIDImport SectionIDFunction SectionIDTable SectionIDMemory SectionIDGlobal SectionIDExport SectionIDStart SectionIDElement SectionIDCode SectionIDData // SectionIDDataCount may exist in WebAssembly 2.0 or WebAssembly 1.0 with CoreFeatureBulkMemoryOperations enabled. // // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions SectionIDDataCount // SectionIDTag is for exception handling tags. // // See https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md SectionIDTag SectionID = 13 ) // SectionIDName returns the canonical name of a module section. // https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0 func SectionIDName(sectionID SectionID) string { switch sectionID { case SectionIDCustom: return "custom" case SectionIDType: return "type" case SectionIDImport: return "import" case SectionIDFunction: return "function" case SectionIDTable: return "table" case SectionIDMemory: return "memory" case SectionIDGlobal: return "global" case SectionIDExport: return "export" case SectionIDStart: return "start" case SectionIDElement: return "element" case SectionIDCode: return "code" case SectionIDData: return "data" case SectionIDDataCount: return "data_count" case SectionIDTag: return "tag" } return "unknown" } // ValueType represents a WebAssembly value type as a uint64. // // Layout: // // bits 0-7: kind byte (backward-compatible with api.ValueType) // bits 8-15: flags (nullability, concrete ref) // bits 32-63: type index (for concrete refs like (ref $3)) type ValueType uint64 const ( flagNonNullable ValueType = 1 << 8 flagConcreteRef ValueType = 1 << 9 ) const ( ValueTypeI32 ValueType = 0x7f ValueTypeI64 ValueType = 0x7e ValueTypeF32 ValueType = 0x7d ValueTypeF64 ValueType = 0x7c ValueTypeV128 ValueType = 0x7b ValueTypeFuncref ValueType = 0x70 ValueTypeExternref ValueType = 0x6f ValueTypeExnref ValueType = 0x69 ) // Kind returns the base type byte (bits 0-7). func (v ValueType) Kind() byte { return byte(v) } // IsRef returns true if this is a reference type (including non-nullable variants). func (v ValueType) IsRef() bool { k := v.Kind() return k == ValueTypeFuncref.Kind() || k == ValueTypeExternref.Kind() || k == ValueTypeExnref.Kind() || v&flagConcreteRef != 0 } // IsNullable returns true if this reference type is nullable. Must only be called on ref types. func (v ValueType) IsNullable() bool { return v.IsRef() && v&flagNonNullable == 0 } // IsConcreteRef returns true if this is a concrete reference type with a type index. func (v ValueType) IsConcreteRef() bool { return v&flagConcreteRef != 0 } // TypeIndex returns the concrete type index (bits 32-63). func (v ValueType) TypeIndex() uint32 { return uint32(v >> 32) } // AsNonNullable returns a copy with the non-nullable flag set. func (v ValueType) AsNonNullable() ValueType { return v | flagNonNullable } // AsNullable returns a copy with the non-nullable flag cleared. func (v ValueType) AsNullable() ValueType { return v &^ flagNonNullable } // ValueTypeConcreteRef creates a concrete reference type with the given type index and nullability. func ValueTypeConcreteRef(typeIndex uint32, nullable bool) ValueType { v := ValueTypeFuncref | flagConcreteRef | ValueType(typeIndex)<<32 if !nullable { v |= flagNonNullable } return v } const ( // RefPrefixNullable is the binary encoding prefix for nullable reference types (ref null ). RefPrefixNullable byte = 0x63 // RefPrefixNonNullable is the binary encoding prefix for non-nullable reference types (ref ). RefPrefixNonNullable byte = 0x64 ) const ( // HeapTypeFunc is the abstract heap type for function references. HeapTypeFunc int64 = -16 // HeapTypeExtern is the abstract heap type for external references. HeapTypeExtern int64 = -17 // HeapTypeExn is the abstract heap type for exception references. HeapTypeExn int64 = -23 ) // ValueTypeName returns the name of a ValueType. func ValueTypeName(t ValueType) string { if t.IsConcreteRef() { if t.IsNullable() { return fmt.Sprintf("(ref null %d)", t.TypeIndex()) } return fmt.Sprintf("(ref %d)", t.TypeIndex()) } switch t.AsNullable() { case ValueTypeI32: return "i32" case ValueTypeI64: return "i64" case ValueTypeF32: return "f32" case ValueTypeF64: return "f64" case ValueTypeV128: return "v128" case ValueTypeFuncref: if !t.IsNullable() { return "(ref func)" } return "funcref" case ValueTypeExternref: if !t.IsNullable() { return "(ref extern)" } return "externref" case ValueTypeExnref: if !t.IsNullable() { return "(ref exn)" } return "exnref" } return "unknown" } func isReferenceValueType(vt ValueType) bool { return vt.IsRef() } // isRefSubtypeOf returns true if actual is a subtype of (or equal to) expected. // Non-nullable is a subtype of nullable. Concrete function refs are subtypes of funcref. func isRefSubtypeOf(actual, expected ValueType) bool { if actual == expected { return true } // Non-nullable is subtype of nullable (same kind/index). if actual.AsNullable() == expected.AsNullable() && expected.IsNullable() { return true } // Concrete function ref is subtype of (abstract) funcref (nullable or non-nullable). if actual.IsConcreteRef() && expected.Kind() == ValueTypeFuncref.Kind() { if !actual.IsNullable() || expected.IsNullable() { return true } } return false } // areRefTypesCompatible returns true if either type is a subtype of the other. func areRefTypesCompatible(a, b ValueType) bool { return isRefSubtypeOf(a, b) || isRefSubtypeOf(b, a) } // ExternType is an alias of api.ExternType defined to simplify imports. type ExternType = api.ExternType const ( ExternTypeFunc = api.ExternTypeFunc ExternTypeFuncName = api.ExternTypeFuncName ExternTypeTable = api.ExternTypeTable ExternTypeTableName = api.ExternTypeTableName ExternTypeMemory = api.ExternTypeMemory ExternTypeMemoryName = api.ExternTypeMemoryName ExternTypeGlobal = api.ExternTypeGlobal ExternTypeGlobalName = api.ExternTypeGlobalName ExternTypeTag = ExternType(0x04) ExternTypeTagName = "tag" ) // ExternTypeName is an alias of api.ExternTypeName defined to simplify imports. func ExternTypeName(t ExternType) string { if t == ExternTypeTag { return ExternTypeTagName } return api.ExternTypeName(t) }