[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
}