[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)

This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
kim
2024-05-27 15:46:15 +00:00
committed by GitHub
parent cce21c11cb
commit 1e7b32490d
398 changed files with 86174 additions and 684 deletions

View File

@ -0,0 +1,100 @@
package binary
import (
"bytes"
"fmt"
"io"
"math"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeCode(r *bytes.Reader, codeSectionStart uint64, ret *wasm.Code) (err error) {
ss, _, err := leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("get the size of code: %w", err)
}
remaining := int64(ss)
// Parse #locals.
ls, bytesRead, err := leb128.DecodeUint32(r)
remaining -= int64(bytesRead)
if err != nil {
return fmt.Errorf("get the size locals: %v", err)
} else if remaining < 0 {
return io.EOF
}
// Validate the locals.
bytesRead = 0
var sum uint64
for i := uint32(0); i < ls; i++ {
num, n, err := leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("read n of locals: %v", err)
} else if remaining < 0 {
return io.EOF
}
sum += uint64(num)
b, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read type of local: %v", err)
}
bytesRead += n + 1
switch vt := b; vt {
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128:
default:
return fmt.Errorf("invalid local type: 0x%x", vt)
}
}
if sum > math.MaxUint32 {
return fmt.Errorf("too many locals: %d", sum)
}
// Rewind the buffer.
_, err = r.Seek(-int64(bytesRead), io.SeekCurrent)
if err != nil {
return err
}
localTypes := make([]wasm.ValueType, 0, sum)
for i := uint32(0); i < ls; i++ {
num, bytesRead, err := leb128.DecodeUint32(r)
remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte
if err != nil {
return fmt.Errorf("read n of locals: %v", err)
} else if remaining < 0 {
return io.EOF
}
b, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read type of local: %v", err)
}
for j := uint32(0); j < num; j++ {
localTypes = append(localTypes, b)
}
}
bodyOffsetInCodeSection := codeSectionStart - uint64(r.Len())
body := make([]byte, remaining)
if _, err = io.ReadFull(r, body); err != nil {
return fmt.Errorf("read body: %w", err)
}
if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd {
return fmt.Errorf("expr not end with OpcodeEnd")
}
ret.BodyOffsetInCodeSection = bodyOffsetInCodeSection
ret.LocalTypes = localTypes
ret.Body = body
return nil
}

View File

@ -0,0 +1,105 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/ieee754"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeConstantExpression(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ConstantExpression) error {
b, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read opcode: %v", err)
}
remainingBeforeData := int64(r.Len())
offsetAtData := r.Size() - remainingBeforeData
opcode := b
switch opcode {
case wasm.OpcodeI32Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt32(r)
case wasm.OpcodeI64Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
_, _, err = leb128.DecodeInt64(r)
case wasm.OpcodeF32Const:
buf := make([]byte, 4)
if _, err := io.ReadFull(r, buf); err != nil {
return fmt.Errorf("read f32 constant: %v", err)
}
_, err = ieee754.DecodeFloat32(buf)
case wasm.OpcodeF64Const:
buf := make([]byte, 8)
if _, err := io.ReadFull(r, buf); err != nil {
return fmt.Errorf("read f64 constant: %v", err)
}
_, err = ieee754.DecodeFloat64(buf)
case wasm.OpcodeGlobalGet:
_, _, err = leb128.DecodeUint32(r)
case wasm.OpcodeRefNull:
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
return fmt.Errorf("ref.null is not supported as %w", err)
}
reftype, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read reference type for ref.null: %w", err)
} else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref {
return fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
}
case wasm.OpcodeRefFunc:
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
return fmt.Errorf("ref.func is not supported as %w", err)
}
// Parsing index.
_, _, err = leb128.DecodeUint32(r)
case wasm.OpcodeVecPrefix:
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil {
return fmt.Errorf("vector instructions are not supported as %w", err)
}
opcode, err = r.ReadByte()
if err != nil {
return fmt.Errorf("read vector instruction opcode suffix: %w", err)
}
if opcode != wasm.OpcodeVecV128Const {
return fmt.Errorf("invalid vector opcode for const expression: %#x", opcode)
}
remainingBeforeData = int64(r.Len())
offsetAtData = r.Size() - remainingBeforeData
n, err := r.Read(make([]byte, 16))
if err != nil {
return fmt.Errorf("read vector const instruction immediates: %w", err)
} else if n != 16 {
return fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n)
}
default:
return fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b)
}
if err != nil {
return fmt.Errorf("read value: %v", err)
}
if b, err = r.ReadByte(); err != nil {
return fmt.Errorf("look for end opcode: %v", err)
}
if b != wasm.OpcodeEnd {
return fmt.Errorf("constant expression has been not terminated")
}
ret.Data = make([]byte, remainingBeforeData-int64(r.Len())-1)
if _, err = r.ReadAt(ret.Data, offsetAtData); err != nil {
return fmt.Errorf("error re-buffering ConstantExpression.Data")
}
ret.Opcode = opcode
return nil
}

View File

@ -0,0 +1,22 @@
package binary
import (
"bytes"
"github.com/tetratelabs/wazero/internal/wasm"
)
// decodeCustomSection deserializes the data **not** associated with the "name" key in SectionIDCustom.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
func decodeCustomSection(r *bytes.Reader, name string, limit uint64) (result *wasm.CustomSection, err error) {
buf := make([]byte, limit)
_, err = r.Read(buf)
result = &wasm.CustomSection{
Name: name,
Data: buf,
}
return
}

View File

@ -0,0 +1,79 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
// dataSegmentPrefix represents three types of data segments.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
type dataSegmentPrefix = uint32
const (
// dataSegmentPrefixActive is the prefix for the version 1.0 compatible data segment, which is classified as "active" in 2.0.
dataSegmentPrefixActive dataSegmentPrefix = 0x0
// dataSegmentPrefixPassive prefixes the "passive" data segment as in version 2.0 specification.
dataSegmentPrefixPassive dataSegmentPrefix = 0x1
// dataSegmentPrefixActiveWithMemoryIndex is the active prefix with memory index encoded which is defined for futur use as of 2.0.
dataSegmentPrefixActiveWithMemoryIndex dataSegmentPrefix = 0x2
)
func decodeDataSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.DataSegment) (err error) {
dataSegmentPrefx, _, err := leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("read data segment prefix: %w", err)
return
}
if dataSegmentPrefx != dataSegmentPrefixActive {
if err = enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
err = fmt.Errorf("non-zero prefix for data segment is invalid as %w", err)
return
}
}
switch dataSegmentPrefx {
case dataSegmentPrefixActive,
dataSegmentPrefixActiveWithMemoryIndex:
// Active data segment as in
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
if dataSegmentPrefx == 0x2 {
d, _, err := leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("read memory index: %v", err)
} else if d != 0 {
return fmt.Errorf("memory index must be zero but was %d", d)
}
}
err = decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpression)
if err != nil {
return fmt.Errorf("read offset expression: %v", err)
}
case dataSegmentPrefixPassive:
// Passive data segment doesn't need const expr nor memory index encoded.
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
ret.Passive = true
default:
err = fmt.Errorf("invalid data segment prefix: 0x%x", dataSegmentPrefx)
return
}
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("get the size of vector: %v", err)
return
}
ret.Init = make([]byte, vs)
if _, err = io.ReadFull(r, ret.Init); err != nil {
err = fmt.Errorf("read bytes for init: %v", err)
}
return
}

View File

@ -0,0 +1,193 @@
package binary
import (
"bytes"
"debug/dwarf"
"errors"
"fmt"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
// DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Binary Format
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
func DecodeModule(
binary []byte,
enabledFeatures api.CoreFeatures,
memoryLimitPages uint32,
memoryCapacityFromMax,
dwarfEnabled, storeCustomSections bool,
) (*wasm.Module, error) {
r := bytes.NewReader(binary)
// Magic number.
buf := make([]byte, 4)
if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) {
return nil, ErrInvalidMagicNumber
}
// Version.
if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) {
return nil, ErrInvalidVersion
}
memSizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax)
m := &wasm.Module{}
var info, line, str, abbrev, ranges []byte // For DWARF Data.
for {
// TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA
sectionID, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, fmt.Errorf("read section id: %w", err)
}
sectionSize, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
}
sectionContentStart := r.Len()
switch sectionID {
case wasm.SectionIDCustom:
// First, validate the section and determine if the section for this name has already been set
name, nameSize, decodeErr := decodeUTF8(r, "custom section name")
if decodeErr != nil {
err = decodeErr
break
} else if sectionSize < nameSize {
err = fmt.Errorf("malformed custom section %s", name)
break
} else if name == "name" && m.NameSection != nil {
err = fmt.Errorf("redundant custom section %s", name)
break
}
// Now, either decode the NameSection or CustomSection
limit := sectionSize - nameSize
var c *wasm.CustomSection
if name != "name" {
if storeCustomSections || dwarfEnabled {
c, err = decodeCustomSection(r, name, uint64(limit))
if err != nil {
return nil, fmt.Errorf("failed to read custom section name[%s]: %w", name, err)
}
m.CustomSections = append(m.CustomSections, c)
if dwarfEnabled {
switch name {
case ".debug_info":
info = c.Data
case ".debug_line":
line = c.Data
case ".debug_str":
str = c.Data
case ".debug_abbrev":
abbrev = c.Data
case ".debug_ranges":
ranges = c.Data
}
}
} else {
if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil {
return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
}
}
} else {
m.NameSection, err = decodeNameSection(r, uint64(limit))
}
case wasm.SectionIDType:
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
case wasm.SectionIDImport:
m.ImportSection, m.ImportPerModule, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures)
if err != nil {
return nil, err // avoid re-wrapping the error.
}
case wasm.SectionIDFunction:
m.FunctionSection, err = decodeFunctionSection(r)
case wasm.SectionIDTable:
m.TableSection, err = decodeTableSection(r, enabledFeatures)
case wasm.SectionIDMemory:
m.MemorySection, err = decodeMemorySection(r, enabledFeatures, memSizer, memoryLimitPages)
case wasm.SectionIDGlobal:
if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil {
return nil, err // avoid re-wrapping the error.
}
case wasm.SectionIDExport:
m.ExportSection, m.Exports, err = decodeExportSection(r)
case wasm.SectionIDStart:
if m.StartSection != nil {
return nil, errors.New("multiple start sections are invalid")
}
m.StartSection, err = decodeStartSection(r)
case wasm.SectionIDElement:
m.ElementSection, err = decodeElementSection(r, enabledFeatures)
case wasm.SectionIDCode:
m.CodeSection, err = decodeCodeSection(r)
case wasm.SectionIDData:
m.DataSection, err = decodeDataSection(r, enabledFeatures)
case wasm.SectionIDDataCount:
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
return nil, fmt.Errorf("data count section not supported as %v", err)
}
m.DataCountSection, err = decodeDataCountSection(r)
default:
err = ErrInvalidSectionID
}
readBytes := sectionContentStart - r.Len()
if err == nil && int(sectionSize) != readBytes {
err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes)
}
if err != nil {
return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err)
}
}
if dwarfEnabled {
d, _ := dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str)
m.DWARFLines = wasmdebug.NewDWARFLines(d)
}
functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode)
if functionCount != codeCount {
return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount)
}
return m, nil
}
// memorySizer derives min, capacity and max pages from decoded wasm.
type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32)
// newMemorySizer sets capacity to minPages unless max is defined and
// memoryCapacityFromMax is true.
func newMemorySizer(memoryLimitPages uint32, memoryCapacityFromMax bool) memorySizer {
return func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
if maxPages != nil {
if memoryCapacityFromMax {
return minPages, *maxPages, *maxPages
}
// This is an invalid value: let it propagate, we will fail later.
if *maxPages > wasm.MemoryLimitPages {
return minPages, minPages, *maxPages
}
// This is a valid value, but it goes over the run-time limit: return the limit.
if *maxPages > memoryLimitPages {
return minPages, minPages, memoryLimitPages
}
return minPages, minPages, *maxPages
}
if memoryCapacityFromMax {
return minPages, memoryLimitPages, memoryLimitPages
}
return minPages, minPages, memoryLimitPages
}
}

View File

@ -0,0 +1,269 @@
package binary
import (
"bytes"
"errors"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func ensureElementKindFuncRef(r *bytes.Reader) error {
elemKind, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read element prefix: %w", err)
}
if elemKind != 0x0 { // ElemKind is fixed to 0x0 now: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
return fmt.Errorf("element kind must be zero but was 0x%x", elemKind)
}
return nil
}
func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.Index, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
vec := make([]wasm.Index, vs)
for i := range vec {
u32, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("read function index: %w", err)
}
if u32 >= wasm.MaximumFunctionIndex {
return nil, fmt.Errorf("too large function index in Element init: %d", u32)
}
vec[i] = u32
}
return vec, nil
}
func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]wasm.Index, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err)
}
vec := make([]wasm.Index, vs)
for i := range vec {
var expr wasm.ConstantExpression
err := decodeConstantExpression(r, enabledFeatures, &expr)
if err != nil {
return nil, err
}
switch expr.Opcode {
case wasm.OpcodeRefFunc:
if elemType != wasm.RefTypeFuncref {
return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType))
}
v, _, _ := leb128.LoadUint32(expr.Data)
if v >= wasm.MaximumFunctionIndex {
return nil, fmt.Errorf("too large function index in Element init: %d", v)
}
vec[i] = v
case wasm.OpcodeRefNull:
if elemType != expr.Data[0] {
return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s",
wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0]))
}
vec[i] = wasm.ElementInitNullReference
case wasm.OpcodeGlobalGet:
i32, _, _ := leb128.LoadInt32(expr.Data)
// Resolving the reference type from globals is done at instantiation phase. See the comment on
// wasm.elementInitImportedGlobalReferenceType.
vec[i] = wasm.WrapGlobalIndexAsElementInit(wasm.Index(i32))
default:
return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode))
}
}
return vec, nil
}
func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) {
ret, err = r.ReadByte()
if err != nil {
err = fmt.Errorf("read element ref type: %w", err)
return
}
if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref {
return 0, errors.New("ref type must be funcref or externref for element as of WebAssembly 2.0")
}
return
}
const (
// The prefix is explained at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
// elementSegmentPrefixLegacy is the legacy prefix and is only valid one before CoreFeatureBulkMemoryOperations.
elementSegmentPrefixLegacy = iota
// elementSegmentPrefixPassiveFuncrefValueVector is the passive element whose indexes are encoded as vec(varint), and reftype is fixed to funcref.
elementSegmentPrefixPassiveFuncrefValueVector
// elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex is the same as elementSegmentPrefixPassiveFuncrefValueVector but active and table index is encoded.
elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex
// elementSegmentPrefixDeclarativeFuncrefValueVector is the same as elementSegmentPrefixPassiveFuncrefValueVector but declarative.
elementSegmentPrefixDeclarativeFuncrefValueVector
// elementSegmentPrefixActiveFuncrefConstExprVector is active whoce reftype is fixed to funcref and indexes are encoded as vec(const_expr).
elementSegmentPrefixActiveFuncrefConstExprVector
// elementSegmentPrefixPassiveConstExprVector is passive whoce indexes are encoded as vec(const_expr), and reftype is encoded.
elementSegmentPrefixPassiveConstExprVector
// elementSegmentPrefixPassiveConstExprVector is active whoce indexes are encoded as vec(const_expr), and reftype and table index are encoded.
elementSegmentPrefixActiveConstExprVector
// elementSegmentPrefixDeclarativeConstExprVector is declarative whoce indexes are encoded as vec(const_expr), and reftype is encoded.
elementSegmentPrefixDeclarativeConstExprVector
)
func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ElementSegment) error {
prefix, _, err := leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("read element prefix: %w", err)
}
if prefix != elementSegmentPrefixLegacy {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
return fmt.Errorf("non-zero prefix for element segment is invalid as %w", err)
}
}
// Encoding depends on the prefix and described at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
switch prefix {
case elementSegmentPrefixLegacy:
// Legacy prefix which is WebAssembly 1.0 compatible.
err = decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
if err != nil {
return fmt.Errorf("read expr for offset: %w", err)
}
ret.Init, err = decodeElementInitValueVector(r)
if err != nil {
return err
}
ret.Mode = wasm.ElementModeActive
ret.Type = wasm.RefTypeFuncref
return nil
case elementSegmentPrefixPassiveFuncrefValueVector:
// Prefix 1 requires funcref.
if err = ensureElementKindFuncRef(r); err != nil {
return err
}
ret.Init, err = decodeElementInitValueVector(r)
if err != nil {
return err
}
ret.Mode = wasm.ElementModePassive
ret.Type = wasm.RefTypeFuncref
return nil
case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex:
ret.TableIndex, _, err = leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("get size of vector: %w", err)
}
if ret.TableIndex != 0 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("table index must be zero but was %d: %w", ret.TableIndex, err)
}
}
err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
if err != nil {
return fmt.Errorf("read expr for offset: %w", err)
}
// Prefix 2 requires funcref.
if err = ensureElementKindFuncRef(r); err != nil {
return err
}
ret.Init, err = decodeElementInitValueVector(r)
if err != nil {
return err
}
ret.Mode = wasm.ElementModeActive
ret.Type = wasm.RefTypeFuncref
return nil
case elementSegmentPrefixDeclarativeFuncrefValueVector:
// Prefix 3 requires funcref.
if err = ensureElementKindFuncRef(r); err != nil {
return err
}
ret.Init, err = decodeElementInitValueVector(r)
if err != nil {
return err
}
ret.Type = wasm.RefTypeFuncref
ret.Mode = wasm.ElementModeDeclarative
return nil
case elementSegmentPrefixActiveFuncrefConstExprVector:
err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
if err != nil {
return fmt.Errorf("read expr for offset: %w", err)
}
ret.Init, err = decodeElementConstExprVector(r, wasm.RefTypeFuncref, enabledFeatures)
if err != nil {
return err
}
ret.Mode = wasm.ElementModeActive
ret.Type = wasm.RefTypeFuncref
return nil
case elementSegmentPrefixPassiveConstExprVector:
ret.Type, err = decodeElementRefType(r)
if err != nil {
return err
}
ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures)
if err != nil {
return err
}
ret.Mode = wasm.ElementModePassive
return nil
case elementSegmentPrefixActiveConstExprVector:
ret.TableIndex, _, err = leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("get size of vector: %w", err)
}
if ret.TableIndex != 0 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("table index must be zero but was %d: %w", ret.TableIndex, err)
}
}
err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
if err != nil {
return fmt.Errorf("read expr for offset: %w", err)
}
ret.Type, err = decodeElementRefType(r)
if err != nil {
return err
}
ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures)
if err != nil {
return err
}
ret.Mode = wasm.ElementModeActive
return nil
case elementSegmentPrefixDeclarativeConstExprVector:
ret.Type, err = decodeElementRefType(r)
if err != nil {
return err
}
ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures)
if err != nil {
return err
}
ret.Mode = wasm.ElementModeDeclarative
return nil
default:
return fmt.Errorf("invalid element segment prefix: 0x%x", prefix)
}
}

View File

@ -0,0 +1,11 @@
package binary
import "errors"
var (
ErrInvalidByte = errors.New("invalid byte")
ErrInvalidMagicNumber = errors.New("invalid magic number")
ErrInvalidVersion = errors.New("invalid version header")
ErrInvalidSectionID = errors.New("invalid section id")
ErrCustomSectionNotFound = errors.New("custom section not found")
)

View File

@ -0,0 +1,32 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeExport(r *bytes.Reader, ret *wasm.Export) (err error) {
if ret.Name, _, err = decodeUTF8(r, "export name"); err != nil {
return
}
b, err := r.ReadByte()
if err != nil {
err = fmt.Errorf("error decoding export kind: %w", err)
return
}
ret.Type = b
switch ret.Type {
case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal:
if ret.Index, _, err = leb128.DecodeUint32(r); err != nil {
err = fmt.Errorf("error decoding export index: %w", err)
}
default:
err = fmt.Errorf("%w: invalid byte for exportdesc: %#x", ErrInvalidByte, b)
}
return
}

View File

@ -0,0 +1,56 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeFunctionType(enabledFeatures api.CoreFeatures, r *bytes.Reader, ret *wasm.FunctionType) (err error) {
b, err := r.ReadByte()
if err != nil {
return fmt.Errorf("read leading byte: %w", err)
}
if b != 0x60 {
return fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b)
}
paramCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("could not read parameter count: %w", err)
}
paramTypes, err := decodeValueTypes(r, paramCount)
if err != nil {
return fmt.Errorf("could not read parameter types: %w", err)
}
resultCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return fmt.Errorf("could not read result count: %w", err)
}
// Guard >1.0 feature multi-value
if resultCount > 1 {
if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
return fmt.Errorf("multiple result types invalid as %v", err)
}
}
resultTypes, err := decodeValueTypes(r, resultCount)
if err != nil {
return fmt.Errorf("could not read result types: %w", err)
}
ret.Params = paramTypes
ret.Results = resultTypes
// cache the key for the function type
_ = ret.String()
return nil
}

View File

@ -0,0 +1,50 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasm"
)
// decodeGlobal returns the api.Global decoded with the WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global
func decodeGlobal(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Global) (err error) {
ret.Type, err = decodeGlobalType(r)
if err != nil {
return err
}
err = decodeConstantExpression(r, enabledFeatures, &ret.Init)
return
}
// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-globaltype
func decodeGlobalType(r *bytes.Reader) (wasm.GlobalType, error) {
vt, err := decodeValueTypes(r, 1)
if err != nil {
return wasm.GlobalType{}, fmt.Errorf("read value type: %w", err)
}
ret := wasm.GlobalType{
ValType: vt[0],
}
b, err := r.ReadByte()
if err != nil {
return wasm.GlobalType{}, fmt.Errorf("read mutablity: %w", err)
}
switch mut := b; mut {
case 0x00: // not mutable
case 0x01: // mutable
ret.Mutable = true
default:
return wasm.GlobalType{}, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut)
}
return ret, nil
}

View File

@ -0,0 +1,9 @@
package binary
// Magic is the 4 byte preamble (literally "\0asm") of the binary format
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-magic
var Magic = []byte{0x00, 0x61, 0x73, 0x6D}
// version is format version and doesn't change between known specification versions
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-version
var version = []byte{0x01, 0x00, 0x00, 0x00}

View File

@ -0,0 +1,52 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeImport(
r *bytes.Reader,
idx uint32,
memorySizer memorySizer,
memoryLimitPages uint32,
enabledFeatures api.CoreFeatures,
ret *wasm.Import,
) (err error) {
if ret.Module, _, err = decodeUTF8(r, "import module"); err != nil {
err = fmt.Errorf("import[%d] error decoding module: %w", idx, err)
return
}
if ret.Name, _, err = decodeUTF8(r, "import name"); err != nil {
err = fmt.Errorf("import[%d] error decoding name: %w", idx, err)
return
}
b, err := r.ReadByte()
if err != nil {
err = fmt.Errorf("import[%d] error decoding type: %w", idx, err)
return
}
ret.Type = b
switch ret.Type {
case wasm.ExternTypeFunc:
ret.DescFunc, _, err = leb128.DecodeUint32(r)
case wasm.ExternTypeTable:
err = decodeTable(r, enabledFeatures, &ret.DescTable)
case wasm.ExternTypeMemory:
ret.DescMem, err = decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages)
case wasm.ExternTypeGlobal:
ret.DescGlobal, err = decodeGlobalType(r)
default:
err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b)
}
if err != nil {
err = fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, wasm.ExternTypeName(ret.Type), ret.Module, ret.Name, err)
}
return
}

View File

@ -0,0 +1,47 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/internal/leb128"
)
// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
//
// Extended in threads proposal: https://webassembly.github.io/threads/core/binary/types.html#limits
func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, shared bool, err error) {
var flag byte
if flag, err = r.ReadByte(); err != nil {
err = fmt.Errorf("read leading byte: %v", err)
return
}
switch flag {
case 0x00, 0x02:
min, _, err = leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("read min of limit: %v", err)
}
case 0x01, 0x03:
min, _, err = leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("read min of limit: %v", err)
return
}
var m uint32
if m, _, err = leb128.DecodeUint32(r); err != nil {
err = fmt.Errorf("read max of limit: %v", err)
} else {
max = &m
}
default:
err = fmt.Errorf("%v for limits: %#x not in (0x00, 0x01, 0x02, 0x03)", ErrInvalidByte, flag)
}
shared = flag == 0x02 || flag == 0x03
return
}

View File

@ -0,0 +1,42 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/wasm"
)
// decodeMemory returns the api.Memory decoded with the WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory
func decodeMemory(
r *bytes.Reader,
enabledFeatures api.CoreFeatures,
memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32),
memoryLimitPages uint32,
) (*wasm.Memory, error) {
min, maxP, shared, err := decodeLimitsType(r)
if err != nil {
return nil, err
}
if shared {
if !enabledFeatures.IsEnabled(experimental.CoreFeaturesThreads) {
return nil, fmt.Errorf("shared memory requested but threads feature not enabled")
}
// This restriction may be lifted in the future.
// https://webassembly.github.io/threads/core/binary/types.html#memory-types
if maxP == nil {
return nil, fmt.Errorf("shared memory requires a maximum size to be specified")
}
}
min, capacity, max := memorySizer(min, maxP)
mem := &wasm.Memory{Min: min, Cap: capacity, Max: max, IsMaxEncoded: maxP != nil, IsShared: shared}
return mem, mem.Validate(memoryLimitPages)
}

View File

@ -0,0 +1,151 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
const (
// subsectionIDModuleName contains only the module name.
subsectionIDModuleName = uint8(0)
// subsectionIDFunctionNames is a map of indices to function names, in ascending order by function index
subsectionIDFunctionNames = uint8(1)
// subsectionIDLocalNames contain a map of function indices to a map of local indices to their names, in ascending
// order by function and local index
subsectionIDLocalNames = uint8(2)
)
// decodeNameSection deserializes the data associated with the "name" key in SectionIDCustom according to the
// standard:
//
// * ModuleName decode from subsection 0
// * FunctionNames decode from subsection 1
// * LocalNames decode from subsection 2
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) {
// TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based
// leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte.
result = &wasm.NameSection{}
// subsectionID is decoded if known, and skipped if not
var subsectionID uint8
// subsectionSize is the length to skip when the subsectionID is unknown
var subsectionSize uint32
var bytesRead uint64
for limit > 0 {
if subsectionID, err = r.ReadByte(); err != nil {
if err == io.EOF {
return result, nil
}
// TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer
return nil, fmt.Errorf("failed to read a subsection ID: %w", err)
}
limit--
if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil {
return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err)
}
limit -= bytesRead
switch subsectionID {
case subsectionIDModuleName:
if result.ModuleName, _, err = decodeUTF8(r, "module name"); err != nil {
return nil, err
}
case subsectionIDFunctionNames:
if result.FunctionNames, err = decodeFunctionNames(r); err != nil {
return nil, err
}
case subsectionIDLocalNames:
if result.LocalNames, err = decodeLocalNames(r); err != nil {
return nil, err
}
default: // Skip other subsections.
// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil {
return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err)
}
}
limit -= uint64(subsectionSize)
}
return
}
func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) {
functionCount, err := decodeFunctionCount(r, subsectionIDFunctionNames)
if err != nil {
return nil, err
}
result := make(wasm.NameMap, functionCount)
for i := uint32(0); i < functionCount; i++ {
functionIndex, err := decodeFunctionIndex(r, subsectionIDFunctionNames)
if err != nil {
return nil, err
}
name, _, err := decodeUTF8(r, "function[%d] name", functionIndex)
if err != nil {
return nil, err
}
result[i] = wasm.NameAssoc{Index: functionIndex, Name: name}
}
return result, nil
}
func decodeLocalNames(r *bytes.Reader) (wasm.IndirectNameMap, error) {
functionCount, err := decodeFunctionCount(r, subsectionIDLocalNames)
if err != nil {
return nil, err
}
result := make(wasm.IndirectNameMap, functionCount)
for i := uint32(0); i < functionCount; i++ {
functionIndex, err := decodeFunctionIndex(r, subsectionIDLocalNames)
if err != nil {
return nil, err
}
localCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("failed to read the local count for function[%d]: %w", functionIndex, err)
}
locals := make(wasm.NameMap, localCount)
for j := uint32(0); j < localCount; j++ {
localIndex, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("failed to read a local index of function[%d]: %w", functionIndex, err)
}
name, _, err := decodeUTF8(r, "function[%d] local[%d] name", functionIndex, localIndex)
if err != nil {
return nil, err
}
locals[j] = wasm.NameAssoc{Index: localIndex, Name: name}
}
result[i] = wasm.NameMapAssoc{Index: functionIndex, NameMap: locals}
}
return result, nil
}
func decodeFunctionIndex(r *bytes.Reader, subsectionID uint8) (uint32, error) {
functionIndex, _, err := leb128.DecodeUint32(r)
if err != nil {
return 0, fmt.Errorf("failed to read a function index in subsection[%d]: %w", subsectionID, err)
}
return functionIndex, nil
}
func decodeFunctionCount(r *bytes.Reader, subsectionID uint8) (uint32, error) {
functionCount, _, err := leb128.DecodeUint32(r)
if err != nil {
return 0, fmt.Errorf("failed to read the function count of subsection[%d]: %w", subsectionID, err)
}
return functionCount, nil
}

View File

@ -0,0 +1,226 @@
package binary
import (
"bytes"
"fmt"
"io"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeTypeSection(enabledFeatures api.CoreFeatures, r *bytes.Reader) ([]wasm.FunctionType, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]wasm.FunctionType, vs)
for i := uint32(0); i < vs; i++ {
if err = decodeFunctionType(enabledFeatures, r, &result[i]); err != nil {
return nil, fmt.Errorf("read %d-th type: %v", i, err)
}
}
return result, nil
}
// decodeImportSection decodes the decoded import segments plus the count per wasm.ExternType.
func decodeImportSection(
r *bytes.Reader,
memorySizer memorySizer,
memoryLimitPages uint32,
enabledFeatures api.CoreFeatures,
) (result []wasm.Import,
perModule map[string][]*wasm.Import,
funcCount, globalCount, memoryCount, tableCount wasm.Index, err error,
) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
err = fmt.Errorf("get size of vector: %w", err)
return
}
perModule = make(map[string][]*wasm.Import)
result = make([]wasm.Import, vs)
for i := uint32(0); i < vs; i++ {
imp := &result[i]
if err = decodeImport(r, i, memorySizer, memoryLimitPages, enabledFeatures, imp); err != nil {
return
}
switch imp.Type {
case wasm.ExternTypeFunc:
imp.IndexPerType = funcCount
funcCount++
case wasm.ExternTypeGlobal:
imp.IndexPerType = globalCount
globalCount++
case wasm.ExternTypeMemory:
imp.IndexPerType = memoryCount
memoryCount++
case wasm.ExternTypeTable:
imp.IndexPerType = tableCount
tableCount++
}
perModule[imp.Module] = append(perModule[imp.Module], imp)
}
return
}
func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]uint32, vs)
for i := uint32(0); i < vs; i++ {
if result[i], _, err = leb128.DecodeUint32(r); err != nil {
return nil, fmt.Errorf("get type index: %w", err)
}
}
return result, err
}
func decodeTableSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.Table, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("error reading size")
}
if vs > 1 {
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return nil, fmt.Errorf("at most one table allowed in module as %w", err)
}
}
ret := make([]wasm.Table, vs)
for i := range ret {
err = decodeTable(r, enabledFeatures, &ret[i])
if err != nil {
return nil, err
}
}
return ret, nil
}
func decodeMemorySection(
r *bytes.Reader,
enabledFeatures api.CoreFeatures,
memorySizer memorySizer,
memoryLimitPages uint32,
) (*wasm.Memory, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("error reading size")
}
if vs > 1 {
return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs)
} else if vs == 0 {
// memory count can be zero.
return nil, nil
}
return decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages)
}
func decodeGlobalSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.Global, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]wasm.Global, vs)
for i := uint32(0); i < vs; i++ {
if err = decodeGlobal(r, enabledFeatures, &result[i]); err != nil {
return nil, fmt.Errorf("global[%d]: %w", i, err)
}
}
return result, nil
}
func decodeExportSection(r *bytes.Reader) ([]wasm.Export, map[string]*wasm.Export, error) {
vs, _, sizeErr := leb128.DecodeUint32(r)
if sizeErr != nil {
return nil, nil, fmt.Errorf("get size of vector: %v", sizeErr)
}
exportMap := make(map[string]*wasm.Export, vs)
exportSection := make([]wasm.Export, vs)
for i := wasm.Index(0); i < vs; i++ {
export := &exportSection[i]
err := decodeExport(r, export)
if err != nil {
return nil, nil, fmt.Errorf("read export: %w", err)
}
if _, ok := exportMap[export.Name]; ok {
return nil, nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name)
} else {
exportMap[export.Name] = export
}
}
return exportSection, exportMap, nil
}
func decodeStartSection(r *bytes.Reader) (*wasm.Index, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get function index: %w", err)
}
return &vs, nil
}
func decodeElementSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.ElementSegment, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]wasm.ElementSegment, vs)
for i := uint32(0); i < vs; i++ {
if err = decodeElementSegment(r, enabledFeatures, &result[i]); err != nil {
return nil, fmt.Errorf("read element: %w", err)
}
}
return result, nil
}
func decodeCodeSection(r *bytes.Reader) ([]wasm.Code, error) {
codeSectionStart := uint64(r.Len())
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]wasm.Code, vs)
for i := uint32(0); i < vs; i++ {
err = decodeCode(r, codeSectionStart, &result[i])
if err != nil {
return nil, fmt.Errorf("read %d-th code segment: %v", i, err)
}
}
return result, nil
}
func decodeDataSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.DataSegment, error) {
vs, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, fmt.Errorf("get size of vector: %w", err)
}
result := make([]wasm.DataSegment, vs)
for i := uint32(0); i < vs; i++ {
if err = decodeDataSegment(r, enabledFeatures, &result[i]); err != nil {
return nil, fmt.Errorf("read data segment: %w", err)
}
}
return result, nil
}
func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) {
v, _, err := leb128.DecodeUint32(r)
if err != nil && err != io.EOF {
// data count is optional, so EOF is fine.
return nil, err
}
return &v, nil
}

View File

@ -0,0 +1,43 @@
package binary
import (
"bytes"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasm"
)
// decodeTable returns the wasm.Table decoded with the WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Table) (err error) {
ret.Type, err = r.ReadByte()
if err != nil {
return fmt.Errorf("read leading byte: %v", err)
}
if ret.Type != wasm.RefTypeFuncref {
if err = enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
return fmt.Errorf("table type funcref is invalid: %w", err)
}
}
var shared bool
ret.Min, ret.Max, shared, err = decodeLimitsType(r)
if err != nil {
return fmt.Errorf("read limits: %v", err)
}
if ret.Min > wasm.MaximumFunctionIndex {
return fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex)
}
if ret.Max != nil {
if *ret.Max < ret.Min {
return fmt.Errorf("table size minimum must not be greater than maximum")
}
}
if shared {
return fmt.Errorf("tables cannot be marked as shared")
}
return
}

View File

@ -0,0 +1,60 @@
package binary
import (
"bytes"
"fmt"
"io"
"unicode/utf8"
"unsafe"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
if num == 0 {
return nil, nil
}
ret := make([]wasm.ValueType, num)
_, err := io.ReadFull(r, ret)
if err != nil {
return nil, err
}
for _, v := range ret {
switch v {
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128:
default:
return nil, fmt.Errorf("invalid value type: %d", v)
}
}
return ret, nil
}
// decodeUTF8 decodes a size prefixed string from the reader, returning it and the count of bytes read.
// contextFormat and contextArgs apply an error format when present
func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{}) (string, uint32, error) {
size, sizeOfSize, err := leb128.DecodeUint32(r)
if err != nil {
return "", 0, fmt.Errorf("failed to read %s size: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
}
if size == 0 {
return "", uint32(sizeOfSize), nil
}
buf := make([]byte, size)
if _, err = io.ReadFull(r, buf); err != nil {
return "", 0, fmt.Errorf("failed to read %s: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
}
if !utf8.Valid(buf) {
return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...))
}
// TODO: use unsafe.String after flooring Go 1.20.
ret := *(*string)(unsafe.Pointer(&buf))
return ret, size + uint32(sizeOfSize), nil
}

View File

@ -0,0 +1,51 @@
package wasm
import "fmt"
// SectionElementCount returns the count of elements in a given section ID
//
// For example...
// * SectionIDType returns the count of FunctionType
// * SectionIDCustom returns the count of CustomSections plus one if NameSection is present
// * SectionIDHostFunction returns the count of HostFunctionSection
// * SectionIDExport returns the count of unique export names
func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
switch sectionID {
case SectionIDCustom:
numCustomSections := uint32(len(m.CustomSections))
if m.NameSection != nil {
numCustomSections++
}
return numCustomSections
case SectionIDType:
return uint32(len(m.TypeSection))
case SectionIDImport:
return uint32(len(m.ImportSection))
case SectionIDFunction:
return uint32(len(m.FunctionSection))
case SectionIDTable:
return uint32(len(m.TableSection))
case SectionIDMemory:
if m.MemorySection != nil {
return 1
}
return 0
case SectionIDGlobal:
return uint32(len(m.GlobalSection))
case SectionIDExport:
return uint32(len(m.ExportSection))
case SectionIDStart:
if m.StartSection != nil {
return 1
}
return 0
case SectionIDElement:
return uint32(len(m.ElementSection))
case SectionIDCode:
return uint32(len(m.CodeSection))
case SectionIDData:
return uint32(len(m.DataSection))
default:
panic(fmt.Errorf("BUG: unknown section: %d", sectionID))
}
}

View File

@ -0,0 +1,72 @@
package wasm
import (
"context"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
)
// Engine is a Store-scoped mechanism to compile functions declared or imported by a module.
// This is a top-level type implemented by an interpreter or compiler.
type Engine interface {
// Close closes this engine, and releases all the compiled cache.
Close() (err error)
// CompileModule implements the same method as documented on wasm.Engine.
CompileModule(ctx context.Context, module *Module, listeners []experimental.FunctionListener, ensureTermination bool) error
// CompiledModuleCount is exported for testing, to track the size of the compilation cache.
CompiledModuleCount() uint32
// DeleteCompiledModule releases compilation caches for the given module (source).
// Note: it is safe to call this function for a module from which module instances are instantiated even when these
// module instances have outstanding calls.
DeleteCompiledModule(module *Module)
// NewModuleEngine compiles down the function instances in a module, and returns ModuleEngine for the module.
//
// * module is the source module from which moduleFunctions are instantiated. This is used for caching.
// * instance is the *ModuleInstance which is created from `module`.
//
// Note: Input parameters must be pre-validated with wasm.Module Validate, to ensure no fields are invalid
// due to reasons such as out-of-bounds.
NewModuleEngine(module *Module, instance *ModuleInstance) (ModuleEngine, error)
}
// ModuleEngine implements function calls for a given module.
type ModuleEngine interface {
// DoneInstantiation is called at the end of the instantiation of the module.
DoneInstantiation()
// NewFunction returns an api.Function for the given function pointed by the given Index.
NewFunction(index Index) api.Function
// ResolveImportedFunction is used to add imported functions needed to make this ModuleEngine fully functional.
// - `index` is the function Index of this imported function.
// - `indexInImportedModule` is the function Index of the imported function in the imported module.
// - `importedModuleEngine` is the ModuleEngine for the imported ModuleInstance.
ResolveImportedFunction(index, indexInImportedModule Index, importedModuleEngine ModuleEngine)
// ResolveImportedMemory is called when this module imports a memory from another module.
ResolveImportedMemory(importedModuleEngine ModuleEngine)
// LookupFunction returns the FunctionModule and the Index of the function in the returned ModuleInstance at the given offset in the table.
LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (*ModuleInstance, Index)
// GetGlobalValue returns the value of the global variable at the given Index.
// Only called when OwnsGlobals() returns true, and must not be called for imported globals
GetGlobalValue(idx Index) (lo, hi uint64)
// SetGlobalValue sets the value of the global variable at the given Index.
// Only called when OwnsGlobals() returns true, and must not be called for imported globals
SetGlobalValue(idx Index, lo, hi uint64)
// OwnsGlobals returns true if this ModuleEngine owns the global variables. If true, wasm.GlobalInstance's Val,ValHi should
// not be accessed directly.
OwnsGlobals() bool
// FunctionInstanceReference returns Reference for the given Index for a FunctionInstance. The returned values are used by
// the initialization via ElementSegment.
FunctionInstanceReference(funcIndex Index) Reference
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,188 @@
package wasm
import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/internalapi"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
// ImportedFunctions returns the definitions of each imported function.
//
// Note: Unlike ExportedFunctions, there is no unique constraint on imports.
func (m *Module) ImportedFunctions() (ret []api.FunctionDefinition) {
for i := uint32(0); i < m.ImportFunctionCount; i++ {
ret = append(ret, m.FunctionDefinition(i))
}
return
}
// ExportedFunctions returns the definitions of each exported function.
func (m *Module) ExportedFunctions() map[string]api.FunctionDefinition {
ret := map[string]api.FunctionDefinition{}
for i := range m.ExportSection {
exp := &m.ExportSection[i]
if exp.Type == ExternTypeFunc {
d := m.FunctionDefinition(exp.Index)
ret[exp.Name] = d
}
}
return ret
}
// FunctionDefinition returns the FunctionDefinition for the given `index`.
func (m *Module) FunctionDefinition(index Index) *FunctionDefinition {
// TODO: function initialization is lazy, but bulk. Make it per function.
m.buildFunctionDefinitions()
return &m.FunctionDefinitionSection[index]
}
// buildFunctionDefinitions generates function metadata that can be parsed from
// the module. This must be called after all validation.
func (m *Module) buildFunctionDefinitions() {
m.functionDefinitionSectionInitOnce.Do(m.buildFunctionDefinitionsOnce)
}
func (m *Module) buildFunctionDefinitionsOnce() {
var moduleName string
var functionNames NameMap
var localNames, resultNames IndirectNameMap
if m.NameSection != nil {
moduleName = m.NameSection.ModuleName
functionNames = m.NameSection.FunctionNames
localNames = m.NameSection.LocalNames
resultNames = m.NameSection.ResultNames
}
importCount := m.ImportFunctionCount
m.FunctionDefinitionSection = make([]FunctionDefinition, importCount+uint32(len(m.FunctionSection)))
importFuncIdx := Index(0)
for i := range m.ImportSection {
imp := &m.ImportSection[i]
if imp.Type != ExternTypeFunc {
continue
}
def := &m.FunctionDefinitionSection[importFuncIdx]
def.importDesc = imp
def.index = importFuncIdx
def.Functype = &m.TypeSection[imp.DescFunc]
importFuncIdx++
}
for codeIndex, typeIndex := range m.FunctionSection {
code := &m.CodeSection[codeIndex]
idx := importFuncIdx + Index(codeIndex)
def := &m.FunctionDefinitionSection[idx]
def.index = idx
def.Functype = &m.TypeSection[typeIndex]
def.goFunc = code.GoFunc
}
n, nLen := 0, len(functionNames)
for i := range m.FunctionDefinitionSection {
d := &m.FunctionDefinitionSection[i]
// The function name section begins with imports, but can be sparse.
// This keeps track of how far in the name section we've searched.
funcIdx := d.index
var funcName string
for ; n < nLen; n++ {
next := &functionNames[n]
if next.Index > funcIdx {
break // we have function names, but starting at a later index.
} else if next.Index == funcIdx {
funcName = next.Name
break
}
}
d.moduleName = moduleName
d.name = funcName
d.Debugname = wasmdebug.FuncName(moduleName, funcName, funcIdx)
d.paramNames = paramNames(localNames, funcIdx, len(d.Functype.Params))
d.resultNames = paramNames(resultNames, funcIdx, len(d.Functype.Results))
for i := range m.ExportSection {
e := &m.ExportSection[i]
if e.Type == ExternTypeFunc && e.Index == funcIdx {
d.exportNames = append(d.exportNames, e.Name)
}
}
}
}
// FunctionDefinition implements api.FunctionDefinition
type FunctionDefinition struct {
internalapi.WazeroOnlyType
moduleName string
index Index
name string
// Debugname is exported for testing purpose.
Debugname string
goFunc interface{}
// Functype is exported for testing purpose.
Functype *FunctionType
importDesc *Import
exportNames []string
paramNames []string
resultNames []string
}
// ModuleName implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) ModuleName() string {
return f.moduleName
}
// Index implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) Index() uint32 {
return f.index
}
// Name implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) Name() string {
return f.name
}
// DebugName implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) DebugName() string {
return f.Debugname
}
// Import implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) Import() (moduleName, name string, isImport bool) {
if f.importDesc != nil {
importDesc := f.importDesc
moduleName, name, isImport = importDesc.Module, importDesc.Name, true
}
return
}
// ExportNames implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) ExportNames() []string {
return f.exportNames
}
// GoFunction implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) GoFunction() interface{} {
return f.goFunc
}
// ParamTypes implements api.FunctionDefinition ParamTypes.
func (f *FunctionDefinition) ParamTypes() []ValueType {
return f.Functype.Params
}
// ParamNames implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) ParamNames() []string {
return f.paramNames
}
// ResultTypes implements api.FunctionDefinition ResultTypes.
func (f *FunctionDefinition) ResultTypes() []ValueType {
return f.Functype.Results
}
// ResultNames implements the same method as documented on api.FunctionDefinition.
func (f *FunctionDefinition) ResultNames() []string {
return f.resultNames
}

View File

@ -0,0 +1,55 @@
package wasm
import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/internalapi"
)
// constantGlobal wraps GlobalInstance to implement api.Global.
type constantGlobal struct {
internalapi.WazeroOnlyType
g *GlobalInstance
}
// Type implements api.Global.
func (g constantGlobal) Type() api.ValueType {
return g.g.Type.ValType
}
// Get implements api.Global.
func (g constantGlobal) Get() uint64 {
ret, _ := g.g.Value()
return ret
}
// String implements api.Global.
func (g constantGlobal) String() string {
return g.g.String()
}
// mutableGlobal extends constantGlobal to allow updates.
type mutableGlobal struct {
internalapi.WazeroOnlyType
g *GlobalInstance
}
// Type implements api.Global.
func (g mutableGlobal) Type() api.ValueType {
return g.g.Type.ValType
}
// Get implements api.Global.
func (g mutableGlobal) Get() uint64 {
ret, _ := g.g.Value()
return ret
}
// String implements api.Global.
func (g mutableGlobal) String() string {
return g.g.String()
}
// Set implements the same method as documented on api.MutableGlobal.
func (g mutableGlobal) Set(v uint64) {
g.g.SetValue(v, 0)
}

View File

@ -0,0 +1,279 @@
package wasm
import (
"bytes"
"context"
"errors"
"fmt"
"math"
"reflect"
"github.com/tetratelabs/wazero/api"
)
type paramsKind byte
const (
paramsKindNoContext paramsKind = iota
paramsKindContext
paramsKindContextModule
)
// Below are reflection code to get the interface type used to parse functions and set values.
var (
moduleType = reflect.TypeOf((*api.Module)(nil)).Elem()
goContextType = reflect.TypeOf((*context.Context)(nil)).Elem()
errorType = reflect.TypeOf((*error)(nil)).Elem()
)
// compile-time check to ensure reflectGoModuleFunction implements
// api.GoModuleFunction.
var _ api.GoModuleFunction = (*reflectGoModuleFunction)(nil)
type reflectGoModuleFunction struct {
fn *reflect.Value
params, results []ValueType
}
// Call implements the same method as documented on api.GoModuleFunction.
func (f *reflectGoModuleFunction) Call(ctx context.Context, mod api.Module, stack []uint64) {
callGoFunc(ctx, mod, f.fn, stack)
}
// EqualTo is exposed for testing.
func (f *reflectGoModuleFunction) EqualTo(that interface{}) bool {
if f2, ok := that.(*reflectGoModuleFunction); !ok {
return false
} else {
// TODO compare reflect pointers
return bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
}
}
// compile-time check to ensure reflectGoFunction implements api.GoFunction.
var _ api.GoFunction = (*reflectGoFunction)(nil)
type reflectGoFunction struct {
fn *reflect.Value
pk paramsKind
params, results []ValueType
}
// EqualTo is exposed for testing.
func (f *reflectGoFunction) EqualTo(that interface{}) bool {
if f2, ok := that.(*reflectGoFunction); !ok {
return false
} else {
// TODO compare reflect pointers
return f.pk == f2.pk &&
bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
}
}
// Call implements the same method as documented on api.GoFunction.
func (f *reflectGoFunction) Call(ctx context.Context, stack []uint64) {
if f.pk == paramsKindNoContext {
ctx = nil
}
callGoFunc(ctx, nil, f.fn, stack)
}
// callGoFunc executes the reflective function by converting params to Go
// types. The results of the function call are converted back to api.ValueType.
func callGoFunc(ctx context.Context, mod api.Module, fn *reflect.Value, stack []uint64) {
tp := fn.Type()
var in []reflect.Value
pLen := tp.NumIn()
if pLen != 0 {
in = make([]reflect.Value, pLen)
i := 0
if ctx != nil {
in[0] = newContextVal(ctx)
i++
}
if mod != nil {
in[1] = newModuleVal(mod)
i++
}
for j := 0; i < pLen; i++ {
next := tp.In(i)
val := reflect.New(next).Elem()
k := next.Kind()
raw := stack[j]
j++
switch k {
case reflect.Float32:
val.SetFloat(float64(math.Float32frombits(uint32(raw))))
case reflect.Float64:
val.SetFloat(math.Float64frombits(raw))
case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
val.SetUint(raw)
case reflect.Int32, reflect.Int64:
val.SetInt(int64(raw))
default:
panic(fmt.Errorf("BUG: param[%d] has an invalid type: %v", i, k))
}
in[i] = val
}
}
// Execute the host function and push back the call result onto the stack.
for i, ret := range fn.Call(in) {
switch ret.Kind() {
case reflect.Float32:
stack[i] = uint64(math.Float32bits(float32(ret.Float())))
case reflect.Float64:
stack[i] = math.Float64bits(ret.Float())
case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
stack[i] = ret.Uint()
case reflect.Int32, reflect.Int64:
stack[i] = uint64(ret.Int())
default:
panic(fmt.Errorf("BUG: result[%d] has an invalid type: %v", i, ret.Kind()))
}
}
}
func newContextVal(ctx context.Context) reflect.Value {
val := reflect.New(goContextType).Elem()
val.Set(reflect.ValueOf(ctx))
return val
}
func newModuleVal(m api.Module) reflect.Value {
val := reflect.New(moduleType).Elem()
val.Set(reflect.ValueOf(m))
return val
}
// MustParseGoReflectFuncCode parses Code from the go function or panics.
//
// Exposing this simplifies FunctionDefinition of host functions in built-in host
// modules and tests.
func MustParseGoReflectFuncCode(fn interface{}) Code {
_, _, code, err := parseGoReflectFunc(fn)
if err != nil {
panic(err)
}
return code
}
func parseGoReflectFunc(fn interface{}) (params, results []ValueType, code Code, err error) {
fnV := reflect.ValueOf(fn)
p := fnV.Type()
if fnV.Kind() != reflect.Func {
err = fmt.Errorf("kind != func: %s", fnV.Kind().String())
return
}
pk, kindErr := kind(p)
if kindErr != nil {
err = kindErr
return
}
pOffset := 0
switch pk {
case paramsKindNoContext:
case paramsKindContext:
pOffset = 1
case paramsKindContextModule:
pOffset = 2
}
pCount := p.NumIn() - pOffset
if pCount > 0 {
params = make([]ValueType, pCount)
}
for i := 0; i < len(params); i++ {
pI := p.In(i + pOffset)
if t, ok := getTypeOf(pI.Kind()); ok {
params[i] = t
continue
}
// Now, we will definitely err, decide which message is best
var arg0Type reflect.Type
if hc := pI.Implements(moduleType); hc {
arg0Type = moduleType
} else if gc := pI.Implements(goContextType); gc {
arg0Type = goContextType
}
if arg0Type != nil {
err = fmt.Errorf("param[%d] is a %s, which may be defined only once as param[0]", i+pOffset, arg0Type)
} else {
err = fmt.Errorf("param[%d] is unsupported: %s", i+pOffset, pI.Kind())
}
return
}
rCount := p.NumOut()
if rCount > 0 {
results = make([]ValueType, rCount)
}
for i := 0; i < len(results); i++ {
rI := p.Out(i)
if t, ok := getTypeOf(rI.Kind()); ok {
results[i] = t
continue
}
// Now, we will definitely err, decide which message is best
if rI.Implements(errorType) {
err = fmt.Errorf("result[%d] is an error, which is unsupported", i)
} else {
err = fmt.Errorf("result[%d] is unsupported: %s", i, rI.Kind())
}
return
}
code = Code{}
if pk == paramsKindContextModule {
code.GoFunc = &reflectGoModuleFunction{fn: &fnV, params: params, results: results}
} else {
code.GoFunc = &reflectGoFunction{pk: pk, fn: &fnV, params: params, results: results}
}
return
}
func kind(p reflect.Type) (paramsKind, error) {
pCount := p.NumIn()
if pCount > 0 && p.In(0).Kind() == reflect.Interface {
p0 := p.In(0)
if p0.Implements(moduleType) {
return 0, errors.New("invalid signature: api.Module parameter must be preceded by context.Context")
} else if p0.Implements(goContextType) {
if pCount >= 2 && p.In(1).Implements(moduleType) {
return paramsKindContextModule, nil
}
return paramsKindContext, nil
}
}
// Without context param allows portability with reflective runtimes.
// This allows people to more easily port to wazero.
return paramsKindNoContext, nil
}
func getTypeOf(kind reflect.Kind) (ValueType, bool) {
switch kind {
case reflect.Float64:
return ValueTypeF64, true
case reflect.Float32:
return ValueTypeF32, true
case reflect.Int32, reflect.Uint32:
return ValueTypeI32, true
case reflect.Int64, reflect.Uint64:
return ValueTypeI64, true
case reflect.Uintptr:
return ValueTypeExternref, true
default:
return 0x00, false
}
}

View File

@ -0,0 +1,179 @@
package wasm
import (
"errors"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/wasmdebug"
)
type HostFuncExporter interface {
ExportHostFunc(*HostFunc)
}
// HostFunc is a function with an inlined type, used for NewHostModule.
// Any corresponding FunctionType will be reused or added to the Module.
type HostFunc struct {
// ExportName is the only value returned by api.FunctionDefinition.
ExportName string
// Name is equivalent to the same method on api.FunctionDefinition.
Name string
// ParamTypes is equivalent to the same method on api.FunctionDefinition.
ParamTypes []ValueType
// ParamNames is equivalent to the same method on api.FunctionDefinition.
ParamNames []string
// ResultTypes is equivalent to the same method on api.FunctionDefinition.
ResultTypes []ValueType
// ResultNames is equivalent to the same method on api.FunctionDefinition.
ResultNames []string
// Code is the equivalent function in the SectionIDCode.
Code Code
}
// WithGoModuleFunc returns a copy of the function, replacing its Code.GoFunc.
func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc {
ret := *f
ret.Code.GoFunc = fn
return &ret
}
// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
func NewHostModule(
moduleName string,
exportNames []string,
nameToHostFunc map[string]*HostFunc,
enabledFeatures api.CoreFeatures,
) (m *Module, err error) {
if moduleName != "" {
m = &Module{NameSection: &NameSection{ModuleName: moduleName}}
} else {
return nil, errors.New("a module name must not be empty")
}
if exportCount := uint32(len(nameToHostFunc)); exportCount > 0 {
m.ExportSection = make([]Export, 0, exportCount)
m.Exports = make(map[string]*Export, exportCount)
if err = addFuncs(m, exportNames, nameToHostFunc, enabledFeatures); err != nil {
return
}
}
m.IsHostModule = true
// Uses the address of *wasm.Module as the module ID so that host functions can have each state per compilation.
// Downside of this is that compilation cache on host functions (trampoline codes for Go functions and
// Wasm codes for Wasm-implemented host functions) are not available and compiles each time. On the other hand,
// compilation of host modules is not costly as it's merely small trampolines vs the real-world native Wasm binary.
// TODO: refactor engines so that we can properly cache compiled machine codes for host modules.
m.AssignModuleID([]byte(fmt.Sprintf("@@@@@@@@%p", m)), // @@@@@@@@ = any 8 bytes different from Wasm header.
nil, false)
return
}
func addFuncs(
m *Module,
exportNames []string,
nameToHostFunc map[string]*HostFunc,
enabledFeatures api.CoreFeatures,
) (err error) {
if m.NameSection == nil {
m.NameSection = &NameSection{}
}
moduleName := m.NameSection.ModuleName
for _, k := range exportNames {
hf := nameToHostFunc[k]
if hf.Name == "" {
hf.Name = k // default name to export name
}
switch hf.Code.GoFunc.(type) {
case api.GoModuleFunction, api.GoFunction:
continue // already parsed
}
// Resolve the code using reflection
hf.ParamTypes, hf.ResultTypes, hf.Code, err = parseGoReflectFunc(hf.Code.GoFunc)
if err != nil {
return fmt.Errorf("func[%s.%s] %w", moduleName, k, err)
}
// Assign names to the function, if they exist.
params := hf.ParamTypes
if paramNames := hf.ParamNames; paramNames != nil {
if paramNamesLen := len(paramNames); paramNamesLen != len(params) {
return fmt.Errorf("func[%s.%s] has %d params, but %d params names", moduleName, k, paramNamesLen, len(params))
}
}
results := hf.ResultTypes
if resultNames := hf.ResultNames; resultNames != nil {
if resultNamesLen := len(resultNames); resultNamesLen != len(results) {
return fmt.Errorf("func[%s.%s] has %d results, but %d results names", moduleName, k, resultNamesLen, len(results))
}
}
}
funcCount := uint32(len(exportNames))
m.NameSection.FunctionNames = make([]NameAssoc, 0, funcCount)
m.FunctionSection = make([]Index, 0, funcCount)
m.CodeSection = make([]Code, 0, funcCount)
idx := Index(0)
for _, name := range exportNames {
hf := nameToHostFunc[name]
debugName := wasmdebug.FuncName(moduleName, name, idx)
typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures)
if typeErr != nil {
return fmt.Errorf("func[%s] %v", debugName, typeErr)
}
m.FunctionSection = append(m.FunctionSection, typeIdx)
m.CodeSection = append(m.CodeSection, hf.Code)
export := hf.ExportName
m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx})
m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1]
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, NameAssoc{Index: idx, Name: hf.Name})
if len(hf.ParamNames) > 0 {
localNames := NameMapAssoc{Index: idx}
for i, n := range hf.ParamNames {
localNames.NameMap = append(localNames.NameMap, NameAssoc{Index: Index(i), Name: n})
}
m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames)
}
if len(hf.ResultNames) > 0 {
resultNames := NameMapAssoc{Index: idx}
for i, n := range hf.ResultNames {
resultNames.NameMap = append(resultNames.NameMap, NameAssoc{Index: Index(i), Name: n})
}
m.NameSection.ResultNames = append(m.NameSection.ResultNames, resultNames)
}
idx++
}
return nil
}
func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures api.CoreFeatures) (Index, error) {
if len(results) > 1 {
// Guard >1.0 feature multi-value
if err := enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
return 0, fmt.Errorf("multiple result types invalid as %v", err)
}
}
for i := range m.TypeSection {
t := &m.TypeSection[i]
if t.EqualsSignature(params, results) {
return Index(i), nil
}
}
result := m.SectionElementCount(SectionIDType)
m.TypeSection = append(m.TypeSection, FunctionType{Params: params, Results: results})
return result, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,461 @@
package wasm
import (
"container/list"
"encoding/binary"
"fmt"
"math"
"reflect"
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/internalapi"
"github.com/tetratelabs/wazero/internal/wasmruntime"
)
const (
// MemoryPageSize is the unit of memory length in WebAssembly,
// and is defined as 2^16 = 65536.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
MemoryPageSize = uint32(65536)
// MemoryLimitPages is maximum number of pages defined (2^16).
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
MemoryLimitPages = uint32(65536)
// MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize".
MemoryPageSizeInBits = 16
)
// compile-time check to ensure MemoryInstance implements api.Memory
var _ api.Memory = &MemoryInstance{}
type waiters struct {
mux sync.Mutex
l *list.List
}
// MemoryInstance represents a memory instance in a store, and implements api.Memory.
//
// Note: In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means the precise memory is always
// wasm.Store Memories index zero: `store.Memories[0]`
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
type MemoryInstance struct {
internalapi.WazeroOnlyType
Buffer []byte
Min, Cap, Max uint32
Shared bool
// definition is known at compile time.
definition api.MemoryDefinition
// Mux is used in interpreter mode to prevent overlapping calls to atomic instructions,
// introduced with WebAssembly threads proposal.
Mux sync.Mutex
// waiters implements atomic wait and notify. It is implemented similarly to golang.org/x/sync/semaphore,
// with a fixed weight of 1 and no spurious notifications.
waiters sync.Map
expBuffer experimental.LinearMemory
}
// NewMemoryInstance creates a new instance based on the parameters in the SectionIDMemory.
func NewMemoryInstance(memSec *Memory, allocator experimental.MemoryAllocator) *MemoryInstance {
minBytes := MemoryPagesToBytesNum(memSec.Min)
capBytes := MemoryPagesToBytesNum(memSec.Cap)
maxBytes := MemoryPagesToBytesNum(memSec.Max)
var buffer []byte
var expBuffer experimental.LinearMemory
if allocator != nil {
expBuffer = allocator.Allocate(capBytes, maxBytes)
buffer = expBuffer.Reallocate(minBytes)
} else if memSec.IsShared {
// Shared memory needs a fixed buffer, so allocate with the maximum size.
//
// The rationale as to why we can simply use make([]byte) to a fixed buffer is that Go's GC is non-relocating.
// That is not a part of Go spec, but is well-known thing in Go community (wazero's compiler heavily relies on it!)
// * https://github.com/go4org/unsafe-assume-no-moving-gc
//
// Also, allocating Max here isn't harmful as the Go runtime uses mmap for large allocations, therefore,
// the memory buffer allocation here is virtual and doesn't consume physical memory until it's used.
// * https://github.com/golang/go/blob/8121604559035734c9677d5281bbdac8b1c17a1e/src/runtime/malloc.go#L1059
// * https://github.com/golang/go/blob/8121604559035734c9677d5281bbdac8b1c17a1e/src/runtime/malloc.go#L1165
buffer = make([]byte, minBytes, maxBytes)
} else {
buffer = make([]byte, minBytes, capBytes)
}
return &MemoryInstance{
Buffer: buffer,
Min: memSec.Min,
Cap: memoryBytesNumToPages(uint64(cap(buffer))),
Max: memSec.Max,
Shared: memSec.IsShared,
expBuffer: expBuffer,
}
}
// Definition implements the same method as documented on api.Memory.
func (m *MemoryInstance) Definition() api.MemoryDefinition {
return m.definition
}
// Size implements the same method as documented on api.Memory.
func (m *MemoryInstance) Size() uint32 {
return uint32(len(m.Buffer))
}
// ReadByte implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadByte(offset uint32) (byte, bool) {
if !m.hasSize(offset, 1) {
return 0, false
}
return m.Buffer[offset], true
}
// ReadUint16Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint16Le(offset uint32) (uint16, bool) {
if !m.hasSize(offset, 2) {
return 0, false
}
return binary.LittleEndian.Uint16(m.Buffer[offset : offset+2]), true
}
// ReadUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint32Le(offset uint32) (uint32, bool) {
return m.readUint32Le(offset)
}
// ReadFloat32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadFloat32Le(offset uint32) (float32, bool) {
v, ok := m.readUint32Le(offset)
if !ok {
return 0, false
}
return math.Float32frombits(v), true
}
// ReadUint64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadUint64Le(offset uint32) (uint64, bool) {
return m.readUint64Le(offset)
}
// ReadFloat64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) ReadFloat64Le(offset uint32) (float64, bool) {
v, ok := m.readUint64Le(offset)
if !ok {
return 0, false
}
return math.Float64frombits(v), true
}
// Read implements the same method as documented on api.Memory.
func (m *MemoryInstance) Read(offset, byteCount uint32) ([]byte, bool) {
if !m.hasSize(offset, uint64(byteCount)) {
return nil, false
}
return m.Buffer[offset : offset+byteCount : offset+byteCount], true
}
// WriteByte implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteByte(offset uint32, v byte) bool {
if !m.hasSize(offset, 1) {
return false
}
m.Buffer[offset] = v
return true
}
// WriteUint16Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint16Le(offset uint32, v uint16) bool {
if !m.hasSize(offset, 2) {
return false
}
binary.LittleEndian.PutUint16(m.Buffer[offset:], v)
return true
}
// WriteUint32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint32Le(offset, v uint32) bool {
return m.writeUint32Le(offset, v)
}
// WriteFloat32Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteFloat32Le(offset uint32, v float32) bool {
return m.writeUint32Le(offset, math.Float32bits(v))
}
// WriteUint64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteUint64Le(offset uint32, v uint64) bool {
return m.writeUint64Le(offset, v)
}
// WriteFloat64Le implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteFloat64Le(offset uint32, v float64) bool {
return m.writeUint64Le(offset, math.Float64bits(v))
}
// Write implements the same method as documented on api.Memory.
func (m *MemoryInstance) Write(offset uint32, val []byte) bool {
if !m.hasSize(offset, uint64(len(val))) {
return false
}
copy(m.Buffer[offset:], val)
return true
}
// WriteString implements the same method as documented on api.Memory.
func (m *MemoryInstance) WriteString(offset uint32, val string) bool {
if !m.hasSize(offset, uint64(len(val))) {
return false
}
copy(m.Buffer[offset:], val)
return true
}
// MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages.
func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
return uint64(pages) << MemoryPageSizeInBits
}
// Grow implements the same method as documented on api.Memory.
func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) {
currentPages := m.Pages()
if delta == 0 {
return currentPages, true
}
// If exceeds the max of memory size, we push -1 according to the spec.
newPages := currentPages + delta
if newPages > m.Max || int32(delta) < 0 {
return 0, false
} else if m.expBuffer != nil {
buffer := m.expBuffer.Reallocate(MemoryPagesToBytesNum(newPages))
if m.Shared {
if unsafe.SliceData(buffer) != unsafe.SliceData(m.Buffer) {
panic("shared memory cannot move, this is a bug in the memory allocator")
}
// We assume grow is called under a guest lock.
// But the memory length is accessed elsewhere,
// so use atomic to make the new length visible across threads.
atomicStoreLengthAndCap(&m.Buffer, uintptr(len(buffer)), uintptr(cap(buffer)))
m.Cap = memoryBytesNumToPages(uint64(cap(buffer)))
} else {
m.Buffer = buffer
m.Cap = newPages
}
return currentPages, true
} else if newPages > m.Cap { // grow the memory.
if m.Shared {
panic("shared memory cannot be grown, this is a bug in wazero")
}
m.Buffer = append(m.Buffer, make([]byte, MemoryPagesToBytesNum(delta))...)
m.Cap = newPages
return currentPages, true
} else { // We already have the capacity we need.
if m.Shared {
// We assume grow is called under a guest lock.
// But the memory length is accessed elsewhere,
// so use atomic to make the new length visible across threads.
atomicStoreLength(&m.Buffer, uintptr(MemoryPagesToBytesNum(newPages)))
} else {
m.Buffer = m.Buffer[:MemoryPagesToBytesNum(newPages)]
}
return currentPages, true
}
}
// Pages implements the same method as documented on api.Memory.
func (m *MemoryInstance) Pages() (result uint32) {
return memoryBytesNumToPages(uint64(len(m.Buffer)))
}
// PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. e.g. 1 -> "64Ki"
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
func PagesToUnitOfBytes(pages uint32) string {
k := pages * 64
if k < 1024 {
return fmt.Sprintf("%d Ki", k)
}
m := k / 1024
if m < 1024 {
return fmt.Sprintf("%d Mi", m)
}
g := m / 1024
if g < 1024 {
return fmt.Sprintf("%d Gi", g)
}
return fmt.Sprintf("%d Ti", g/1024)
}
// Below are raw functions used to implement the api.Memory API:
// Uses atomic write to update the length of a slice.
func atomicStoreLengthAndCap(slice *[]byte, length uintptr, cap uintptr) {
slicePtr := (*reflect.SliceHeader)(unsafe.Pointer(slice))
capPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Cap))
atomic.StoreUintptr(capPtr, cap)
lenPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Len))
atomic.StoreUintptr(lenPtr, length)
}
// Uses atomic write to update the length of a slice.
func atomicStoreLength(slice *[]byte, length uintptr) {
slicePtr := (*reflect.SliceHeader)(unsafe.Pointer(slice))
lenPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Len))
atomic.StoreUintptr(lenPtr, length)
}
// memoryBytesNumToPages converts the given number of bytes into the number of pages.
func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
return uint32(bytesNum >> MemoryPageSizeInBits)
}
// hasSize returns true if Len is sufficient for byteCount at the given offset.
//
// Note: This is always fine, because memory can grow, but never shrink.
func (m *MemoryInstance) hasSize(offset uint32, byteCount uint64) bool {
return uint64(offset)+byteCount <= uint64(len(m.Buffer)) // uint64 prevents overflow on add
}
// readUint32Le implements ReadUint32Le without using a context. This is extracted as both ints and floats are stored in
// memory as uint32le.
func (m *MemoryInstance) readUint32Le(offset uint32) (uint32, bool) {
if !m.hasSize(offset, 4) {
return 0, false
}
return binary.LittleEndian.Uint32(m.Buffer[offset : offset+4]), true
}
// readUint64Le implements ReadUint64Le without using a context. This is extracted as both ints and floats are stored in
// memory as uint64le.
func (m *MemoryInstance) readUint64Le(offset uint32) (uint64, bool) {
if !m.hasSize(offset, 8) {
return 0, false
}
return binary.LittleEndian.Uint64(m.Buffer[offset : offset+8]), true
}
// writeUint32Le implements WriteUint32Le without using a context. This is extracted as both ints and floats are stored
// in memory as uint32le.
func (m *MemoryInstance) writeUint32Le(offset uint32, v uint32) bool {
if !m.hasSize(offset, 4) {
return false
}
binary.LittleEndian.PutUint32(m.Buffer[offset:], v)
return true
}
// writeUint64Le implements WriteUint64Le without using a context. This is extracted as both ints and floats are stored
// in memory as uint64le.
func (m *MemoryInstance) writeUint64Le(offset uint32, v uint64) bool {
if !m.hasSize(offset, 8) {
return false
}
binary.LittleEndian.PutUint64(m.Buffer[offset:], v)
return true
}
// Wait32 suspends the caller until the offset is notified by a different agent.
func (m *MemoryInstance) Wait32(offset uint32, exp uint32, timeout int64, reader func(mem *MemoryInstance, offset uint32) uint32) uint64 {
w := m.getWaiters(offset)
w.mux.Lock()
cur := reader(m, offset)
if cur != exp {
w.mux.Unlock()
return 1
}
return m.wait(w, timeout)
}
// Wait64 suspends the caller until the offset is notified by a different agent.
func (m *MemoryInstance) Wait64(offset uint32, exp uint64, timeout int64, reader func(mem *MemoryInstance, offset uint32) uint64) uint64 {
w := m.getWaiters(offset)
w.mux.Lock()
cur := reader(m, offset)
if cur != exp {
w.mux.Unlock()
return 1
}
return m.wait(w, timeout)
}
func (m *MemoryInstance) wait(w *waiters, timeout int64) uint64 {
if w.l == nil {
w.l = list.New()
}
// The specification requires a trap if the number of existing waiters + 1 == 2^32, so we add a check here.
// In practice, it is unlikely the application would ever accumulate such a large number of waiters as it
// indicates several GB of RAM used just for the list of waiters.
// https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#wait
if uint64(w.l.Len()+1) == 1<<32 {
w.mux.Unlock()
panic(wasmruntime.ErrRuntimeTooManyWaiters)
}
ready := make(chan struct{})
elem := w.l.PushBack(ready)
w.mux.Unlock()
if timeout < 0 {
<-ready
return 0
} else {
select {
case <-ready:
return 0
case <-time.After(time.Duration(timeout)):
// While we could see if the channel completed by now and ignore the timeout, similar to x/sync/semaphore,
// the Wasm spec doesn't specify this behavior, so we keep things simple by prioritizing the timeout.
w.mux.Lock()
w.l.Remove(elem)
w.mux.Unlock()
return 2
}
}
}
func (m *MemoryInstance) getWaiters(offset uint32) *waiters {
wAny, ok := m.waiters.Load(offset)
if !ok {
// The first time an address is waited on, simultaneous waits will cause extra allocations.
// Further operations will be loaded above, which is also the general pattern of usage with
// mutexes.
wAny, _ = m.waiters.LoadOrStore(offset, &waiters{})
}
return wAny.(*waiters)
}
// Notify wakes up at most count waiters at the given offset.
func (m *MemoryInstance) Notify(offset uint32, count uint32) uint32 {
wAny, ok := m.waiters.Load(offset)
if !ok {
return 0
}
w := wAny.(*waiters)
w.mux.Lock()
defer w.mux.Unlock()
if w.l == nil {
return 0
}
res := uint32(0)
for num := w.l.Len(); num > 0 && res < count; num = w.l.Len() {
w := w.l.Remove(w.l.Front()).(chan struct{})
close(w)
res++
}
return res
}

View File

@ -0,0 +1,128 @@
package wasm
import (
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/internalapi"
)
// ImportedMemories implements the same method as documented on wazero.CompiledModule.
func (m *Module) ImportedMemories() (ret []api.MemoryDefinition) {
for i := range m.MemoryDefinitionSection {
d := &m.MemoryDefinitionSection[i]
if d.importDesc != nil {
ret = append(ret, d)
}
}
return
}
// ExportedMemories implements the same method as documented on wazero.CompiledModule.
func (m *Module) ExportedMemories() map[string]api.MemoryDefinition {
ret := map[string]api.MemoryDefinition{}
for i := range m.MemoryDefinitionSection {
d := &m.MemoryDefinitionSection[i]
for _, e := range d.exportNames {
ret[e] = d
}
}
return ret
}
// BuildMemoryDefinitions generates memory metadata that can be parsed from
// the module. This must be called after all validation.
//
// Note: This is exported for wazero.Runtime `CompileModule`.
func (m *Module) BuildMemoryDefinitions() {
var moduleName string
if m.NameSection != nil {
moduleName = m.NameSection.ModuleName
}
memoryCount := m.ImportMemoryCount
if m.MemorySection != nil {
memoryCount++
}
if memoryCount == 0 {
return
}
m.MemoryDefinitionSection = make([]MemoryDefinition, 0, memoryCount)
importMemIdx := Index(0)
for i := range m.ImportSection {
imp := &m.ImportSection[i]
if imp.Type != ExternTypeMemory {
continue
}
m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, MemoryDefinition{
importDesc: &[2]string{imp.Module, imp.Name},
index: importMemIdx,
memory: imp.DescMem,
})
importMemIdx++
}
if m.MemorySection != nil {
m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, MemoryDefinition{
index: importMemIdx,
memory: m.MemorySection,
})
}
for i := range m.MemoryDefinitionSection {
d := &m.MemoryDefinitionSection[i]
d.moduleName = moduleName
for i := range m.ExportSection {
e := &m.ExportSection[i]
if e.Type == ExternTypeMemory && e.Index == d.index {
d.exportNames = append(d.exportNames, e.Name)
}
}
}
}
// MemoryDefinition implements api.MemoryDefinition
type MemoryDefinition struct {
internalapi.WazeroOnlyType
moduleName string
index Index
importDesc *[2]string
exportNames []string
memory *Memory
}
// ModuleName implements the same method as documented on api.MemoryDefinition.
func (f *MemoryDefinition) ModuleName() string {
return f.moduleName
}
// Index implements the same method as documented on api.MemoryDefinition.
func (f *MemoryDefinition) Index() uint32 {
return f.index
}
// Import implements the same method as documented on api.MemoryDefinition.
func (f *MemoryDefinition) Import() (moduleName, name string, isImport bool) {
if importDesc := f.importDesc; importDesc != nil {
moduleName, name, isImport = importDesc[0], importDesc[1], true
}
return
}
// ExportNames implements the same method as documented on api.MemoryDefinition.
func (f *MemoryDefinition) ExportNames() []string {
return f.exportNames
}
// Min implements the same method as documented on api.MemoryDefinition.
func (f *MemoryDefinition) Min() uint32 {
return f.memory.Min
}
// Max implements the same method as documented on api.MemoryDefinition.
func (f *MemoryDefinition) Max() (max uint32, encoded bool) {
max = f.memory.Max
encoded = f.memory.IsMaxEncoded
return
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,251 @@
package wasm
import (
"context"
"errors"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/sys"
)
// FailIfClosed returns a sys.ExitError if CloseWithExitCode was called.
func (m *ModuleInstance) FailIfClosed() (err error) {
if closed := m.Closed.Load(); closed != 0 {
switch closed & exitCodeFlagMask {
case exitCodeFlagResourceClosed:
case exitCodeFlagResourceNotClosed:
// This happens when this module is closed asynchronously in CloseModuleOnCanceledOrTimeout,
// and the closure of resources have been deferred here.
_ = m.ensureResourcesClosed(context.Background())
}
return sys.NewExitError(uint32(closed >> 32)) // Unpack the high order bits as the exit code.
}
return nil
}
// CloseModuleOnCanceledOrTimeout take a context `ctx`, which might be a Cancel or Timeout context,
// and spawns the Goroutine to check the context is canceled ot deadline exceeded. If it reaches
// one of the conditions, it sets the appropriate exit code.
//
// Callers of this function must invoke the returned context.CancelFunc to release the spawned Goroutine.
func (m *ModuleInstance) CloseModuleOnCanceledOrTimeout(ctx context.Context) context.CancelFunc {
// Creating an empty channel in this case is a bit more efficient than
// creating a context.Context and canceling it with the same effect. We
// really just need to be notified when to stop listening to the users
// context. Closing the channel will unblock the select in the goroutine
// causing it to return an stop listening to ctx.Done().
cancelChan := make(chan struct{})
go m.closeModuleOnCanceledOrTimeout(ctx, cancelChan)
return func() { close(cancelChan) }
}
// closeModuleOnCanceledOrTimeout is extracted from CloseModuleOnCanceledOrTimeout for testing.
func (m *ModuleInstance) closeModuleOnCanceledOrTimeout(ctx context.Context, cancelChan <-chan struct{}) {
select {
case <-ctx.Done():
select {
case <-cancelChan:
// In some cases by the time this goroutine is scheduled, the caller
// has already closed both the context and the cancelChan. In this
// case go will randomize which branch of the outer select to enter
// and we don't want to close the module.
default:
// This is the same logic as CloseWithCtxErr except this calls closeWithExitCodeWithoutClosingResource
// so that we can defer the resource closure in FailIfClosed.
switch {
case errors.Is(ctx.Err(), context.Canceled):
// TODO: figure out how to report error here.
_ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeContextCanceled)
case errors.Is(ctx.Err(), context.DeadlineExceeded):
// TODO: figure out how to report error here.
_ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeDeadlineExceeded)
}
}
case <-cancelChan:
}
}
// CloseWithCtxErr closes the module with an exit code based on the type of
// error reported by the context.
//
// If the context's error is unknown or nil, the module does not close.
func (m *ModuleInstance) CloseWithCtxErr(ctx context.Context) {
switch {
case errors.Is(ctx.Err(), context.Canceled):
// TODO: figure out how to report error here.
_ = m.CloseWithExitCode(ctx, sys.ExitCodeContextCanceled)
case errors.Is(ctx.Err(), context.DeadlineExceeded):
// TODO: figure out how to report error here.
_ = m.CloseWithExitCode(ctx, sys.ExitCodeDeadlineExceeded)
}
}
// Name implements the same method as documented on api.Module
func (m *ModuleInstance) Name() string {
return m.ModuleName
}
// String implements the same method as documented on api.Module
func (m *ModuleInstance) String() string {
return fmt.Sprintf("Module[%s]", m.Name())
}
// Close implements the same method as documented on api.Module.
func (m *ModuleInstance) Close(ctx context.Context) (err error) {
return m.CloseWithExitCode(ctx, 0)
}
// CloseWithExitCode implements the same method as documented on api.Module.
func (m *ModuleInstance) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) {
return nil // not an error to have already closed
}
_ = m.s.deleteModule(m)
return m.ensureResourcesClosed(ctx)
}
// IsClosed implements the same method as documented on api.Module.
func (m *ModuleInstance) IsClosed() bool {
return m.Closed.Load() != 0
}
func (m *ModuleInstance) closeWithExitCodeWithoutClosingResource(exitCode uint32) (err error) {
if !m.setExitCode(exitCode, exitCodeFlagResourceNotClosed) {
return nil // not an error to have already closed
}
_ = m.s.deleteModule(m)
return nil
}
// closeWithExitCode is the same as CloseWithExitCode besides this doesn't delete it from Store.moduleList.
func (m *ModuleInstance) closeWithExitCode(ctx context.Context, exitCode uint32) (err error) {
if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) {
return nil // not an error to have already closed
}
return m.ensureResourcesClosed(ctx)
}
type exitCodeFlag = uint64
const exitCodeFlagMask = 0xff
const (
// exitCodeFlagResourceClosed indicates that the module was closed and resources were already closed.
exitCodeFlagResourceClosed = 1 << iota
// exitCodeFlagResourceNotClosed indicates that the module was closed while resources are not closed yet.
exitCodeFlagResourceNotClosed
)
func (m *ModuleInstance) setExitCode(exitCode uint32, flag exitCodeFlag) bool {
closed := flag | uint64(exitCode)<<32 // Store exitCode as high-order bits.
return m.Closed.CompareAndSwap(0, closed)
}
// ensureResourcesClosed ensures that resources assigned to ModuleInstance is released.
// Only one call will happen per module, due to external atomic guards on Closed.
func (m *ModuleInstance) ensureResourcesClosed(ctx context.Context) (err error) {
if closeNotifier := m.CloseNotifier; closeNotifier != nil { // experimental
closeNotifier.CloseNotify(ctx, uint32(m.Closed.Load()>>32))
m.CloseNotifier = nil
}
if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder
err = sysCtx.FS().Close()
m.Sys = nil
}
if mem := m.MemoryInstance; mem != nil {
if mem.expBuffer != nil {
mem.expBuffer.Free()
mem.expBuffer = nil
}
}
if m.CodeCloser != nil {
if e := m.CodeCloser.Close(ctx); err == nil {
err = e
}
m.CodeCloser = nil
}
return err
}
// Memory implements the same method as documented on api.Module.
func (m *ModuleInstance) Memory() api.Memory {
return m.MemoryInstance
}
// ExportedMemory implements the same method as documented on api.Module.
func (m *ModuleInstance) ExportedMemory(name string) api.Memory {
_, err := m.getExport(name, ExternTypeMemory)
if err != nil {
return nil
}
// We Assume that we have at most one memory.
return m.MemoryInstance
}
// ExportedMemoryDefinitions implements the same method as documented on
// api.Module.
func (m *ModuleInstance) ExportedMemoryDefinitions() map[string]api.MemoryDefinition {
// Special case as we currently only support one memory.
if mem := m.MemoryInstance; mem != nil {
// Now, find out if it is exported
for name, exp := range m.Exports {
if exp.Type == ExternTypeMemory {
return map[string]api.MemoryDefinition{name: mem.definition}
}
}
}
return map[string]api.MemoryDefinition{}
}
// ExportedFunction implements the same method as documented on api.Module.
func (m *ModuleInstance) ExportedFunction(name string) api.Function {
exp, err := m.getExport(name, ExternTypeFunc)
if err != nil {
return nil
}
return m.Engine.NewFunction(exp.Index)
}
// ExportedFunctionDefinitions implements the same method as documented on
// api.Module.
func (m *ModuleInstance) ExportedFunctionDefinitions() map[string]api.FunctionDefinition {
result := map[string]api.FunctionDefinition{}
for name, exp := range m.Exports {
if exp.Type == ExternTypeFunc {
result[name] = m.Source.FunctionDefinition(exp.Index)
}
}
return result
}
// GlobalVal is an internal hack to get the lower 64 bits of a global.
func (m *ModuleInstance) GlobalVal(idx Index) uint64 {
return m.Globals[idx].Val
}
// ExportedGlobal implements the same method as documented on api.Module.
func (m *ModuleInstance) ExportedGlobal(name string) api.Global {
exp, err := m.getExport(name, ExternTypeGlobal)
if err != nil {
return nil
}
g := m.Globals[exp.Index]
if g.Type.Mutable {
return mutableGlobal{g: g}
}
return constantGlobal{g: g}
}
// NumGlobal implements experimental.InternalModule.
func (m *ModuleInstance) NumGlobal() int {
return len(m.Globals)
}
// Global implements experimental.InternalModule.
func (m *ModuleInstance) Global(idx int) api.Global {
return constantGlobal{g: m.Globals[idx]}
}

View File

@ -0,0 +1,73 @@
package wasm
import (
"context"
"fmt"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/internalapi"
)
// LookupFunction looks up the table by the given index, and returns the api.Function implementation if found,
// otherwise this panics according to the same semantics as call_indirect instruction.
// Currently, this is only used by emscripten which needs to do call_indirect-like operation in the host function.
func (m *ModuleInstance) LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) api.Function {
fm, index := m.Engine.LookupFunction(t, typeId, tableOffset)
if source := fm.Source; source.IsHostModule {
// This case, the found function is a host function stored in the table. Generally, Engine.NewFunction are only
// responsible for calling Wasm-defined functions (not designed for calling Go functions!). Hence we need to wrap
// the host function as a special case.
def := &source.FunctionDefinitionSection[index]
goF := source.CodeSection[index].GoFunc
switch typed := goF.(type) {
case api.GoFunction:
// GoFunction doesn't need looked up module.
return &lookedUpGoFunction{def: def, g: goFunctionAsGoModuleFunction(typed)}
case api.GoModuleFunction:
return &lookedUpGoFunction{def: def, lookedUpModule: m, g: typed}
default:
panic(fmt.Sprintf("unexpected GoFunc type: %T", goF))
}
} else {
return fm.Engine.NewFunction(index)
}
}
// lookedUpGoFunction implements lookedUpGoModuleFunction.
type lookedUpGoFunction struct {
internalapi.WazeroOnly
def *FunctionDefinition
// lookedUpModule is the *ModuleInstance from which this Go function is looked up, i.e. owner of the table.
lookedUpModule *ModuleInstance
g api.GoModuleFunction
}
// goFunctionAsGoModuleFunction converts api.GoFunction to api.GoModuleFunction which ignores the api.Module argument.
func goFunctionAsGoModuleFunction(g api.GoFunction) api.GoModuleFunction {
return api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {
g.Call(ctx, stack)
})
}
// Definition implements api.Function.
func (l *lookedUpGoFunction) Definition() api.FunctionDefinition { return l.def }
// Call implements api.Function.
func (l *lookedUpGoFunction) Call(ctx context.Context, params ...uint64) ([]uint64, error) {
typ := l.def.Functype
stackSize := typ.ParamNumInUint64
rn := typ.ResultNumInUint64
if rn > stackSize {
stackSize = rn
}
stack := make([]uint64, stackSize)
copy(stack, params)
return stack[:rn], l.CallWithStack(ctx, stack)
}
// CallWithStack implements api.Function.
func (l *lookedUpGoFunction) CallWithStack(ctx context.Context, stack []uint64) error {
// The Go host function always needs to access caller's module, in this case the one holding the table.
l.g.Call(ctx, l.lookedUpModule, stack)
return nil
}

View File

@ -0,0 +1,668 @@
package wasm
import (
"context"
"encoding/binary"
"fmt"
"sync"
"sync/atomic"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/internal/expctxkeys"
"github.com/tetratelabs/wazero/internal/internalapi"
"github.com/tetratelabs/wazero/internal/leb128"
internalsys "github.com/tetratelabs/wazero/internal/sys"
"github.com/tetratelabs/wazero/sys"
)
// nameToModuleShrinkThreshold is the size the nameToModule map can grow to
// before it starts to be monitored for shrinking.
// The capacity will never be smaller than this once the threshold is met.
const nameToModuleShrinkThreshold = 100
type (
// Store is the runtime representation of "instantiated" Wasm module and objects.
// Multiple modules can be instantiated within a single store, and each instance,
// (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection.
//
// Every type whose name ends with "Instance" suffix belongs to exactly one store.
//
// Note that store is not thread (concurrency) safe, meaning that using single Store
// via multiple goroutines might result in race conditions. In that case, the invocation
// and access to any methods and field of Store must be guarded by mutex.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
Store struct {
// moduleList ensures modules are closed in reverse initialization order.
moduleList *ModuleInstance // guarded by mux
// nameToModule holds the instantiated Wasm modules by module name from Instantiate.
// It ensures no race conditions instantiating two modules of the same name.
nameToModule map[string]*ModuleInstance // guarded by mux
// nameToModuleCap tracks the growth of the nameToModule map in order to
// track when to shrink it.
nameToModuleCap int // guarded by mux
// EnabledFeatures are read-only to allow optimizations.
EnabledFeatures api.CoreFeatures
// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
Engine Engine
// typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
// do type-checks on indirect function calls.
typeIDs map[string]FunctionTypeID
// functionMaxTypes represents the limit on the number of function types in a store.
// Note: this is fixed to 2^27 but have this a field for testability.
functionMaxTypes uint32
// mux is used to guard the fields from concurrent access.
mux sync.RWMutex
}
// ModuleInstance represents instantiated wasm module.
// The difference from the spec is that in wazero, a ModuleInstance holds pointers
// to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst
//
// This implements api.Module.
ModuleInstance struct {
internalapi.WazeroOnlyType
ModuleName string
Exports map[string]*Export
Globals []*GlobalInstance
MemoryInstance *MemoryInstance
Tables []*TableInstance
// Engine implements function calls for this module.
Engine ModuleEngine
// TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store.
// This is necessary to achieve fast runtime type checking for indirect function calls at runtime.
TypeIDs []FunctionTypeID
// DataInstances holds data segments bytes of the module.
// This is only used by bulk memory operations.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
DataInstances []DataInstance
// ElementInstances holds the element instance, and each holds the references to either functions
// or external objects (unimplemented).
ElementInstances []ElementInstance
// Sys is exposed for use in special imports such as WASI, assemblyscript.
//
// # Notes
//
// - This is a part of ModuleInstance so that scope and Close is coherent.
// - This is not exposed outside this repository (as a host function
// parameter) because we haven't thought through capabilities based
// security implications.
Sys *internalsys.Context
// Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
//
// The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
//
// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
// See /RATIONALE.md
Closed atomic.Uint64
// CodeCloser is non-nil when the code should be closed after this module.
CodeCloser api.Closer
// s is the Store on which this module is instantiated.
s *Store
// prev and next hold the nodes in the linked list of ModuleInstance held by Store.
prev, next *ModuleInstance
// Source is a pointer to the Module from which this ModuleInstance derives.
Source *Module
// CloseNotifier is an experimental hook called once on close.
CloseNotifier experimental.CloseNotifier
}
// DataInstance holds bytes corresponding to the data segment in a module.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
DataInstance = []byte
// GlobalInstance represents a global instance in a store.
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0
GlobalInstance struct {
Type GlobalType
// Val holds a 64-bit representation of the actual value.
// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
Val uint64
// ValHi is only used for vector type globals, and holds the higher bits of the vector.
// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
ValHi uint64
// Me is the module engine that owns this global instance.
// The .Val and .ValHi fields are only valid when me is nil.
// If me is non-nil, the value is stored in the module engine.
Me ModuleEngine
Index Index
}
// FunctionTypeID is a uniquely assigned integer for a function type.
// This is wazero specific runtime object and specific to a store,
// and used at runtime to do type-checks on indirect function calls.
FunctionTypeID uint32
)
// The wazero specific limitations described at RATIONALE.md.
const maximumFunctionTypes = 1 << 27
// GetFunctionTypeID is used by emscripten.
func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID {
id, err := m.s.GetFunctionTypeID(t)
if err != nil {
// This is not recoverable in practice since the only error GetFunctionTypeID returns is
// when there's too many function types in the store.
panic(err)
}
return id
}
func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) {
m.ElementInstances = make([][]Reference, len(elements))
for i, elm := range elements {
if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive {
// Only passive elements can be access as element instances.
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
inits := elm.Init
inst := make([]Reference, len(inits))
m.ElementInstances[i] = inst
for j, idx := range inits {
if index, ok := unwrapElementInitGlobalReference(idx); ok {
global := m.Globals[index]
inst[j] = Reference(global.Val)
} else {
if idx != ElementInitNullReference {
inst[j] = m.Engine.FunctionInstanceReference(idx)
}
}
}
}
}
}
func (m *ModuleInstance) applyElements(elems []ElementSegment) {
for elemI := range elems {
elem := &elems[elemI]
if !elem.IsActive() ||
// Per https://github.com/WebAssembly/spec/issues/1427 init can be no-op.
len(elem.Init) == 0 {
continue
}
var offset uint32
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
// Ignore error as it's already validated.
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
global := m.Globals[globalIdx]
offset = uint32(global.Val)
} else {
// Ignore error as it's already validated.
o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
offset = uint32(o)
}
table := m.Tables[elem.TableIndex]
references := table.References
if int(offset)+len(elem.Init) > len(references) {
// ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length.
// Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal,
// this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error.
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
//
// In wazero, we ignore it since in any way, the instantiated module and engines are fine and can be used
// for function invocations.
return
}
if table.Type == RefTypeExternref {
for i := 0; i < len(elem.Init); i++ {
references[offset+uint32(i)] = Reference(0)
}
} else {
for i, init := range elem.Init {
if init == ElementInitNullReference {
continue
}
var ref Reference
if index, ok := unwrapElementInitGlobalReference(init); ok {
global := m.Globals[index]
ref = Reference(global.Val)
} else {
ref = m.Engine.FunctionInstanceReference(index)
}
references[offset+uint32(i)] = ref
}
}
}
}
// validateData ensures that data segments are valid in terms of memory boundary.
// Note: this is used only when bulk-memory/reference type feature is disabled.
func (m *ModuleInstance) validateData(data []DataSegment) (err error) {
for i := range data {
d := &data[i]
if !d.IsPassive() {
offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression))
ceil := offset + len(d.Init)
if offset < 0 || ceil > len(m.MemoryInstance.Buffer) {
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
}
}
}
return
}
// applyData uses the given data segments and mutate the memory according to the initial contents on it
// and populate the `DataInstances`. This is called after all the validation phase passes and out of
// bounds memory access error here is not a validation error, but rather a runtime error.
func (m *ModuleInstance) applyData(data []DataSegment) error {
m.DataInstances = make([][]byte, len(data))
for i := range data {
d := &data[i]
m.DataInstances[i] = d.Init
if !d.IsPassive() {
offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression)
if offset < 0 || int(offset)+len(d.Init) > len(m.MemoryInstance.Buffer) {
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
}
copy(m.MemoryInstance.Buffer[offset:], d.Init)
}
}
return nil
}
// GetExport returns an export of the given name and type or errs if not exported or the wrong type.
func (m *ModuleInstance) getExport(name string, et ExternType) (*Export, error) {
exp, ok := m.Exports[name]
if !ok {
return nil, fmt.Errorf("%q is not exported in module %q", name, m.ModuleName)
}
if exp.Type != et {
return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.ModuleName, ExternTypeName(exp.Type), ExternTypeName(et))
}
return exp, nil
}
func NewStore(enabledFeatures api.CoreFeatures, engine Engine) *Store {
return &Store{
nameToModule: map[string]*ModuleInstance{},
nameToModuleCap: nameToModuleShrinkThreshold,
EnabledFeatures: enabledFeatures,
Engine: engine,
typeIDs: map[string]FunctionTypeID{},
functionMaxTypes: maximumFunctionTypes,
}
}
// Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under
// different names safely and concurrently.
//
// * ctx: the default context used for function calls.
// * name: the name of the module.
// * sys: the system context, which will be closed (SysContext.Close) on ModuleInstance.Close.
//
// Note: Module.Validate must be called prior to instantiation.
func (s *Store) Instantiate(
ctx context.Context,
module *Module,
name string,
sys *internalsys.Context,
typeIDs []FunctionTypeID,
) (*ModuleInstance, error) {
// Instantiate the module and add it to the store so that other modules can import it.
m, err := s.instantiate(ctx, module, name, sys, typeIDs)
if err != nil {
return nil, err
}
// Now that the instantiation is complete without error, add it.
if err = s.registerModule(m); err != nil {
_ = m.Close(ctx)
return nil, err
}
return m, nil
}
func (s *Store) instantiate(
ctx context.Context,
module *Module,
name string,
sysCtx *internalsys.Context,
typeIDs []FunctionTypeID,
) (m *ModuleInstance, err error) {
m = &ModuleInstance{ModuleName: name, TypeIDs: typeIDs, Sys: sysCtx, s: s, Source: module}
m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection))
m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection))
m.Engine, err = s.Engine.NewModuleEngine(module, m)
if err != nil {
return nil, err
}
if err = m.resolveImports(module); err != nil {
return nil, err
}
err = m.buildTables(module,
// As of reference-types proposal, boundary check must be done after instantiation.
s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes))
if err != nil {
return nil, err
}
allocator, _ := ctx.Value(expctxkeys.MemoryAllocatorKey{}).(experimental.MemoryAllocator)
m.buildGlobals(module, m.Engine.FunctionInstanceReference)
m.buildMemory(module, allocator)
m.Exports = module.Exports
for _, exp := range m.Exports {
if exp.Type == ExternTypeTable {
t := m.Tables[exp.Index]
t.involvingModuleInstances = append(t.involvingModuleInstances, m)
}
}
// As of reference types proposal, data segment validation must happen after instantiation,
// and the side effect must persist even if there's out of bounds error after instantiation.
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405
if !s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) {
if err = m.validateData(module.DataSection); err != nil {
return nil, err
}
}
// After engine creation, we can create the funcref element instances and initialize funcref type globals.
m.buildElementInstances(module.ElementSection)
// Now all the validation passes, we are safe to mutate memory instances (possibly imported ones).
if err = m.applyData(module.DataSection); err != nil {
return nil, err
}
m.applyElements(module.ElementSection)
m.Engine.DoneInstantiation()
// Execute the start function.
if module.StartSection != nil {
funcIdx := *module.StartSection
ce := m.Engine.NewFunction(funcIdx)
_, err = ce.Call(ctx)
if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error!
return nil, exitErr
} else if err != nil {
return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err)
}
}
return
}
func (m *ModuleInstance) resolveImports(module *Module) (err error) {
for moduleName, imports := range module.ImportPerModule {
var importedModule *ModuleInstance
importedModule, err = m.s.module(moduleName)
if err != nil {
return err
}
for _, i := range imports {
var imported *Export
imported, err = importedModule.getExport(i.Name, i.Type)
if err != nil {
return
}
switch i.Type {
case ExternTypeFunc:
expectedType := &module.TypeSection[i.DescFunc]
src := importedModule.Source
actual := src.typeOfFunction(imported.Index)
if !actual.EqualsSignature(expectedType.Params, expectedType.Results) {
err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual))
return
}
m.Engine.ResolveImportedFunction(i.IndexPerType, imported.Index, importedModule.Engine)
case ExternTypeTable:
expected := i.DescTable
importedTable := importedModule.Tables[imported.Index]
if expected.Type != importedTable.Type {
err = errorInvalidImport(i, fmt.Errorf("table type mismatch: %s != %s",
RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
return
}
if expected.Min > importedTable.Min {
err = errorMinSizeMismatch(i, expected.Min, importedTable.Min)
return
}
if expected.Max != nil {
expectedMax := *expected.Max
if importedTable.Max == nil {
err = errorNoMax(i, expectedMax)
return
} else if expectedMax < *importedTable.Max {
err = errorMaxSizeMismatch(i, expectedMax, *importedTable.Max)
return
}
}
m.Tables[i.IndexPerType] = importedTable
importedTable.involvingModuleInstancesMutex.Lock()
if len(importedTable.involvingModuleInstances) == 0 {
panic("BUG: involvingModuleInstances must not be nil when it's imported")
}
importedTable.involvingModuleInstances = append(importedTable.involvingModuleInstances, m)
importedTable.involvingModuleInstancesMutex.Unlock()
case ExternTypeMemory:
expected := i.DescMem
importedMemory := importedModule.MemoryInstance
if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min)
return
}
if expected.Max < importedMemory.Max {
err = errorMaxSizeMismatch(i, expected.Max, importedMemory.Max)
return
}
m.MemoryInstance = importedMemory
m.Engine.ResolveImportedMemory(importedModule.Engine)
case ExternTypeGlobal:
expected := i.DescGlobal
importedGlobal := importedModule.Globals[imported.Index]
if expected.Mutable != importedGlobal.Type.Mutable {
err = errorInvalidImport(i, fmt.Errorf("mutability mismatch: %t != %t",
expected.Mutable, importedGlobal.Type.Mutable))
return
}
if expected.ValType != importedGlobal.Type.ValType {
err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s",
ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
return
}
m.Globals[i.IndexPerType] = importedGlobal
}
}
}
return
}
func errorMinSizeMismatch(i *Import, expected, actual uint32) error {
return errorInvalidImport(i, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
}
func errorNoMax(i *Import, expected uint32) error {
return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
}
func errorMaxSizeMismatch(i *Import, expected, actual uint32) error {
return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
}
func errorInvalidImport(i *Import, err error) error {
return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err)
}
// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
// The validity of the expression is ensured when calling this function as this is only called
// during instantiation phrase, and the validation happens in compilation (validateConstExpression).
func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) {
switch expr.Opcode {
case OpcodeI32Const:
ret, _, _ = leb128.LoadInt32(expr.Data)
case OpcodeGlobalGet:
id, _, _ := leb128.LoadUint32(expr.Data)
g := importedGlobals[id]
ret = int32(g.Val)
}
return
}
// initialize initializes the value of this global instance given the const expr and imported globals.
// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr.
//
// 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
func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) {
switch expr.Opcode {
case OpcodeI32Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
v, _, _ := leb128.LoadInt32(expr.Data)
g.Val = uint64(uint32(v))
case OpcodeI64Const:
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
v, _, _ := leb128.LoadInt64(expr.Data)
g.Val = uint64(v)
case OpcodeF32Const:
g.Val = uint64(binary.LittleEndian.Uint32(expr.Data))
case OpcodeF64Const:
g.Val = binary.LittleEndian.Uint64(expr.Data)
case OpcodeGlobalGet:
id, _, _ := leb128.LoadUint32(expr.Data)
importedG := importedGlobals[id]
switch importedG.Type.ValType {
case ValueTypeI32:
g.Val = uint64(uint32(importedG.Val))
case ValueTypeI64:
g.Val = importedG.Val
case ValueTypeF32:
g.Val = importedG.Val
case ValueTypeF64:
g.Val = importedG.Val
case ValueTypeV128:
g.Val, g.ValHi = importedG.Val, importedG.ValHi
case ValueTypeFuncref, ValueTypeExternref:
g.Val = importedG.Val
}
case OpcodeRefNull:
switch expr.Data[0] {
case ValueTypeExternref, ValueTypeFuncref:
g.Val = 0 // Reference types are opaque 64bit pointer at runtime.
}
case OpcodeRefFunc:
v, _, _ := leb128.LoadUint32(expr.Data)
g.Val = uint64(funcRefResolver(v))
case OpcodeVecV128Const:
g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])
}
}
// String implements api.Global.
func (g *GlobalInstance) String() string {
switch g.Type.ValType {
case ValueTypeI32, ValueTypeI64:
return fmt.Sprintf("global(%d)", g.Val)
case ValueTypeF32:
return fmt.Sprintf("global(%f)", api.DecodeF32(g.Val))
case ValueTypeF64:
return fmt.Sprintf("global(%f)", api.DecodeF64(g.Val))
default:
panic(fmt.Errorf("BUG: unknown value type %X", g.Type.ValType))
}
}
func (g *GlobalInstance) Value() (uint64, uint64) {
if g.Me != nil {
return g.Me.GetGlobalValue(g.Index)
}
return g.Val, g.ValHi
}
func (g *GlobalInstance) SetValue(lo, hi uint64) {
if g.Me != nil {
g.Me.SetGlobalValue(g.Index, lo, hi)
} else {
g.Val, g.ValHi = lo, hi
}
}
func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) {
ret := make([]FunctionTypeID, len(ts))
for i := range ts {
t := &ts[i]
inst, err := s.GetFunctionTypeID(t)
if err != nil {
return nil, err
}
ret[i] = inst
}
return ret, nil
}
func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
s.mux.RLock()
key := t.key()
id, ok := s.typeIDs[key]
s.mux.RUnlock()
if !ok {
s.mux.Lock()
defer s.mux.Unlock()
// Check again in case another goroutine has already added the type.
if id, ok = s.typeIDs[key]; ok {
return id, nil
}
l := len(s.typeIDs)
if uint32(l) >= s.functionMaxTypes {
return 0, fmt.Errorf("too many function types in a store")
}
id = FunctionTypeID(l)
s.typeIDs[key] = id
}
return id, nil
}
// CloseWithExitCode implements the same method as documented on wazero.Runtime.
func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
s.mux.Lock()
defer s.mux.Unlock()
// Close modules in reverse initialization order.
for m := s.moduleList; m != nil; m = m.next {
// If closing this module errs, proceed anyway to close the others.
if e := m.closeWithExitCode(ctx, exitCode); e != nil && err == nil {
// TODO: use multiple errors handling in Go 1.20.
err = e // first error
}
}
s.moduleList = nil
s.nameToModule = nil
s.nameToModuleCap = 0
s.typeIDs = nil
return
}

View File

@ -0,0 +1,97 @@
package wasm
import (
"errors"
"fmt"
"github.com/tetratelabs/wazero/api"
)
// deleteModule makes the moduleName available for instantiation again.
func (s *Store) deleteModule(m *ModuleInstance) error {
s.mux.Lock()
defer s.mux.Unlock()
// Remove this module name.
if m.prev != nil {
m.prev.next = m.next
}
if m.next != nil {
m.next.prev = m.prev
}
if s.moduleList == m {
s.moduleList = m.next
}
// Clear the m state so it does not enter any other branch
// on subsequent calls to deleteModule.
m.prev = nil
m.next = nil
if m.ModuleName != "" {
delete(s.nameToModule, m.ModuleName)
// Shrink the map if it's allocated more than twice the size of the list
newCap := len(s.nameToModule)
if newCap < nameToModuleShrinkThreshold {
newCap = nameToModuleShrinkThreshold
}
if newCap*2 <= s.nameToModuleCap {
nameToModule := make(map[string]*ModuleInstance, newCap)
for k, v := range s.nameToModule {
nameToModule[k] = v
}
s.nameToModule = nameToModule
s.nameToModuleCap = newCap
}
}
return nil
}
// module returns the module of the given name or error if not in this store
func (s *Store) module(moduleName string) (*ModuleInstance, error) {
s.mux.RLock()
defer s.mux.RUnlock()
m, ok := s.nameToModule[moduleName]
if !ok {
return nil, fmt.Errorf("module[%s] not instantiated", moduleName)
}
return m, nil
}
// registerModule registers a ModuleInstance into the store.
// This makes the ModuleInstance visible for import if it's not anonymous, and ensures it is closed when the store is.
func (s *Store) registerModule(m *ModuleInstance) error {
s.mux.Lock()
defer s.mux.Unlock()
if s.nameToModule == nil {
return errors.New("already closed")
}
if m.ModuleName != "" {
if _, ok := s.nameToModule[m.ModuleName]; ok {
return fmt.Errorf("module[%s] has already been instantiated", m.ModuleName)
}
s.nameToModule[m.ModuleName] = m
if len(s.nameToModule) > s.nameToModuleCap {
s.nameToModuleCap = len(s.nameToModule)
}
}
// Add the newest node to the moduleNamesList as the head.
m.next = s.moduleList
if m.next != nil {
m.next.prev = m
}
s.moduleList = m
return nil
}
// Module implements wazero.Runtime Module
func (s *Store) Module(moduleName string) api.Module {
m, err := s.module(moduleName)
if err != nil {
return nil
}
return m
}

View File

@ -0,0 +1,339 @@
package wasm
import (
"fmt"
"math"
"sync"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/leb128"
)
// Table describes the limits of elements and its type in a table.
type Table struct {
Min uint32
Max *uint32
Type RefType
}
// RefType is either RefTypeFuncref or RefTypeExternref as of WebAssembly core 2.0.
type RefType = byte
const (
// RefTypeFuncref represents a reference to a function.
RefTypeFuncref = ValueTypeFuncref
// RefTypeExternref represents a reference to a host object, which is not currently supported in wazero.
RefTypeExternref = ValueTypeExternref
)
func RefTypeName(t RefType) (ret string) {
switch t {
case RefTypeFuncref:
ret = "funcref"
case RefTypeExternref:
ret = "externref"
default:
ret = fmt.Sprintf("unknown(0x%x)", t)
}
return
}
// ElementMode represents a mode of element segment which is either active, passive or declarative.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
type ElementMode = byte
const (
// ElementModeActive is the mode which requires the runtime to initialize table with the contents in .Init field combined with OffsetExpr.
ElementModeActive ElementMode = iota
// ElementModePassive is the mode which doesn't require the runtime to initialize table, and only used with OpcodeTableInitName.
ElementModePassive
// ElementModeDeclarative is introduced in reference-types proposal which can be used to declare function indexes used by OpcodeRefFunc.
ElementModeDeclarative
)
// ElementSegment are initialization instructions for a TableInstance
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-elem
type ElementSegment struct {
// OffsetExpr returns the table element offset to apply to Init indices.
// Note: This can be validated prior to instantiation unless it includes OpcodeGlobalGet (an imported global).
OffsetExpr ConstantExpression
// TableIndex is the table's index to which this element segment is applied.
// Note: This is used if and only if the Mode is active.
TableIndex Index
// Followings are set/used regardless of the Mode.
// Init indices are (nullable) table elements where each index is the function index by which the module initialize the table.
Init []Index
// Type holds the type of this element segment, which is the RefType in WebAssembly 2.0.
Type RefType
// Mode is the mode of this element segment.
Mode ElementMode
}
const (
// ElementInitNullReference represents the null reference in ElementSegment's Init.
// In Wasm spec, an init item represents either Function's Index or null reference,
// and in wazero, we limit the maximum number of functions available in a module to
// MaximumFunctionIndex. Therefore, it is safe to use 1 << 31 to represent the null
// reference in Element segments.
ElementInitNullReference Index = 1 << 31
// elementInitImportedGlobalReferenceType represents an init item which is resolved via an imported global constexpr.
// The actual function reference stored at Global is only known at instantiation-time, so we set this flag
// to items of ElementSegment.Init at binary decoding, and unwrap this flag at instantiation to resolve the value.
//
// This might collide the init element resolved via ref.func instruction which is resolved with the func index at decoding,
// but in practice, that is not allowed in wazero thanks to our limit MaximumFunctionIndex. Thus, it is safe to set this flag
// in init element to indicate as such.
elementInitImportedGlobalReferenceType Index = 1 << 30
)
// unwrapElementInitGlobalReference takes an item of the init vector of an ElementSegment,
// and returns the Global index if it is supposed to get generated from a global.
// ok is true if the given init item is as such.
func unwrapElementInitGlobalReference(init Index) (_ Index, ok bool) {
if init&elementInitImportedGlobalReferenceType == elementInitImportedGlobalReferenceType {
return init &^ elementInitImportedGlobalReferenceType, true
}
return init, false
}
// WrapGlobalIndexAsElementInit wraps the given index as an init item which is resolved via an imported global value.
// See the comments on elementInitImportedGlobalReferenceType for more details.
func WrapGlobalIndexAsElementInit(init Index) Index {
return init | elementInitImportedGlobalReferenceType
}
// IsActive returns true if the element segment is "active" mode which requires the runtime to initialize table
// with the contents in .Init field.
func (e *ElementSegment) IsActive() bool {
return e.Mode == ElementModeActive
}
// TableInstance represents a table of (RefTypeFuncref) elements in a module.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0
type TableInstance struct {
// References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported).
//
// Currently, only function references are supported.
References []Reference
// Min is the minimum (function) elements in this table and cannot grow to accommodate ElementSegment.
Min uint32
// Max if present is the maximum (function) elements in this table, or nil if unbounded.
Max *uint32
// Type is either RefTypeFuncref or RefTypeExternRef.
Type RefType
// The following is only used when the table is exported.
// involvingModuleInstances is a set of module instances which are involved in the table instance.
// This is critical for safety purpose because once a table is imported, it can hold any reference to
// any function in the owner and importing module instances. Therefore, these module instance,
// transitively the compiled modules, must be alive as long as the table instance is alive.
involvingModuleInstances []*ModuleInstance
// involvingModuleInstancesMutex is a mutex to protect involvingModuleInstances.
involvingModuleInstancesMutex sync.RWMutex
}
// ElementInstance represents an element instance in a module.
//
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#element-instances
type ElementInstance = []Reference
// Reference is the runtime representation of RefType which is either RefTypeFuncref or RefTypeExternref.
type Reference = uintptr
// validateTable ensures any ElementSegment is valid. This caches results via Module.validatedActiveElementSegments.
// Note: limitsType are validated by decoders, so not re-validated here.
func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table, maximumTableIndex uint32) error {
if len(tables) > int(maximumTableIndex) {
return fmt.Errorf("too many tables in a module: %d given with limit %d", len(tables), maximumTableIndex)
}
importedTableCount := m.ImportTableCount
// Create bounds checks as these can err prior to instantiation
funcCount := m.ImportFunctionCount + m.SectionElementCount(SectionIDFunction)
globalsCount := m.ImportGlobalCount + m.SectionElementCount(SectionIDGlobal)
// Now, we have to figure out which table elements can be resolved before instantiation and also fail early if there
// are any imported globals that are known to be invalid by their declarations.
for i := range m.ElementSection {
elem := &m.ElementSection[i]
idx := Index(i)
initCount := uint32(len(elem.Init))
// Any offset applied is to the element, not the function index: validate here if the funcidx is sound.
for ei, init := range elem.Init {
if init == ElementInitNullReference {
continue
}
index, ok := unwrapElementInitGlobalReference(init)
if ok {
if index >= globalsCount {
return fmt.Errorf("%s[%d].init[%d] global index %d out of range", SectionIDName(SectionIDElement), idx, ei, index)
}
} else {
if elem.Type == RefTypeExternref {
return fmt.Errorf("%s[%d].init[%d] must be ref.null but was %d", SectionIDName(SectionIDElement), idx, ei, init)
}
if index >= funcCount {
return fmt.Errorf("%s[%d].init[%d] func index %d out of range", SectionIDName(SectionIDElement), idx, ei, index)
}
}
}
if elem.IsActive() {
if len(tables) <= int(elem.TableIndex) {
return fmt.Errorf("unknown table %d as active element target", elem.TableIndex)
}
t := tables[elem.TableIndex]
if t.Type != elem.Type {
return fmt.Errorf("element type mismatch: table has %s but element has %s",
RefTypeName(t.Type), RefTypeName(elem.Type),
)
}
// global.get needs to be discovered during initialization
oc := elem.OffsetExpr.Opcode
if oc == OpcodeGlobalGet {
globalIdx, _, err := leb128.LoadUint32(elem.OffsetExpr.Data)
if err != nil {
return fmt.Errorf("%s[%d] couldn't read global.get parameter: %w", SectionIDName(SectionIDElement), idx, err)
} else if err = m.verifyImportGlobalI32(SectionIDElement, idx, globalIdx); err != nil {
return err
}
} else if oc == OpcodeI32Const {
// Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 we must pass if imported
// table has set its min=0. Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142, we
// have to do fail if module-defined min=0.
if !enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) && elem.TableIndex >= importedTableCount {
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
o, _, err := leb128.LoadInt32(elem.OffsetExpr.Data)
if err != nil {
return fmt.Errorf("%s[%d] couldn't read i32.const parameter: %w", SectionIDName(SectionIDElement), idx, err)
}
offset := Index(o)
if err = checkSegmentBounds(t.Min, uint64(initCount)+uint64(offset), idx); err != nil {
return err
}
}
} else {
return fmt.Errorf("%s[%d] has an invalid const expression: %s", SectionIDName(SectionIDElement), idx, InstructionName(oc))
}
}
}
return nil
}
// buildTable returns TableInstances if the module defines or imports a table.
// - importedTables: returned as `tables` unmodified.
// - importedGlobals: include all instantiated, imported globals.
//
// If the result `init` is non-nil, it is the `tableInit` parameter of Engine.NewModuleEngine.
//
// Note: An error is only possible when an ElementSegment.OffsetExpr is out of range of the TableInstance.Min.
func (m *ModuleInstance) buildTables(module *Module, skipBoundCheck bool) (err error) {
idx := module.ImportTableCount
for i := range module.TableSection {
tsec := &module.TableSection[i]
// The module defining the table is the one that sets its Min/Max etc.
m.Tables[idx] = &TableInstance{
References: make([]Reference, tsec.Min), Min: tsec.Min, Max: tsec.Max,
Type: tsec.Type,
}
idx++
}
if !skipBoundCheck {
for elemI := range module.ElementSection { // Do not loop over the value since elementSegments is a slice of value.
elem := &module.ElementSection[elemI]
table := m.Tables[elem.TableIndex]
var offset uint32
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
// Ignore error as it's already validated.
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
global := m.Globals[globalIdx]
offset = uint32(global.Val)
} else { // i32.const
// Ignore error as it's already validated.
o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
offset = uint32(o)
}
// Check to see if we are out-of-bounds
initCount := uint64(len(elem.Init))
if err = checkSegmentBounds(table.Min, uint64(offset)+initCount, Index(elemI)); err != nil {
return
}
}
}
return
}
// checkSegmentBounds fails if the capacity needed for an ElementSegment.Init is larger than limitsType.Min
//
// WebAssembly 1.0 (20191205) doesn't forbid growing to accommodate element segments, and spectests are inconsistent.
// For example, the spectests enforce elements within Table limitsType.Min, but ignore Import.DescTable min. What this
// means is we have to delay offset checks on imported tables until we link to them.
// e.g. https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 wants pass on min=0 for import
// e.g. https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142 wants fail on min=0 module-defined
func checkSegmentBounds(min uint32, requireMin uint64, idx Index) error { // uint64 in case offset was set to -1
if requireMin > uint64(min) {
return fmt.Errorf("%s[%d].init exceeds min table size", SectionIDName(SectionIDElement), idx)
}
return nil
}
func (m *Module) verifyImportGlobalI32(sectionID SectionID, sectionIdx Index, idx uint32) error {
ig := uint32(math.MaxUint32) // +1 == 0
for i := range m.ImportSection {
imp := &m.ImportSection[i]
if imp.Type == ExternTypeGlobal {
ig++
if ig == idx {
if imp.DescGlobal.ValType != ValueTypeI32 {
return fmt.Errorf("%s[%d] (global.get %d): import[%d].global.ValType != i32", SectionIDName(sectionID), sectionIdx, idx, i)
}
return nil
}
}
}
return fmt.Errorf("%s[%d] (global.get %d): out of range of imported globals", SectionIDName(sectionID), sectionIdx, idx)
}
// Grow appends the `initialRef` by `delta` times into the References slice.
// Returns -1 if the operation is not valid, otherwise the old length of the table.
//
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/instructions.html#xref-syntax-instructions-syntax-instr-table-mathsf-table-grow-x
func (t *TableInstance) Grow(delta uint32, initialRef Reference) (currentLen uint32) {
currentLen = uint32(len(t.References))
if delta == 0 {
return
}
if newLen := int64(currentLen) + int64(delta); // adding as 64bit ints to avoid overflow.
newLen >= math.MaxUint32 || (t.Max != nil && newLen > int64(*t.Max)) {
return 0xffffffff // = -1 in signed 32-bit integer.
}
t.References = append(t.References, make([]uintptr, delta)...)
// Uses the copy trick for faster filling the new region with the initial value.
// https://gist.github.com/taylorza/df2f89d5f9ab3ffd06865062a4cf015d
newRegion := t.References[currentLen:]
newRegion[0] = initialRef
for i := 1; i < len(newRegion); i *= 2 {
copy(newRegion[i:], newRegion[:i])
}
return
}