Files
wazero-wazero/internal/wasm/module.go
T
Edoardo Vacchi bfb20e0ba7 feat: exception handling spec (#2489)
Add experimental support to the Exception Handling spec.

Exception handling adds structured error propagation to WebAssembly
through `tags` (typed exception signatures), `throw/throw_ref` (raising
exceptions), and `try_table` (catching exceptions with typed `catch`
clauses). A new value type, `exnref`, represents a reference to a caught
exception.

Excluding tests and spec suites, the entire feature amounts to a little
less than 2000 lines of code.

Feature flag: `experimental.CoreFeaturesExceptionHandling`


## What's the use for this?

In the spirit of always having a real use case for the specs we
implement, I did check this implementation against the one large Wasm
C++ codebase I am aware about, i.e.
https://github.com/klippa-app/go-pdfium, and I verified that the entire
suite, compiled with `emcc+wasm-opt` does work; incidentally, much of
the existing support code, based on Emscripten's longjmp/setjmp, can be
dropped (thx @jerbob92 for the help!)

Broadly speaking, I don't know how much EH-enabled Wasm code you will
find in the wild. For instance, code compiled using Clang's C++ backend
does not really throw new-style exceptions, unless you compile WASI-SDK
with some flags, or you build with `emcc` and translate old-style
exceptions using `wasm-opt` (see exceptions_test.go)

In short, this spec should be really seen as a step towards supporting
the GC spec. The design decisions (esp. regarding the test suite) follow
from this reasoning.


## Spec Suite

`wast2json` **does not** support the EH suite, but `wasm-tools` does,
albeit with a tiny quirk in the way negative numbers are rendered. In
this PR _I am not_ moving the other suites to `wasm-tools` though.

The spec test suite passes entirely, except for two tests that are
intentionally skipped.

These tests assume **a very small subset** of typed references is
implemented; specifically, the distinction between nullable and
non-nullable function references (`(ref null $t)` vs `(ref $t)`). I have
decided to just flatten all non-nullable refs to nullable refs at decode
time, so we cannot detect the type mismatch the spec expects in these
cases.

Passing the **full suite is actually straightforward** because it only
requires introducing a non-nullable variant for each ref type: strictly
speaking the 2 failing tests are only for related nullable vs
non-nullable **func refs** -- so I can special-case these and/or add
non-nullable types for each known value; however, we will **need** to
rethink the representation of Values for the GC proposal anyway (because
this will introduce custom type indices, and these won't fit our
currently byte-sized `ValueType` enum).

The subtype-checking functions (`isRefSubtypeOf`,
`isStrictRefSubtypeOf`) are kept as abstraction points but currently
they are just equalities.

Nevertheless, as proven by the CPP example and
https://github.com/klippa-app/go-pdfium this PR provides a starting
point that is already useful (even if it is for limited cases).


## Interpreter

Exception handling in the interpreter:

**Within a frame**, `try_table` blocks are compiled into a static
exception table with PC ranges. When `throw` or `throw_ref` executes,
`searchExceptionTable` scans backwards (inner handlers first) for a
catch clause whose PC range covers the current instruction. If a match
is found, `applyExceptionHandler` adjusts the operand stack depth and
jumps to the catch target PC directly.

**Across frames**, if no handler is found in the current frame,
`panic(&thrownException{...})` propagates up the Go call stack. Each
cross-function call goes through `callWithUnwind()`, which uses
`defer/recover` to intercept the panic. Its `canRestore` method unwinds
`ce.frames` to the caller's depth and searches that frame's exception
table. If a handler is found, `doRestore` applies it and
`callWithUnwind` returns `true` (frame unwound -- caller refreshes
locals and continues). If not, the panic re-propagates to the next outer
`callWithUnwind`.

Note on the implementation: this mechanism is unified with the existing
`snapshot/restore` API through the `restorable` interface: both
`*thrownException` and `*snapshot` implement `canRestore`/`doRestore`,
so `callWithUnwind` handles both with a single defer/recover path. The
short-circuit check (`len(exceptionTable) == 0 && no snapshotter`) skips
the defer/recover overhead entirely for frames that don't need it.


## Compiler

The compiler could not reuse the snapshot/restore mechanism directly,
but it follows a similar pattern. One cool thing is that exception
handling could be implemented entirely as an SSA-level lowering with new
exit codes; there is no backend-specific code.

Throwing uses a two-phase ABI:

1. `ExitCodeThrowAlloc`: exits to the Go runtime to allocate an
`Exception` struct on the heap and writes a pointer to its params array
into the execution context. Then we return control to compiled code,
which stores the tag parameters directly into the struct.
2. `ExitCodeThrow`: exits to the Go runtime again to search for a
matching handler, restore the stack checkpoint, and branch to the catch
target.

The idea is that we NEED to allocate the parameter slice dynamically, so
we delegate to the Go runtime. Incidentally, this also allows us to
unify `throw` and `throwRef`; in the latter case, we skip the `alloc`
phase, and we exit straight to `ExitCodeThrow`.

### Trampolines

The compiler uses four trampolines to return to host to manage exception
state:

| Trampoline | Exit Code | Purpose |
|---|---|---|
| **ThrowAlloc** | `ExitCodeThrowAlloc` | Allocates an `Exception`
struct on the Go heap, writes `&exn.Params[0]` into `execCtx` so
compiled code can store tag parameters directly. |
| **Throw** | `ExitCodeThrow` | Searches `tryHandlers` for a matching
catch clause, restores the stack checkpoint on match, sets
`caughtExceptionClauseIdx` for the compiled dispatch. Used for both
`throw` (after ThrowAlloc) and `throw_ref` (directly). |
| **TryTableEnter** | `ExitCodeTryTableEnter` | Clones the current stack
as a checkpoint, pushes a `tryHandler` with the catch clause table and
saved module instance. Sets `caughtExceptionClauseIdx = -1` (no
exception). |
| **TryTableLeave** | `ExitCodeTryTableLeave` | Pops the most recent
`tryHandler`. Emitted at `try_table` block ends, and before any branch
or return that exits a `try_table` scope. |

### Lowering

As mentioned earlier, this entirely resolved at the SSA-level. In
particular:

- **Entry**: when a `try_table` is encountered, the compiler emits a
call to the TryTableEnter trampoline. Control returns to compiled code,
which reads `caughtExceptionClauseIdx` from the execution context — if
it's -1 (no exception), execution continues into the `try_table` body.

- **Normal exit**: when execution reaches the `End` opcode of the
`try_table` block, a TryTableLeave trampoline call is emitted.

However, every TryTableEnter must have a matching TryTableLeave on every
control flow path that doesn't throw; so we also "TryTableLeaves" also
for the following:

- **Early exit (branch)**: a `br`, `br_if`, or `br_table` can jump to a
label outside the `try_table`, skipping its `End`.
`emitTryTableLeaves(depth)` walks the control frame stack and emits one
TryTableLeave for each `try_table` frame the branch crosses. For
`br_if`, this happens in a trampoline basic block that only executes on
the taken path.

- **Early exit (return)**: `return`, `return_call`, and
`return_call_indirect` call `emitTryTableLeaves` with the full control
stack depth, popping all active handlers before leaving the frame.

- **Throw**: on `throw`/`throw_ref`, compiled code exits to the dispatch
loop via ExitCodeThrow. `doHandleException` searches `tryHandlers`
innermost-to-outermost. On match, it restores the cloned stack
checkpoint, sets `caughtExceptionClauseIdx`, and re-enters compiled code
at the handler's return address. The compiled dispatch branches to the
matching catch clause's handler block.


### Caveats

- As described above, we are skipping 2 spec tests (will be addressed
when we implement typed references)
- Because of that, I am _not_ enabling by default the fuzzer: I would
have to special-case for typed references and/or TODOs I have left
behind; I did run the fuzzer with some special-casing while debugging
the issue with pdfium (#2488) and did not find issues (the fuzzer did go
frequently OOM though); in fact, the pdfium issue was actually unrelated
(!)

---------

Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
2026-04-26 09:12:51 -07:00

1216 lines
43 KiB
Go

package wasm
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"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 {
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 nil
}
return &m.TypeSection[imp.DescFunc]
}
cur++
}
}
funcSectionIdx := funcIdx - m.ImportFunctionCount
if funcSectionIdx >= uint32(len(m.FunctionSection)) {
return nil
}
typeIdx := m.FunctionSection[funcSectionIdx]
if typeIdx >= typeSectionLength {
return nil
}
return &m.TypeSection[typeIdx]
}
func (m *Module) Validate(enabledFeatures api.CoreFeatures) error {
for i := range m.TypeSection {
tp := &m.TypeSection[i]
tp.CacheNumInUint64()
}
if err := m.validateStartSection(); err != nil {
return err
}
functions, globals, memory, tables, tags, err := m.AllDeclarations()
if 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) 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 := 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 := 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 validateConstExpression(globals []GlobalType, numFuncs uint32, expr *ConstantExpression, expectedType ValueType) (err error) {
_, 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)
}
return 0, nil
},
)
if err != nil {
return err
}
if 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 bytes.Equal(f.Params, params) && bytes.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 is an alias of api.ValueType defined to simplify imports.
type ValueType = api.ValueType
const (
ValueTypeI32 = api.ValueTypeI32
ValueTypeI64 = api.ValueTypeI64
ValueTypeF32 = api.ValueTypeF32
ValueTypeF64 = api.ValueTypeF64
// TODO: ValueTypeV128 is not exposed in the api pkg yet.
ValueTypeV128 ValueType = 0x7b
// TODO: ValueTypeFuncref is not exposed in the api pkg yet.
ValueTypeFuncref ValueType = 0x70
ValueTypeExternref = api.ValueTypeExternref
// ValueTypeExnref is the exception reference type used in exception handling.
ValueTypeExnref ValueType = 0x69
)
const (
// RefPrefixNullable is the binary encoding prefix for nullable reference types (ref null <heaptype>).
RefPrefixNullable byte = 0x63
// RefPrefixNonNullable is the binary encoding prefix for non-nullable reference types (ref <heaptype>).
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 is an alias of api.ValueTypeName defined to simplify imports.
func ValueTypeName(t ValueType) string {
if t == ValueTypeFuncref {
return "funcref"
} else if t == ValueTypeV128 {
return "v128"
} else if t == ValueTypeExnref {
return "exnref"
}
return api.ValueTypeName(t)
}
func isReferenceValueType(vt ValueType) bool {
return vt == ValueTypeExternref || vt == ValueTypeFuncref || vt == ValueTypeExnref
}
// isRefSubtypeOf returns true if actual is assignment-compatible with expected.
// Currently, non-nullable ref types are desugared to nullable at decode time,
// so this reduces to equality. When non-nullable ref types are properly supported,
// this function should allow non-nullable to match nullable and vice versa.
func isRefSubtypeOf(actual, expected ValueType) bool {
return actual == expected
}
// isStrictRefSubtypeOf returns true if actual is a strict subtype of expected.
// Currently, non-nullable ref types are desugared to nullable at decode time,
// so this reduces to equality. When non-nullable ref types are properly supported,
// non-nullable should be a subtype of nullable, but NOT vice versa.
func isStrictRefSubtypeOf(actual, expected ValueType) bool {
return actual == expected
}
// 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 ValueType) string {
if t == ExternTypeTag {
return ExternTypeTagName
}
return api.ExternTypeName(t)
}