working commit
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
func decodeCode(r *bytes.Reader) (*wasm.Code, error) {
|
||||
ss, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, 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 nil, fmt.Errorf("get the size locals: %v", err)
|
||||
} else if remaining < 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
var nums []uint64
|
||||
var types []wasm.ValueType
|
||||
var sum uint64
|
||||
var n uint32
|
||||
for i := uint32(0); i < ls; i++ {
|
||||
n, bytesRead, err = leb128.DecodeUint32(r)
|
||||
remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read n of locals: %v", err)
|
||||
} else if remaining < 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
sum += uint64(n)
|
||||
nums = append(nums, uint64(n))
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read type of local: %v", err)
|
||||
}
|
||||
switch vt := b; vt {
|
||||
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
|
||||
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128:
|
||||
types = append(types, vt)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid local type: 0x%x", vt)
|
||||
}
|
||||
}
|
||||
|
||||
if sum > math.MaxUint32 {
|
||||
return nil, fmt.Errorf("too many locals: %d", sum)
|
||||
}
|
||||
|
||||
var localTypes []wasm.ValueType
|
||||
for i, num := range nums {
|
||||
t := types[i]
|
||||
for j := uint64(0); j < num; j++ {
|
||||
localTypes = append(localTypes, t)
|
||||
}
|
||||
}
|
||||
|
||||
body := make([]byte, remaining)
|
||||
if _, err = io.ReadFull(r, body); err != nil {
|
||||
return nil, fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
|
||||
if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd {
|
||||
return nil, fmt.Errorf("expr not end with OpcodeEnd")
|
||||
}
|
||||
|
||||
return &wasm.Code{Body: body, LocalTypes: localTypes}, nil
|
||||
}
|
||||
|
||||
// encodeCode returns the wasm.Code encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
|
||||
func encodeCode(c *wasm.Code) []byte {
|
||||
// local blocks compress locals while preserving index order by grouping locals of the same type.
|
||||
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
|
||||
localBlockCount := uint32(0) // how many blocks of locals with the same type (types can repeat!)
|
||||
var localBlocks []byte
|
||||
localTypeLen := len(c.LocalTypes)
|
||||
if localTypeLen > 0 {
|
||||
i := localTypeLen - 1
|
||||
var runCount uint32 // count of the same type
|
||||
var lastValueType wasm.ValueType // initialize to an invalid type 0
|
||||
|
||||
// iterate backwards so it is easier to size prefix
|
||||
for ; i >= 0; i-- {
|
||||
vt := c.LocalTypes[i]
|
||||
if lastValueType != vt {
|
||||
if runCount != 0 { // Only on the first iteration, this is zero when vt is compared against invalid
|
||||
localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...)
|
||||
}
|
||||
lastValueType = vt
|
||||
localBlocks = append(leb128.EncodeUint32(uint32(vt)), localBlocks...) // reuse the EncodeUint32 cache
|
||||
localBlockCount++
|
||||
runCount = 1
|
||||
} else {
|
||||
runCount++
|
||||
}
|
||||
}
|
||||
localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...)
|
||||
localBlocks = append(leb128.EncodeUint32(localBlockCount), localBlocks...)
|
||||
} else {
|
||||
localBlocks = leb128.EncodeUint32(0)
|
||||
}
|
||||
code := append(localBlocks, c.Body...)
|
||||
return append(leb128.EncodeUint32(uint32(len(code))), code...)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/ieee754"
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
func decodeConstantExpression(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.ConstantExpression, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, 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:
|
||||
_, err = ieee754.DecodeFloat32(r)
|
||||
case wasm.OpcodeF64Const:
|
||||
_, err = ieee754.DecodeFloat64(r)
|
||||
case wasm.OpcodeGlobalGet:
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeRefNull:
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return nil, fmt.Errorf("ref.null is not supported as %w", err)
|
||||
}
|
||||
reftype, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read reference type for ref.null: %w", err)
|
||||
} else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref {
|
||||
return nil, fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
|
||||
}
|
||||
case wasm.OpcodeRefFunc:
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return nil, fmt.Errorf("ref.func is not supported as %w", err)
|
||||
}
|
||||
// Parsing index.
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeVecPrefix:
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureSIMD); err != nil {
|
||||
return nil, fmt.Errorf("vector instructions are not supported as %w", err)
|
||||
}
|
||||
opcode, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read vector instruction opcode suffix: %w", err)
|
||||
}
|
||||
|
||||
if opcode != wasm.OpcodeVecV128Const {
|
||||
return nil, 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 nil, fmt.Errorf("read vector const instruction immediates: %w", err)
|
||||
} else if n != 16 {
|
||||
return nil, fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read value: %v", err)
|
||||
}
|
||||
|
||||
if b, err = r.ReadByte(); err != nil {
|
||||
return nil, fmt.Errorf("look for end opcode: %v", err)
|
||||
}
|
||||
|
||||
if b != wasm.OpcodeEnd {
|
||||
return nil, fmt.Errorf("constant expression has been not terminated")
|
||||
}
|
||||
|
||||
data := make([]byte, remainingBeforeData-int64(r.Len())-1)
|
||||
if _, err := r.ReadAt(data, offsetAtData); err != nil {
|
||||
return nil, fmt.Errorf("error re-buffering ConstantExpression.Data")
|
||||
}
|
||||
|
||||
return &wasm.ConstantExpression{Opcode: opcode, Data: data}, nil
|
||||
}
|
||||
|
||||
func encodeConstantExpression(expr *wasm.ConstantExpression) (ret []byte) {
|
||||
ret = append(ret, expr.Opcode)
|
||||
ret = append(ret, expr.Data...)
|
||||
ret = append(ret, wasm.OpcodeEnd)
|
||||
return
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/tetratelabs/wabin/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
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/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 future use as of 2.0.
|
||||
dataSegmentPrefixActiveWithMemoryIndex dataSegmentPrefix = 0x2
|
||||
)
|
||||
|
||||
func decodeDataSegment(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.DataSegment, error) {
|
||||
dataSegmentPrefix, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read data segment prefix: %w", err)
|
||||
}
|
||||
|
||||
if dataSegmentPrefix != dataSegmentPrefixActive {
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return nil, fmt.Errorf("non-zero prefix for data segment is invalid as %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var expr *wasm.ConstantExpression
|
||||
switch dataSegmentPrefix {
|
||||
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 dataSegmentPrefix == 0x2 {
|
||||
d, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read memory index: %v", err)
|
||||
} else if d != 0 {
|
||||
return nil, fmt.Errorf("memory index must be zero but was %d", d)
|
||||
}
|
||||
}
|
||||
|
||||
expr, err = decodeConstantExpression(r, features)
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid data segment prefix: 0x%x", dataSegmentPrefix)
|
||||
}
|
||||
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get the size of vector: %v", err)
|
||||
}
|
||||
|
||||
b := make([]byte, vs)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return nil, fmt.Errorf("read bytes for init: %v", err)
|
||||
}
|
||||
|
||||
return &wasm.DataSegment{
|
||||
OffsetExpression: expr,
|
||||
Init: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeDataSegment(d *wasm.DataSegment) (ret []byte) {
|
||||
if d.OffsetExpression == nil {
|
||||
ret = append(ret, leb128.EncodeInt32(int32(dataSegmentPrefixPassive))...)
|
||||
} else {
|
||||
// Currently multiple memories are not supported.
|
||||
ret = append(ret, leb128.EncodeInt32(int32(dataSegmentPrefixActive))...)
|
||||
ret = append(ret, encodeConstantExpression(d.OffsetExpression)...)
|
||||
}
|
||||
ret = append(ret, leb128.EncodeUint32(uint32(len(d.Init)))...)
|
||||
ret = append(ret, d.Init...)
|
||||
return
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
// DecodeModule implements wasm.DecodeModule for the WebAssembly Binary Format
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
|
||||
func DecodeModule(binary []byte, features wasm.CoreFeatures) (*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
|
||||
}
|
||||
|
||||
m := &wasm.Module{}
|
||||
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
|
||||
if name == "name" {
|
||||
m.NameSection, err = decodeNameSection(r, uint64(limit))
|
||||
} else {
|
||||
custom, 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, custom)
|
||||
}
|
||||
|
||||
case wasm.SectionIDType:
|
||||
m.TypeSection, err = decodeTypeSection(features, r)
|
||||
case wasm.SectionIDImport:
|
||||
if m.ImportSection, err = decodeImportSection(r, features); 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, features)
|
||||
case wasm.SectionIDMemory:
|
||||
m.MemorySection, err = decodeMemorySection(r)
|
||||
case wasm.SectionIDGlobal:
|
||||
if m.GlobalSection, err = decodeGlobalSection(r, features); err != nil {
|
||||
return nil, err // avoid re-wrapping the error.
|
||||
}
|
||||
case wasm.SectionIDExport:
|
||||
m.ExportSection, 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, features)
|
||||
case wasm.SectionIDCode:
|
||||
m.CodeSection, err = decodeCodeSection(r)
|
||||
case wasm.SectionIDData:
|
||||
m.DataSection, err = decodeDataSection(r, features)
|
||||
case wasm.SectionIDDataCount:
|
||||
if err := features.RequireEnabled(wasm.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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/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)
|
||||
}
|
||||
vec[i] = &u32
|
||||
}
|
||||
return vec, nil
|
||||
}
|
||||
|
||||
func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, features wasm.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 {
|
||||
expr, err := decodeConstantExpression(r, features)
|
||||
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.DecodeUint32(bytes.NewReader(expr.Data))
|
||||
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] is already nil, so nothing to do.
|
||||
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 FeatureBulkMemoryOperations.
|
||||
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 and reftype
|
||||
// is fixed to funcref and indexes are encoded as vec(const_expr).
|
||||
elementSegmentPrefixActiveFuncrefConstExprVector
|
||||
// elementSegmentPrefixPassiveConstExprVector is passive where indexes
|
||||
// are encoded as vec(const_expr), and reftype is encoded.
|
||||
elementSegmentPrefixPassiveConstExprVector
|
||||
// elementSegmentPrefixPassiveConstExprVector is active where indexes are
|
||||
// encoded as vec(const_expr), and reftype and table index are encoded.
|
||||
elementSegmentPrefixActiveConstExprVector
|
||||
// elementSegmentPrefixDeclarativeConstExprVector is declarative where
|
||||
// indexes are encoded as vec(const_expr), and reftype is encoded.
|
||||
elementSegmentPrefixDeclarativeConstExprVector
|
||||
)
|
||||
|
||||
func decodeElementSegment(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.ElementSegment, error) {
|
||||
prefix, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read element prefix: %w", err)
|
||||
}
|
||||
|
||||
if prefix != elementSegmentPrefixLegacy {
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return nil, 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.
|
||||
expr, err := decodeConstantExpression(r, features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
init, err := decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wasm.ElementSegment{
|
||||
OffsetExpr: expr,
|
||||
Init: init,
|
||||
Type: wasm.RefTypeFuncref,
|
||||
Mode: wasm.ElementModeActive,
|
||||
// Legacy prefix has the fixed table index zero.
|
||||
TableIndex: 0,
|
||||
}, nil
|
||||
case elementSegmentPrefixPassiveFuncrefValueVector:
|
||||
// Prefix 1 requires funcref.
|
||||
if err = ensureElementKindFuncRef(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
init, err := decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wasm.ElementSegment{
|
||||
Init: init,
|
||||
Type: wasm.RefTypeFuncref,
|
||||
Mode: wasm.ElementModePassive,
|
||||
}, nil
|
||||
case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex:
|
||||
tableIndex, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
if tableIndex != 0 {
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
|
||||
return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
|
||||
}
|
||||
}
|
||||
|
||||
expr, err := decodeConstantExpression(r, features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
// Prefix 2 requires funcref.
|
||||
if err = ensureElementKindFuncRef(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
init, err := decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wasm.ElementSegment{
|
||||
OffsetExpr: expr,
|
||||
Init: init,
|
||||
Type: wasm.RefTypeFuncref,
|
||||
Mode: wasm.ElementModeActive,
|
||||
TableIndex: tableIndex,
|
||||
}, nil
|
||||
case elementSegmentPrefixDeclarativeFuncrefValueVector:
|
||||
// Prefix 3 requires funcref.
|
||||
if err = ensureElementKindFuncRef(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
init, err := decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wasm.ElementSegment{
|
||||
Init: init,
|
||||
Type: wasm.RefTypeFuncref,
|
||||
Mode: wasm.ElementModeDeclarative,
|
||||
}, nil
|
||||
case elementSegmentPrefixActiveFuncrefConstExprVector:
|
||||
expr, err := decodeConstantExpression(r, features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
init, err := decodeElementConstExprVector(r, wasm.RefTypeFuncref, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wasm.ElementSegment{
|
||||
OffsetExpr: expr,
|
||||
Init: init,
|
||||
Type: wasm.RefTypeFuncref,
|
||||
Mode: wasm.ElementModeActive,
|
||||
TableIndex: 0,
|
||||
}, nil
|
||||
case elementSegmentPrefixPassiveConstExprVector:
|
||||
refType, err := decodeElementRefType(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
init, err := decodeElementConstExprVector(r, refType, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wasm.ElementSegment{
|
||||
Init: init,
|
||||
Type: refType,
|
||||
Mode: wasm.ElementModePassive,
|
||||
}, nil
|
||||
case elementSegmentPrefixActiveConstExprVector:
|
||||
tableIndex, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
if tableIndex != 0 {
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
|
||||
return nil, fmt.Errorf("table index must be zero but was %d: %w", tableIndex, err)
|
||||
}
|
||||
}
|
||||
expr, err := decodeConstantExpression(r, features)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
refType, err := decodeElementRefType(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
init, err := decodeElementConstExprVector(r, refType, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wasm.ElementSegment{
|
||||
OffsetExpr: expr,
|
||||
Init: init,
|
||||
Type: refType,
|
||||
Mode: wasm.ElementModeActive,
|
||||
TableIndex: tableIndex,
|
||||
}, nil
|
||||
case elementSegmentPrefixDeclarativeConstExprVector:
|
||||
refType, err := decodeElementRefType(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
init, err := decodeElementConstExprVector(r, refType, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wasm.ElementSegment{
|
||||
Init: init,
|
||||
Type: refType,
|
||||
Mode: wasm.ElementModeDeclarative,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid element segment prefix: 0x%x", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// encodeCode returns the wasm.ElementSegment encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0
|
||||
func encodeElement(e *wasm.ElementSegment) (ret []byte) {
|
||||
if e.Mode == wasm.ElementModeActive {
|
||||
ret = append(ret, leb128.EncodeInt32(int32(e.TableIndex))...)
|
||||
ret = append(ret, encodeConstantExpression(e.OffsetExpr)...)
|
||||
ret = append(ret, leb128.EncodeUint32(uint32(len(e.Init)))...)
|
||||
for _, idx := range e.Init {
|
||||
ret = append(ret, leb128.EncodeInt32(int32(*idx))...)
|
||||
}
|
||||
} else {
|
||||
panic("TODO: support encoding for non-active elements in bulk-memory-operations proposal")
|
||||
}
|
||||
return
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
var sizePrefixedName = []byte{4, 'n', 'a', 'm', 'e'}
|
||||
|
||||
// EncodeModule implements wasm.EncodeModule for the WebAssembly Binary Format.
|
||||
// Note: If saving to a file, the conventional extension is wasm
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
|
||||
func EncodeModule(m *wasm.Module) (bytes []byte) {
|
||||
bytes = append(Magic, version...)
|
||||
if m.SectionElementCount(wasm.SectionIDType) > 0 {
|
||||
bytes = append(bytes, encodeTypeSection(m.TypeSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDImport) > 0 {
|
||||
bytes = append(bytes, encodeImportSection(m.ImportSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDFunction) > 0 {
|
||||
bytes = append(bytes, encodeFunctionSection(m.FunctionSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDTable) > 0 {
|
||||
bytes = append(bytes, encodeTableSection(m.TableSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDMemory) > 0 {
|
||||
bytes = append(bytes, encodeMemorySection(m.MemorySection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDGlobal) > 0 {
|
||||
bytes = append(bytes, encodeGlobalSection(m.GlobalSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDExport) > 0 {
|
||||
bytes = append(bytes, encodeExportSection(m.ExportSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDStart) > 0 {
|
||||
bytes = append(bytes, encodeStartSection(*m.StartSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDElement) > 0 {
|
||||
bytes = append(bytes, encodeElementSection(m.ElementSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDCode) > 0 {
|
||||
bytes = append(bytes, encodeCodeSection(m.CodeSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDData) > 0 {
|
||||
bytes = append(bytes, encodeDataSection(m.DataSection)...)
|
||||
}
|
||||
if m.SectionElementCount(wasm.SectionIDCustom) > 0 {
|
||||
// >> The name section should appear only once in a module, and only after the data section.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
|
||||
if m.NameSection != nil {
|
||||
nameSection := append(sizePrefixedName, encodeNameSectionData(m.NameSection)...)
|
||||
bytes = append(bytes, encodeSection(wasm.SectionIDCustom, nameSection)...)
|
||||
}
|
||||
for _, custom := range m.CustomSections {
|
||||
bytes = append(bytes, encodeSection(wasm.SectionIDCustom, encodeCustomSection(custom))...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
+11
@@ -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")
|
||||
)
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
func decodeExport(r *bytes.Reader) (i *wasm.Export, err error) {
|
||||
i = &wasm.Export{}
|
||||
|
||||
if i.Name, _, err = decodeUTF8(r, "export name"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding export kind: %w", err)
|
||||
}
|
||||
|
||||
i.Type = b
|
||||
switch i.Type {
|
||||
case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal:
|
||||
if i.Index, _, err = leb128.DecodeUint32(r); err != nil {
|
||||
return nil, fmt.Errorf("error decoding export index: %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: invalid byte for exportdesc: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// encodeExport returns the wasm.Export encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0
|
||||
func encodeExport(i *wasm.Export) []byte {
|
||||
data := encodeSizePrefixed([]byte(i.Name))
|
||||
data = append(data, i.Type)
|
||||
data = append(data, leb128.EncodeUint32(i.Index)...)
|
||||
return data
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
var nullary = []byte{0x60, 0, 0}
|
||||
|
||||
// encodedOneParam is a cache of wasm.FunctionType values for param length 1 and result length 0
|
||||
var encodedOneParam = map[wasm.ValueType][]byte{
|
||||
wasm.ValueTypeI32: {0x60, 1, wasm.ValueTypeI32, 0},
|
||||
wasm.ValueTypeI64: {0x60, 1, wasm.ValueTypeI64, 0},
|
||||
wasm.ValueTypeF32: {0x60, 1, wasm.ValueTypeF32, 0},
|
||||
wasm.ValueTypeF64: {0x60, 1, wasm.ValueTypeF64, 0},
|
||||
}
|
||||
|
||||
// encodedOneResult is a cache of wasm.FunctionType values for param length 0 and result length 1
|
||||
var encodedOneResult = map[wasm.ValueType][]byte{
|
||||
wasm.ValueTypeI32: {0x60, 0, 1, wasm.ValueTypeI32},
|
||||
wasm.ValueTypeI64: {0x60, 0, 1, wasm.ValueTypeI64},
|
||||
wasm.ValueTypeF32: {0x60, 0, 1, wasm.ValueTypeF32},
|
||||
wasm.ValueTypeF64: {0x60, 0, 1, wasm.ValueTypeF64},
|
||||
}
|
||||
|
||||
// encodeFunctionType returns the wasm.FunctionType encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// Note: Function types are encoded by the byte 0x60 followed by the respective vectors of parameter and result types.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A4
|
||||
func encodeFunctionType(t *wasm.FunctionType) []byte {
|
||||
paramCount, resultCount := len(t.Params), len(t.Results)
|
||||
if paramCount == 0 && resultCount == 0 {
|
||||
return nullary
|
||||
}
|
||||
if resultCount == 0 {
|
||||
if paramCount == 1 {
|
||||
if encoded, ok := encodedOneParam[t.Params[0]]; ok {
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 0)
|
||||
} else if resultCount == 1 {
|
||||
if paramCount == 0 {
|
||||
if encoded, ok := encodedOneResult[t.Results[0]]; ok {
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
return append(append([]byte{0x60}, encodeValTypes(t.Params)...), 1, t.Results[0])
|
||||
}
|
||||
// Only reached when "multi-value" is enabled because WebAssembly supports at most 1 result.
|
||||
data := append([]byte{0x60}, encodeValTypes(t.Params)...)
|
||||
return append(data, encodeValTypes(t.Results)...)
|
||||
}
|
||||
|
||||
func decodeFunctionType(features wasm.CoreFeatures, r *bytes.Reader) (*wasm.FunctionType, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read leading byte: %w", err)
|
||||
}
|
||||
|
||||
if b != 0x60 {
|
||||
return nil, fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b)
|
||||
}
|
||||
|
||||
paramCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read parameter count: %w", err)
|
||||
}
|
||||
|
||||
paramTypes, err := decodeValueTypes(r, paramCount)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read parameter types: %w", err)
|
||||
}
|
||||
|
||||
resultCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read result count: %w", err)
|
||||
}
|
||||
|
||||
// Guard >1.0 feature multi-value
|
||||
if resultCount > 1 {
|
||||
if err = features.RequireEnabled(wasm.CoreFeatureMultiValue); err != nil {
|
||||
return nil, fmt.Errorf("multiple result types invalid as %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
resultTypes, err := decodeValueTypes(r, resultCount)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read result types: %w", err)
|
||||
}
|
||||
|
||||
ret := &wasm.FunctionType{
|
||||
Params: paramTypes,
|
||||
Results: resultTypes,
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
// decodeGlobal returns the wasm.Global decoded with the WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global
|
||||
func decodeGlobal(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.Global, error) {
|
||||
gt, err := decodeGlobalType(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
init, err := decodeConstantExpression(r, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wasm.Global{Type: gt, Init: init}, nil
|
||||
}
|
||||
|
||||
// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly 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 nil, fmt.Errorf("read value type: %w", err)
|
||||
}
|
||||
|
||||
ret := &wasm.GlobalType{
|
||||
ValType: vt[0],
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read mutablity: %w", err)
|
||||
}
|
||||
|
||||
switch mut := b; mut {
|
||||
case 0x00: // not mutable
|
||||
case 0x01: // mutable
|
||||
ret.Mutable = true
|
||||
default:
|
||||
return nil, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// encodeGlobal returns the wasm.Global encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0
|
||||
func encodeGlobal(g *wasm.Global) (data []byte) {
|
||||
var mutable byte
|
||||
if g.Type.Mutable {
|
||||
mutable = 1
|
||||
}
|
||||
data = []byte{g.Type.ValType, mutable}
|
||||
data = append(data, encodeConstantExpression(g.Init)...)
|
||||
return
|
||||
}
|
||||
+9
@@ -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}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
func decodeImport(
|
||||
r *bytes.Reader,
|
||||
idx uint32,
|
||||
features wasm.CoreFeatures,
|
||||
) (i *wasm.Import, err error) {
|
||||
i = &wasm.Import{}
|
||||
if i.Module, _, err = decodeUTF8(r, "import module"); err != nil {
|
||||
return nil, fmt.Errorf("import[%d] error decoding module: %w", idx, err)
|
||||
}
|
||||
|
||||
if i.Name, _, err = decodeUTF8(r, "import name"); err != nil {
|
||||
return nil, fmt.Errorf("import[%d] error decoding name: %w", idx, err)
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("import[%d] error decoding type: %w", idx, err)
|
||||
}
|
||||
i.Type = b
|
||||
switch i.Type {
|
||||
case wasm.ExternTypeFunc:
|
||||
i.DescFunc, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.ExternTypeTable:
|
||||
i.DescTable, err = decodeTable(r, features)
|
||||
case wasm.ExternTypeMemory:
|
||||
i.DescMem, err = decodeMemory(r)
|
||||
case wasm.ExternTypeGlobal:
|
||||
i.DescGlobal, err = decodeGlobalType(r)
|
||||
default:
|
||||
err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, wasm.ExternTypeName(i.Type), i.Module, i.Name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// encodeImport returns the wasm.Import encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import
|
||||
func encodeImport(i *wasm.Import) []byte {
|
||||
data := encodeSizePrefixed([]byte(i.Module))
|
||||
data = append(data, encodeSizePrefixed([]byte(i.Name))...)
|
||||
data = append(data, i.Type)
|
||||
switch i.Type {
|
||||
case wasm.ExternTypeFunc:
|
||||
data = append(data, leb128.EncodeUint32(i.DescFunc)...)
|
||||
case wasm.ExternTypeTable:
|
||||
data = append(data, wasm.RefTypeFuncref)
|
||||
data = append(data, encodeLimitsType(i.DescTable.Min, i.DescTable.Max)...)
|
||||
case wasm.ExternTypeMemory:
|
||||
maxPtr := &i.DescMem.Max
|
||||
if !i.DescMem.IsMaxEncoded {
|
||||
maxPtr = nil
|
||||
}
|
||||
data = append(data, encodeLimitsType(i.DescMem.Min, maxPtr)...)
|
||||
case wasm.ExternTypeGlobal:
|
||||
g := i.DescGlobal
|
||||
var mutable byte
|
||||
if g.Mutable {
|
||||
mutable = 1
|
||||
}
|
||||
data = append(data, g.ValType, mutable)
|
||||
default:
|
||||
panic(fmt.Errorf("invalid externtype: %s", wasm.ExternTypeName(i.Type)))
|
||||
}
|
||||
return data
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
)
|
||||
|
||||
// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
|
||||
func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, 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:
|
||||
min, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read min of limit: %v", err)
|
||||
}
|
||||
case 0x01:
|
||||
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 != 0x00 or 0x01", ErrInvalidByte, flag)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// encodeLimitsType returns the `limitsType` (min, max) encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
|
||||
func encodeLimitsType(min uint32, max *uint32) []byte {
|
||||
if max == nil {
|
||||
return append(leb128.EncodeUint32(0x00), leb128.EncodeUint32(min)...)
|
||||
}
|
||||
return append(leb128.EncodeUint32(0x01), append(leb128.EncodeUint32(min), leb128.EncodeUint32(*max)...)...)
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
// decodeMemory returns the wasm.Memory decoded with the WebAssembly
|
||||
// Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory
|
||||
func decodeMemory(r *bytes.Reader) (*wasm.Memory, error) {
|
||||
min, maxP, err := decodeLimitsType(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mem := &wasm.Memory{Min: min}
|
||||
if maxP != nil {
|
||||
mem.Max = *maxP
|
||||
mem.IsMaxEncoded = true
|
||||
|
||||
if min > mem.Max {
|
||||
return nil, fmt.Errorf("min %d pages (%s) > max %d pages (%s)",
|
||||
min, wasm.PagesToUnitOfBytes(min), mem.Max, wasm.PagesToUnitOfBytes(mem.Max))
|
||||
}
|
||||
}
|
||||
|
||||
return mem, nil
|
||||
}
|
||||
|
||||
// encodeMemory returns the wasm.Memory encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory
|
||||
func encodeMemory(i *wasm.Memory) []byte {
|
||||
maxPtr := &i.Max
|
||||
if !i.IsMaxEncoded {
|
||||
maxPtr = nil
|
||||
}
|
||||
return encodeLimitsType(i.Min, maxPtr)
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/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
|
||||
}
|
||||
|
||||
// encodeNameSectionData serializes the data for the "name" key in wasm.SectionIDCustom according to the
|
||||
// standard:
|
||||
//
|
||||
// Note: The result can be nil because this does not encode empty subsections
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
|
||||
func encodeNameSectionData(n *wasm.NameSection) (data []byte) {
|
||||
if n.ModuleName != "" {
|
||||
data = append(data, encodeNameSubsection(subsectionIDModuleName, encodeSizePrefixed([]byte(n.ModuleName)))...)
|
||||
}
|
||||
if fd := encodeFunctionNameData(n); len(fd) > 0 {
|
||||
data = append(data, encodeNameSubsection(subsectionIDFunctionNames, fd)...)
|
||||
}
|
||||
if ld := encodeLocalNameData(n); len(ld) > 0 {
|
||||
data = append(data, encodeNameSubsection(subsectionIDLocalNames, ld)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// encodeFunctionNameData encodes the data for the function name subsection.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-funcnamesec
|
||||
func encodeFunctionNameData(n *wasm.NameSection) []byte {
|
||||
if len(n.FunctionNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return encodeNameMap(n.FunctionNames)
|
||||
}
|
||||
|
||||
func encodeNameMap(m wasm.NameMap) []byte {
|
||||
count := uint32(len(m))
|
||||
data := leb128.EncodeUint32(count)
|
||||
for _, na := range m {
|
||||
data = append(data, encodeNameAssoc(na)...)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// encodeLocalNameData encodes the data for the local name subsection.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-localnamesec
|
||||
func encodeLocalNameData(n *wasm.NameSection) []byte {
|
||||
if len(n.LocalNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
funcNameCount := uint32(len(n.LocalNames))
|
||||
subsection := leb128.EncodeUint32(funcNameCount)
|
||||
|
||||
for _, na := range n.LocalNames {
|
||||
locals := encodeNameMap(na.NameMap)
|
||||
subsection = append(subsection, append(leb128.EncodeUint32(na.Index), locals...)...)
|
||||
}
|
||||
return subsection
|
||||
}
|
||||
|
||||
// encodeNameSubsection returns a buffer encoding the given subsection
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#subsections%E2%91%A0
|
||||
func encodeNameSubsection(subsectionID uint8, content []byte) []byte {
|
||||
contentSizeInBytes := leb128.EncodeUint32(uint32(len(content)))
|
||||
result := []byte{subsectionID}
|
||||
result = append(result, contentSizeInBytes...)
|
||||
result = append(result, content...)
|
||||
return result
|
||||
}
|
||||
|
||||
// encodeNameAssoc encodes the index and data prefixed by their size.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap
|
||||
func encodeNameAssoc(na *wasm.NameAssoc) []byte {
|
||||
return append(leb128.EncodeUint32(na.Index), encodeSizePrefixed([]byte(na.Name))...)
|
||||
}
|
||||
|
||||
// encodeSizePrefixed encodes the data prefixed by their size.
|
||||
func encodeSizePrefixed(data []byte) []byte {
|
||||
size := leb128.EncodeUint32(uint32(len(data)))
|
||||
return append(size, data...)
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
func decodeTypeSection(features wasm.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 result[i], err = decodeFunctionType(features, r); err != nil {
|
||||
return nil, fmt.Errorf("read %d-th type: %v", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeImportSection(r *bytes.Reader, features wasm.CoreFeatures) ([]*wasm.Import, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]*wasm.Import, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if result[i], err = decodeImport(r, i, features); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
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, features wasm.CoreFeatures) ([]*wasm.Table, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading size")
|
||||
}
|
||||
if vs > 1 {
|
||||
if err := features.RequireEnabled(wasm.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 {
|
||||
table, err := decodeTable(r, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = table
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func decodeMemorySection(r *bytes.Reader) (*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)
|
||||
}
|
||||
|
||||
return decodeMemory(r)
|
||||
}
|
||||
|
||||
func decodeGlobalSection(r *bytes.Reader, features wasm.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 result[i], err = decodeGlobal(r, features); err != nil {
|
||||
return nil, fmt.Errorf("global[%d]: %w", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeExportSection(r *bytes.Reader) ([]*wasm.Export, error) {
|
||||
vs, _, sizeErr := leb128.DecodeUint32(r)
|
||||
if sizeErr != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %v", sizeErr)
|
||||
}
|
||||
|
||||
usedName := make(map[string]struct{}, vs)
|
||||
exportSection := make([]*wasm.Export, 0, vs)
|
||||
for i := wasm.Index(0); i < vs; i++ {
|
||||
export, err := decodeExport(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read export: %w", err)
|
||||
}
|
||||
if _, ok := usedName[export.Name]; ok {
|
||||
return nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name)
|
||||
} else {
|
||||
usedName[export.Name] = struct{}{}
|
||||
}
|
||||
exportSection = append(exportSection, export)
|
||||
}
|
||||
return exportSection, 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, features wasm.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 result[i], err = decodeElementSegment(r, features); err != nil {
|
||||
return nil, fmt.Errorf("read element: %w", err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeCodeSection(r *bytes.Reader) ([]*wasm.Code, error) {
|
||||
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++ {
|
||||
if result[i], err = decodeCode(r); err != nil {
|
||||
return nil, fmt.Errorf("read %d-th code segment: %v", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeDataSection(r *bytes.Reader, features wasm.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 result[i], err = decodeDataSegment(r, features); 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
|
||||
}
|
||||
|
||||
// encodeSection encodes the sectionID, the size of its contents in bytes,
|
||||
// followed by the contents.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
|
||||
func encodeSection(sectionID wasm.SectionID, contents []byte) []byte {
|
||||
return append([]byte{sectionID}, encodeSizePrefixed(contents)...)
|
||||
}
|
||||
|
||||
// encodeTypeSection encodes a wasm.SectionIDType for the given imports in
|
||||
// WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeFunctionType
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#type-section%E2%91%A0
|
||||
func encodeTypeSection(types []*wasm.FunctionType) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(types)))
|
||||
for _, t := range types {
|
||||
contents = append(contents, encodeFunctionType(t)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDType, contents)
|
||||
}
|
||||
|
||||
// encodeImportSection encodes a wasm.SectionIDImport for the given imports in
|
||||
// WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeImport
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0
|
||||
func encodeImportSection(imports []*wasm.Import) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(imports)))
|
||||
for _, i := range imports {
|
||||
contents = append(contents, encodeImport(i)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDImport, contents)
|
||||
}
|
||||
|
||||
// encodeFunctionSection encodes a wasm.SectionIDFunction for the type indices
|
||||
// associated with module-defined functions in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0
|
||||
func encodeFunctionSection(typeIndices []wasm.Index) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(typeIndices)))
|
||||
for _, index := range typeIndices {
|
||||
contents = append(contents, leb128.EncodeUint32(index)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDFunction, contents)
|
||||
}
|
||||
|
||||
// encodeCodeSection encodes a wasm.SectionIDCode for the module-defined
|
||||
// function in WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeCode
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
|
||||
func encodeCodeSection(code []*wasm.Code) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(code)))
|
||||
for _, i := range code {
|
||||
contents = append(contents, encodeCode(i)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDCode, contents)
|
||||
}
|
||||
|
||||
// encodeTableSection encodes a wasm.SectionIDTable for the module-defined
|
||||
// function in WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeTable
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0
|
||||
func encodeTableSection(tables []*wasm.Table) []byte {
|
||||
var contents = leb128.EncodeUint32(uint32(len(tables)))
|
||||
for _, table := range tables {
|
||||
contents = append(contents, encodeTable(table)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDTable, contents)
|
||||
}
|
||||
|
||||
// encodeMemorySection encodes a wasm.SectionIDMemory for the module-defined
|
||||
// function in WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeMemory
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0
|
||||
func encodeMemorySection(memory *wasm.Memory) []byte {
|
||||
contents := append([]byte{1}, encodeMemory(memory)...)
|
||||
return encodeSection(wasm.SectionIDMemory, contents)
|
||||
}
|
||||
|
||||
// encodeGlobalSection encodes a wasm.SectionIDGlobal for the given globals in
|
||||
// WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeGlobal
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0
|
||||
func encodeGlobalSection(globals []*wasm.Global) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(globals)))
|
||||
for _, g := range globals {
|
||||
contents = append(contents, encodeGlobal(g)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDGlobal, contents)
|
||||
}
|
||||
|
||||
// encodeExportSection encodes a wasm.SectionIDExport for the given exports in
|
||||
// WebAssembly Binary Format.
|
||||
//
|
||||
// See encodeExport
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0
|
||||
func encodeExportSection(exports []*wasm.Export) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(exports)))
|
||||
for _, e := range exports {
|
||||
contents = append(contents, encodeExport(e)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDExport, contents)
|
||||
}
|
||||
|
||||
// encodeStartSection encodes a wasm.SectionIDStart for the given function
|
||||
// index in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0
|
||||
func encodeStartSection(funcidx wasm.Index) []byte {
|
||||
return encodeSection(wasm.SectionIDStart, leb128.EncodeUint32(funcidx))
|
||||
}
|
||||
|
||||
// encodeElementSection encodes a wasm.SectionIDElement for the elements in
|
||||
// WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#element-section%E2%91%A0
|
||||
func encodeElementSection(elements []*wasm.ElementSegment) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(elements)))
|
||||
for _, e := range elements {
|
||||
contents = append(contents, encodeElement(e)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDElement, contents)
|
||||
}
|
||||
|
||||
// encodeDataSection encodes a wasm.SectionIDData for the data in WebAssembly 1.0 (20191205)
|
||||
// Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#data-section%E2%91%A0
|
||||
func encodeDataSection(datum []*wasm.DataSegment) []byte {
|
||||
contents := leb128.EncodeUint32(uint32(len(datum)))
|
||||
for _, d := range datum {
|
||||
contents = append(contents, encodeDataSegment(d)...)
|
||||
}
|
||||
return encodeSection(wasm.SectionIDData, contents)
|
||||
}
|
||||
|
||||
// encodeCustomSection encodes a wasm.SectionIDCustom for the data in WebAssembly 1.0 (20191205)
|
||||
// Binary Format. This is used for custom sections that are **not** associated with the "name" key.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||
func encodeCustomSection(c *wasm.CustomSection) (data []byte) {
|
||||
data = make([]byte, 0, 1+len(c.Name)+len(c.Data))
|
||||
l := byte(len(c.Name))
|
||||
data = append(data, l)
|
||||
data = append(data, []byte(c.Name)...)
|
||||
data = append(data, c.Data...)
|
||||
return
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
// decodeTable returns the wasm.Table decoded with the WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
|
||||
func decodeTable(r *bytes.Reader, features wasm.CoreFeatures) (*wasm.Table, error) {
|
||||
tableType, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read leading byte: %v", err)
|
||||
}
|
||||
|
||||
if tableType != wasm.RefTypeFuncref {
|
||||
if err := features.RequireEnabled(wasm.CoreFeatureReferenceTypes); err != nil {
|
||||
return nil, fmt.Errorf("table type funcref is invalid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
min, max, err := decodeLimitsType(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read limits: %v", err)
|
||||
}
|
||||
if min > wasm.MaximumFunctionIndex {
|
||||
return nil, fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex)
|
||||
}
|
||||
if max != nil {
|
||||
if *max < min {
|
||||
return nil, fmt.Errorf("table size minimum must not be greater than maximum")
|
||||
}
|
||||
}
|
||||
return &wasm.Table{Min: min, Max: max, Type: tableType}, nil
|
||||
}
|
||||
|
||||
// encodeTable returns the wasm.Table encoded in WebAssembly Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
|
||||
func encodeTable(i *wasm.Table) []byte {
|
||||
return append([]byte{i.Type}, encodeLimitsType(i.Min, i.Max)...)
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tetratelabs/wabin/leb128"
|
||||
"github.com/tetratelabs/wabin/wasm"
|
||||
)
|
||||
|
||||
var noValType = []byte{0}
|
||||
|
||||
// encodedValTypes is a cache of size prefixed binary encoding of known val types.
|
||||
var encodedValTypes = map[wasm.ValueType][]byte{
|
||||
wasm.ValueTypeI32: {1, wasm.ValueTypeI32},
|
||||
wasm.ValueTypeI64: {1, wasm.ValueTypeI64},
|
||||
wasm.ValueTypeF32: {1, wasm.ValueTypeF32},
|
||||
wasm.ValueTypeF64: {1, wasm.ValueTypeF64},
|
||||
wasm.ValueTypeExternref: {1, wasm.ValueTypeExternref},
|
||||
wasm.ValueTypeFuncref: {1, wasm.ValueTypeFuncref},
|
||||
wasm.ValueTypeV128: {1, wasm.ValueTypeV128},
|
||||
}
|
||||
|
||||
// encodeValTypes fast paths binary encoding of common value type lengths
|
||||
func encodeValTypes(vt []wasm.ValueType) []byte {
|
||||
// Special case nullary and parameter lengths of wasi_snapshot_preview1 to avoid excess allocations
|
||||
switch uint32(len(vt)) {
|
||||
case 0: // nullary
|
||||
return noValType
|
||||
case 1: // ex $wasi.fd_close or any result
|
||||
if encoded, ok := encodedValTypes[vt[0]]; ok {
|
||||
return encoded
|
||||
}
|
||||
case 2: // ex $wasi.environ_sizes_get
|
||||
return []byte{2, vt[0], vt[1]}
|
||||
case 4: // ex $wasi.fd_write
|
||||
return []byte{4, vt[0], vt[1], vt[2], vt[3]}
|
||||
case 9: // ex $wasi.fd_write
|
||||
return []byte{9, vt[0], vt[1], vt[2], vt[3], vt[4], vt[5], vt[6], vt[7], vt[8]}
|
||||
}
|
||||
// Slow path others until someone complains with a valid signature
|
||||
count := leb128.EncodeUint32(uint32(len(vt)))
|
||||
return append(count, vt...)
|
||||
}
|
||||
|
||||
func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
|
||||
if num == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ret := make([]wasm.ValueType, num)
|
||||
buf := make([]wasm.ValueType, num)
|
||||
_, err := io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, v := range buf {
|
||||
switch v {
|
||||
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128:
|
||||
ret[i] = v
|
||||
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)
|
||||
}
|
||||
|
||||
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...))
|
||||
}
|
||||
|
||||
return string(buf), size + uint32(sizeOfSize), nil
|
||||
}
|
||||
Reference in New Issue
Block a user