refactor binary encoding to its own package (#1187)

move binary encoder to its own package

Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
This commit is contained in:
Edoardo Vacchi
2023-03-03 00:21:22 +01:00
committed by GitHub
parent e2660b3f17
commit 117474c477
56 changed files with 1042 additions and 608 deletions

View File

@@ -0,0 +1,48 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
// encodeCode returns the wasm.Code encoded in WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
func encodeCode(c *wasm.Code) []byte {
if c.GoFunc != nil {
panic("BUG: GoFunction is not encodable")
}
// 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...)
}

View File

@@ -0,0 +1,96 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
var addLocalZeroLocalTwo = []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 2, wasm.OpcodeI32Add, wasm.OpcodeEnd}
func TestEncodeCode(t *testing.T) {
addLocalZeroLocalOne := []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}
tests := []struct {
name string
input *wasm.Code
expected []byte
}{
{
name: "smallest function body",
input: &wasm.Code{ // e.g. (func)
Body: []byte{wasm.OpcodeEnd},
},
expected: []byte{
0x02, // 2 bytes to encode locals and the body
0x00, // no local blocks
wasm.OpcodeEnd, // Body
},
},
{
name: "params and instructions", // local.get index is params, then locals
input: &wasm.Code{ // e.g. (func (type 3) local.get 0 local.get 1 i32.add)
Body: addLocalZeroLocalOne,
},
expected: append([]byte{
0x07, // 7 bytes to encode locals and the body
0x00, // no local blocks
},
addLocalZeroLocalOne..., // Body
),
},
{
name: "locals and instructions",
input: &wasm.Code{ // e.g. (func (result i32) (local i32, i32) local.get 0 local.get 1 i32.add)
LocalTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32},
Body: addLocalZeroLocalOne,
},
expected: append([]byte{
0x09, // 9 bytes to encode locals and the body
0x01, // 1 local block
0x02, wasm.ValueTypeI32, // local block 1
},
addLocalZeroLocalOne..., // Body
),
},
{
name: "mixed locals and instructions",
input: &wasm.Code{ // e.g. (func (result i32) (local i32) (local i64) (local i32) local.get 0 local.get 2 i32.add)
LocalTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeI32},
Body: addLocalZeroLocalTwo,
},
expected: append([]byte{
0x0d, // 13 bytes to encode locals and the body
0x03, // 3 local blocks
0x01, wasm.ValueTypeI32, // local block 1
0x01, wasm.ValueTypeI64, // local block 2
0x01, wasm.ValueTypeI32, // local block 3
},
addLocalZeroLocalTwo..., // Body
),
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := encodeCode(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}
func BenchmarkEncodeCode(b *testing.B) {
input := &wasm.Code{ // e.g. (func (result i32) (local i32) (local i64) (local i32) local.get 0 local.get 2 i32.add)
LocalTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeI32},
Body: addLocalZeroLocalTwo,
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if bytes := encodeCode(input); len(bytes) == 0 {
b.Fatal("didn't encode anything")
}
}
}

View File

@@ -0,0 +1,12 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/wasm"
)
func encodeConstantExpression(expr *wasm.ConstantExpression) (ret []byte) {
ret = append(ret, expr.Opcode)
ret = append(ret, expr.Data...)
ret = append(ret, wasm.OpcodeEnd)
return
}

View File

@@ -0,0 +1,15 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func encodeDataSegment(d *wasm.DataSegment) (ret []byte) {
// Currently multiple memories are not supported.
ret = append(ret, leb128.EncodeInt32(0)...)
ret = append(ret, encodeConstantExpression(d.OffsetExpression)...)
ret = append(ret, leb128.EncodeUint32(uint32(len(d.Init)))...)
ret = append(ret, d.Init...)
return
}

View File

@@ -0,0 +1,37 @@
package binaryencoding
import (
"bytes"
"fmt"
"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
}
// encodeCode returns the wasm.ElementSegment encoded in WebAssembly 1.0 (20191205) 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
}

View File

@@ -0,0 +1,13 @@
package binaryencoding
import (
"bytes"
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func Test_ensureElementKindFuncRef(t *testing.T) {
require.NoError(t, ensureElementKindFuncRef(bytes.NewReader([]byte{0x0})))
require.Error(t, ensureElementKindFuncRef(bytes.NewReader([]byte{0x1})))
}

View File

@@ -0,0 +1,56 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/wasm"
)
var sizePrefixedName = []byte{4, 'n', 'a', 'm', 'e'}
// EncodeModule implements wasm.EncodeModule for the WebAssembly 1.0 (20191205) 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)...)
}
}
return
}

View File

@@ -0,0 +1,222 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestModule_Encode(t *testing.T) {
i32, f32 := wasm.ValueTypeI32, wasm.ValueTypeF32
zero := uint32(0)
tests := []struct {
name string
input *wasm.Module
expected []byte
}{
{
name: "empty",
input: &wasm.Module{},
expected: append(Magic, version...),
},
{
name: "only name section",
input: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "simple"}},
expected: append(append(Magic, version...),
wasm.SectionIDCustom, 0x0e, // 14 bytes in this section
0x04, 'n', 'a', 'm', 'e',
subsectionIDModuleName, 0x07, // 7 bytes in this subsection
0x06, // the Module name simple is 6 bytes long
's', 'i', 'm', 'p', 'l', 'e'),
},
{
name: "type section",
input: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{},
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
},
},
expected: append(append(Magic, version...),
wasm.SectionIDType, 0x12, // 18 bytes in this section
0x03, // 3 types
0x60, 0x00, 0x00, // func=0x60 no param no result
0x60, 0x02, i32, i32, 0x01, i32, // func=0x60 2 params and 1 result
0x60, 0x04, i32, i32, i32, i32, 0x01, i32, // func=0x60 4 params and 1 result
),
},
{
name: "type and import section",
input: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
{Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}},
},
ImportSection: []*wasm.Import{
{
Module: "Math", Name: "Mul",
Type: wasm.ExternTypeFunc,
DescFunc: 1,
}, {
Module: "Math", Name: "Add",
Type: wasm.ExternTypeFunc,
DescFunc: 0,
},
},
},
expected: append(append(Magic, version...),
wasm.SectionIDType, 0x0d, // 13 bytes in this section
0x02, // 2 types
0x60, 0x02, i32, i32, 0x01, i32, // func=0x60 2 params and 1 result
0x60, 0x02, f32, f32, 0x01, f32, // func=0x60 2 params and 1 result
wasm.SectionIDImport, 0x17, // 23 bytes in this section
0x02, // 2 imports
0x04, 'M', 'a', 't', 'h', 0x03, 'M', 'u', 'l', wasm.ExternTypeFunc,
0x01, // type index
0x04, 'M', 'a', 't', 'h', 0x03, 'A', 'd', 'd', wasm.ExternTypeFunc,
0x00, // type index
),
},
{
name: "type function and start section",
input: &wasm.Module{
TypeSection: []*wasm.FunctionType{{}},
ImportSection: []*wasm.Import{{
Module: "", Name: "hello",
Type: wasm.ExternTypeFunc,
DescFunc: 0,
}},
StartSection: &zero,
},
expected: append(append(Magic, version...),
wasm.SectionIDType, 0x04, // 4 bytes in this section
0x01, // 1 type
0x60, 0x0, 0x0, // func=0x60 0 params and 0 result
wasm.SectionIDImport, 0x0a, // 10 bytes in this section
0x01, // 1 import
0x00, 0x05, 'h', 'e', 'l', 'l', 'o', wasm.ExternTypeFunc,
0x00, // type index
wasm.SectionIDStart, 0x01,
0x00, // start function index
),
},
{
name: "table and memory section",
input: &wasm.Module{
TableSection: []*wasm.Table{{Min: 3, Type: wasm.RefTypeFuncref}},
MemorySection: &wasm.Memory{Min: 1, Max: 1, IsMaxEncoded: true},
},
expected: append(append(Magic, version...),
wasm.SectionIDTable, 0x04, // 4 bytes in this section
0x01, // 1 table
wasm.RefTypeFuncref, 0x0, 0x03, // func, only min: 3
wasm.SectionIDMemory, 0x04, // 4 bytes in this section
0x01, // 1 memory
0x01, 0x01, 0x01, // min and max = 1
),
},
{
name: "exported func with instructions",
input: &wasm.Module{
TypeSection: []*wasm.FunctionType{
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}},
},
ExportSection: []*wasm.Export{
{Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(0)},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "addInt"}},
LocalNames: wasm.IndirectNameMap{
{Index: wasm.Index(0), NameMap: wasm.NameMap{
{Index: wasm.Index(0), Name: "value_1"},
{Index: wasm.Index(1), Name: "value_2"},
}},
},
},
},
expected: append(append(Magic, version...),
wasm.SectionIDType, 0x07, // 7 bytes in this section
0x01, // 1 type
0x60, 0x02, i32, i32, 0x01, i32, // func=0x60 2 params and 1 result
wasm.SectionIDFunction, 0x02, // 2 bytes in this section
0x01, // 1 function
0x00, // func[0] type index 0
wasm.SectionIDExport, 0x0a, // 10 bytes in this section
0x01, // 1 export
0x06, 'A', 'd', 'd', 'I', 'n', 't', // size of "AddInt", "AddInt"
wasm.ExternTypeFunc, 0x00, // func[0]
wasm.SectionIDCode, 0x09, // 9 bytes in this section
0o1, // one code section
0o7, // length of the body + locals
0o0, // count of local blocks
wasm.OpcodeLocalGet, 0, // local.get 0
wasm.OpcodeLocalGet, 1, // local.get 1
wasm.OpcodeI32Add, // i32.add
wasm.OpcodeEnd, // end of instructions/code
wasm.SectionIDCustom, 0x27, // 39 bytes in this section
0x04, 'n', 'a', 'm', 'e',
subsectionIDFunctionNames, 0x09, // 9 bytes
0x01, // two function names
0x00, 0x06, 'a', 'd', 'd', 'I', 'n', 't', // index 0, size of "addInt", "addInt"
subsectionIDLocalNames, 0x15, // 21 bytes
0x01, // one function
0x00, 0x02, // index 0 has 2 locals
0x00, 0x07, 'v', 'a', 'l', 'u', 'e', '_', '1', // index 0, size of "value_1", "value_1"
0x01, 0x07, 'v', 'a', 'l', 'u', 'e', '_', '2', // index 1, size of "value_2", "value_2"
),
},
{
name: "exported global var",
input: &wasm.Module{
GlobalSection: []*wasm.Global{
{
Type: &wasm.GlobalType{ValType: i32, Mutable: true},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(0)},
},
},
ExportSection: []*wasm.Export{
{Name: "sp", Type: wasm.ExternTypeGlobal, Index: wasm.Index(0)},
},
},
expected: append(append(Magic, version...),
wasm.SectionIDGlobal, 0x06, // 6 bytes in this section
0x01, wasm.ValueTypeI32, 0x01, // 1 global i32 mutable
wasm.OpcodeI32Const, 0x00, wasm.OpcodeEnd, // arbitrary init to zero
wasm.SectionIDExport, 0x06, // 6 bytes in this section
0x01, // 1 export
0x02, 's', 'p', // size of "sp", "sp"
wasm.ExternTypeGlobal, 0x00, // global[0]
),
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := EncodeModule(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}
func TestModule_Encode_HostFunctionSection_Unsupported(t *testing.T) {
// We don't currently have an approach to serialize reflect.Value pointers
fn := func() {}
captured := require.CapturePanic(func() {
EncodeModule(&wasm.Module{
TypeSection: []*wasm.FunctionType{{}},
CodeSection: []*wasm.Code{wasm.MustParseGoReflectFuncCode(fn)},
})
})
require.EqualError(t, captured, "BUG: GoFunction is not encodable")
}

View File

@@ -0,0 +1,16 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
// encodeExport returns the wasm.Export encoded in WebAssembly 1.0 (20191205) 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
}

View File

@@ -0,0 +1,131 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeExport(t *testing.T) {
tests := []struct {
name string
input *wasm.Export
expected []byte
}{
{
name: "func no name, index 0",
input: &wasm.Export{ // e.g. (export "" (func 0)))
Type: wasm.ExternTypeFunc,
Name: "",
Index: 0,
},
expected: []byte{wasm.ExternTypeFunc, 0x00, 0x00},
},
{
name: "func name, func index 0",
input: &wasm.Export{ // e.g. (export "pi" (func 0))
Type: wasm.ExternTypeFunc,
Name: "pi",
Index: 0,
},
expected: []byte{
0x02, 'p', 'i',
wasm.ExternTypeFunc,
0x00,
},
},
{
name: "func name, index 10",
input: &wasm.Export{ // e.g. (export "pi" (func 10))
Type: wasm.ExternTypeFunc,
Name: "pi",
Index: 10,
},
expected: []byte{
0x02, 'p', 'i',
wasm.ExternTypeFunc,
0x0a,
},
},
{
name: "global no name, index 0",
input: &wasm.Export{ // e.g. (export "" (global 0)))
Type: wasm.ExternTypeGlobal,
Name: "",
Index: 0,
},
expected: []byte{0x00, wasm.ExternTypeGlobal, 0x00},
},
{
name: "global name, global index 0",
input: &wasm.Export{ // e.g. (export "pi" (global 0))
Type: wasm.ExternTypeGlobal,
Name: "pi",
Index: 0,
},
expected: []byte{
0x02, 'p', 'i',
wasm.ExternTypeGlobal,
0x00,
},
},
{
name: "global name, index 10",
input: &wasm.Export{ // e.g. (export "pi" (global 10))
Type: wasm.ExternTypeGlobal,
Name: "pi",
Index: 10,
},
expected: []byte{
0x02, 'p', 'i',
wasm.ExternTypeGlobal,
0x0a,
},
},
{
name: "memory no name, index 0",
input: &wasm.Export{ // e.g. (export "" (memory 0)))
Type: wasm.ExternTypeMemory,
Name: "",
Index: 0,
},
expected: []byte{0x00, wasm.ExternTypeMemory, 0x00},
},
{
name: "memory name, memory index 0",
input: &wasm.Export{ // e.g. (export "mem" (memory 0))
Type: wasm.ExternTypeMemory,
Name: "mem",
Index: 0,
},
expected: []byte{
0x03, 'm', 'e', 'm',
wasm.ExternTypeMemory,
0x00,
},
},
{
name: "memory name, index 10",
input: &wasm.Export{ // e.g. (export "mem" (memory 10))
Type: wasm.ExternTypeMemory,
Name: "mem",
Index: 10,
},
expected: []byte{
0x03, 'm', 'e', 'm',
wasm.ExternTypeMemory,
0x0a,
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := encodeExport(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}

View File

@@ -0,0 +1,15 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/wasm"
)
// EncodeFunctionType returns the wasm.FunctionType encoded in WebAssembly 1.0 (20191205) 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 {
// Only reached when "multi-value" is enabled because WebAssembly 1.0 (20191205) supports at most 1 result.
data := append([]byte{0x60}, EncodeValTypes(t.Params)...)
return append(data, EncodeValTypes(t.Results)...)
}

View File

@@ -0,0 +1,18 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/wasm"
)
// encodeGlobal returns the wasm.Global encoded in WebAssembly 1.0 (20191205) 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
}

View File

@@ -0,0 +1,49 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeGlobal(t *testing.T) {
tests := []struct {
name string
input *wasm.Global
expected []byte
}{
{
name: "const",
input: &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1)},
},
expected: []byte{
wasm.ValueTypeI32, 0x00, // 0 == const
wasm.OpcodeI32Const, 0x01, wasm.OpcodeEnd,
},
},
{
name: "var",
input: &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32, Mutable: true},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1)},
},
expected: []byte{
wasm.ValueTypeI32, 0x01, // 1 == var
wasm.OpcodeI32Const, 0x01, wasm.OpcodeEnd,
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := encodeGlobal(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}

View File

@@ -0,0 +1,9 @@
package binaryencoding
// 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,40 @@
package binaryencoding
import (
"fmt"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
// EncodeImport returns the wasm.Import encoded in WebAssembly 1.0 (20191205) 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
}

View File

@@ -0,0 +1,161 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeImport(t *testing.T) {
ptrOfUint32 := func(v uint32) *uint32 {
return &v
}
tests := []struct {
name string
input *wasm.Import
expected []byte
}{
{
name: "func no module, no name, type index 0",
input: &wasm.Import{ // e.g. (import "" "" (func (type 0)))
Type: wasm.ExternTypeFunc,
Module: "",
Name: "",
DescFunc: 0,
},
expected: []byte{wasm.ExternTypeFunc, 0x00, 0x00, 0x00},
},
{
name: "func module, no name, type index 0",
input: &wasm.Import{ // e.g. (import "$test" "" (func (type 0)))
Type: wasm.ExternTypeFunc,
Module: "test",
Name: "",
DescFunc: 0,
},
expected: []byte{
0x04, 't', 'e', 's', 't',
0x00,
wasm.ExternTypeFunc,
0x00,
},
},
{
name: "func module, name, type index 0",
input: &wasm.Import{ // e.g. (import "$math" "$pi" (func (type 0)))
Type: wasm.ExternTypeFunc,
Module: "math",
Name: "pi",
DescFunc: 0,
},
expected: []byte{
0x04, 'm', 'a', 't', 'h',
0x02, 'p', 'i',
wasm.ExternTypeFunc,
0x00,
},
},
{
name: "func module, name, type index 10",
input: &wasm.Import{ // e.g. (import "$math" "$pi" (func (type 10)))
Type: wasm.ExternTypeFunc,
Module: "math",
Name: "pi",
DescFunc: 10,
},
expected: []byte{
0x04, 'm', 'a', 't', 'h',
0x02, 'p', 'i',
wasm.ExternTypeFunc,
0x0a,
},
},
{
name: "global const",
input: &wasm.Import{
Type: wasm.ExternTypeGlobal,
Module: "math",
Name: "pi",
DescGlobal: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
},
expected: []byte{
0x04, 'm', 'a', 't', 'h',
0x02, 'p', 'i',
wasm.ExternTypeGlobal,
wasm.ValueTypeF64, 0x00, // 0 == const
},
},
{
name: "global var",
input: &wasm.Import{
Type: wasm.ExternTypeGlobal,
Module: "math",
Name: "pi",
DescGlobal: &wasm.GlobalType{ValType: wasm.ValueTypeF64, Mutable: true},
},
expected: []byte{
0x04, 'm', 'a', 't', 'h',
0x02, 'p', 'i',
wasm.ExternTypeGlobal,
wasm.ValueTypeF64, 0x01, // 1 == var
},
},
{
name: "table",
input: &wasm.Import{
Type: wasm.ExternTypeTable,
Module: "my",
Name: "table",
DescTable: &wasm.Table{Min: 1, Max: ptrOfUint32(2)},
},
expected: []byte{
0x02, 'm', 'y',
0x05, 't', 'a', 'b', 'l', 'e',
wasm.ExternTypeTable,
wasm.RefTypeFuncref,
0x1, 0x1, 0x2, // Limit with max.
},
},
{
name: "memory",
input: &wasm.Import{
Type: wasm.ExternTypeMemory,
Module: "my",
Name: "memory",
DescMem: &wasm.Memory{Min: 1, Max: 2, IsMaxEncoded: true},
},
expected: []byte{
0x02, 'm', 'y',
0x06, 'm', 'e', 'm', 'o', 'r', 'y',
wasm.ExternTypeMemory,
0x1, 0x1, 0x2, // Limit with max.
},
},
{
name: "memory - defaultt max",
input: &wasm.Import{
Type: wasm.ExternTypeMemory,
Module: "my",
Name: "memory",
DescMem: &wasm.Memory{Min: 1, Max: wasm.MemoryLimitPages, IsMaxEncoded: false},
},
expected: []byte{
0x02, 'm', 'y',
0x06, 'm', 'e', 'm', 'o', 'r', 'y',
wasm.ExternTypeMemory,
0x0, 0x1, // Limit without max.
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := EncodeImport(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}

View File

@@ -0,0 +1,15 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/leb128"
)
// EncodeLimitsType returns the `limitsType` (min, max) encoded in WebAssembly 1.0 (20191205) 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)...)...)
}

View File

@@ -0,0 +1,16 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/wasm"
)
// EncodeMemory returns the wasm.Memory encoded in WebAssembly 1.0 (20191205) 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)
}

View File

@@ -0,0 +1,93 @@
package binaryencoding
import (
"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)
)
// 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...)
}

View File

@@ -0,0 +1,147 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeNameSectionData(t *testing.T) {
tests := []struct {
name string
input *wasm.NameSection
expected []byte
}{
{
name: "empty",
input: &wasm.NameSection{},
},
{
name: "only module",
// e.g. (module $simple )
input: &wasm.NameSection{ModuleName: "simple"},
expected: []byte{
subsectionIDModuleName, 0x07, // 7 bytes
0x06, // the Module name simple is 6 bytes long
's', 'i', 'm', 'p', 'l', 'e',
},
},
{
name: "module and function name",
// (module $simple
// (import "" "Hello" (func $hello))
// (start $hello)
// )
input: &wasm.NameSection{
ModuleName: "simple",
FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "hello"}},
},
expected: []byte{
subsectionIDModuleName, 0x07, // 7 bytes
0x06, // the Module name simple is 6 bytes long
's', 'i', 'm', 'p', 'l', 'e',
subsectionIDFunctionNames, 0x08, // 8 bytes
0x01, // one function name
0x00, // the function index is zero
0x05, // the function name hello is 5 bytes long
'h', 'e', 'l', 'l', 'o',
},
},
{
name: "two function names", // e.g. TinyGo which at one point didn't set a module name
// (module
// (import "wasi_snapshot_preview1" "args_sizes_get" (func $wasi.args_sizes_get (param i32, i32) (result i32)))
// (import "wasi_snapshot_preview1" "fd_write" (func $wasi.fd_write (param i32, i32, i32, i32) (result i32)))
// )
input: &wasm.NameSection{
FunctionNames: wasm.NameMap{
{Index: wasm.Index(0), Name: "wasi.args_sizes_get"},
{Index: wasm.Index(1), Name: "wasi.fd_write"},
},
},
expected: []byte{
subsectionIDFunctionNames, 0x25, // 37 bytes
0x02, // two function names
0x00, // the function index is zero
0x13, // the function name wasi.args_sizes_get is 19 bytes long
'w', 'a', 's', 'i', '.', 'a', 'r', 'g', 's', '_', 's', 'i', 'z', 'e', 's', '_', 'g', 'e', 't',
0x01, // the function index is one
0x0d, // the function name wasi.fd_write is 13 bytes long
'w', 'a', 's', 'i', '.', 'f', 'd', '_', 'w', 'r', 'i', 't', 'e',
},
},
{
name: "function with local names",
// (module
// (import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32)))
// (import "Math" "Add" (func $add (param $l f32) (param $r f32) (result f32)))
// )
input: &wasm.NameSection{
FunctionNames: wasm.NameMap{
{Index: wasm.Index(0), Name: "mul"},
{Index: wasm.Index(1), Name: "add"},
},
LocalNames: wasm.IndirectNameMap{
{Index: wasm.Index(0), NameMap: wasm.NameMap{
{Index: wasm.Index(0), Name: "x"},
{Index: wasm.Index(1), Name: "y"},
}},
{Index: wasm.Index(1), NameMap: wasm.NameMap{
{Index: wasm.Index(0), Name: "l"},
{Index: wasm.Index(1), Name: "r"},
}},
},
},
expected: []byte{
subsectionIDFunctionNames, 0x0b, // 7 bytes
0x02, // two function names
0x00, 0x03, 'm', 'u', 'l', // index 0, size of "mul", "mul"
0x01, 0x03, 'a', 'd', 'd', // index 1, size of "add", "add"
subsectionIDLocalNames, 0x11, // 17 bytes
0x02, // two functions
0x00, 0x02, // index 0 has 2 locals
0x00, 0x01, 'x', // index 0, size of "x", "x"
0x01, 0x01, 'y', // index 1, size of "y", "y"
0x01, 0x02, // index 1 has 2 locals
0x00, 0x01, 'l', // index 0, size of "l", "l"
0x01, 0x01, 'r', // index 1, size of "r", "r"
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := EncodeNameSectionData(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}
func TestEncodeNameSubsection(t *testing.T) {
subsectionID := uint8(1)
name := []byte("simple")
require.Equal(t, []byte{
subsectionID,
byte(1 + 6), // 1 is the size of 6 in LEB128 encoding
6, 's', 'i', 'm', 'p', 'l', 'e',
}, encodeNameSubsection(subsectionID, encodeSizePrefixed(name)))
}
func TestEncodeNameAssoc(t *testing.T) {
na := &wasm.NameAssoc{Index: 1, Name: "hello"}
require.Equal(t, []byte{byte(na.Index), 5, 'h', 'e', 'l', 'l', 'o'}, encodeNameAssoc(na))
}
func TestEncodeNameMap(t *testing.T) {
na := &wasm.NameAssoc{Index: 1, Name: "hello"}
m := wasm.NameMap{na}
require.Equal(t, []byte{byte(1), byte(na.Index), 5, 'h', 'e', 'l', 'l', 'o'}, encodeNameMap(m))
}
func TestEncodeSizePrefixed(t *testing.T) {
// We expect size in bytes (LEB128 encoded) then the bytes
require.Equal(t, []byte{5, 'h', 'e', 'l', 'l', 'o'}, encodeSizePrefixed([]byte("hello")))
}

View File

@@ -0,0 +1,144 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
// 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 1.0 (20191205) 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 1.0 (20191205) 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 1.0 (20191205) 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 1.0 (20191205)
// 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 1.0
// (20191205) 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 []byte = 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 1.0
// (20191205) 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 1.0 (20191205) 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 1.0 (20191205) 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 1.0 (20191205)
// 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))
}
// encodeEelementSection encodes a wasm.SectionIDElement for the elements in WebAssembly 1.0 (20191205)
// 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)
}

View File

@@ -0,0 +1,17 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeFunctionSection(t *testing.T) {
require.Equal(t, []byte{wasm.SectionIDFunction, 0x2, 0x01, 0x05}, EncodeFunctionSection([]wasm.Index{5}))
}
// TestEncodeStartSection uses the same index as TestEncodeFunctionSection to highlight the encoding is different.
func TestEncodeStartSection(t *testing.T) {
require.Equal(t, []byte{wasm.SectionIDStart, 0x01, 0x05}, EncodeStartSection(5))
}

View File

@@ -0,0 +1,12 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/wasm"
)
// EncodeTable returns the wasm.Table encoded in WebAssembly 1.0 (20191205) 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)...)
}

View File

@@ -0,0 +1,41 @@
package binaryencoding
import (
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/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...)
}

View File

@@ -0,0 +1,112 @@
package binaryencoding
import (
"testing"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
)
func TestEncodeValTypes(t *testing.T) {
i32, i64, f32, f64, ext, fref := wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref
tests := []struct {
name string
input []wasm.ValueType
expected []byte
}{
{
name: "empty",
input: []wasm.ValueType{},
expected: []byte{0},
},
{
name: "undefined", // ensure future spec changes don't panic
input: []wasm.ValueType{0x6f},
expected: []byte{1, 0x6f},
},
{
name: "funcref",
input: []wasm.ValueType{fref},
expected: []byte{1, fref},
},
{
name: "externref",
input: []wasm.ValueType{ext},
expected: []byte{1, ext},
},
{
name: "i32",
input: []wasm.ValueType{i32},
expected: []byte{1, i32},
},
{
name: "i64",
input: []wasm.ValueType{i64},
expected: []byte{1, i64},
},
{
name: "f32",
input: []wasm.ValueType{f32},
expected: []byte{1, f32},
},
{
name: "f64",
input: []wasm.ValueType{f64},
expected: []byte{1, f64},
},
{
name: "i32i64",
input: []wasm.ValueType{i32, i64},
expected: []byte{2, i32, i64},
},
{
name: "i32i64f32",
input: []wasm.ValueType{i32, i64, f32},
expected: []byte{3, i32, i64, f32},
},
{
name: "i32i64f32f64",
input: []wasm.ValueType{i32, i64, f32, f64},
expected: []byte{4, i32, i64, f32, f64},
},
{
name: "i32i64f32f64i32",
input: []wasm.ValueType{i32, i64, f32, f64, i32},
expected: []byte{5, i32, i64, f32, f64, i32},
},
{
name: "i32i64f32f64i32i64",
input: []wasm.ValueType{i32, i64, f32, f64, i32, i64},
expected: []byte{6, i32, i64, f32, f64, i32, i64},
},
{
name: "i32i64f32f64i32i64f32",
input: []wasm.ValueType{i32, i64, f32, f64, i32, i64, f32},
expected: []byte{7, i32, i64, f32, f64, i32, i64, f32},
},
{
name: "i32i64f32f64i32i64f32f64",
input: []wasm.ValueType{i32, i64, f32, f64, i32, i64, f32, f64},
expected: []byte{8, i32, i64, f32, f64, i32, i64, f32, f64},
},
{
name: "i32i64f32f64i32i64f32f64i32",
input: []wasm.ValueType{i32, i64, f32, f64, i32, i64, f32, f64, i32},
expected: []byte{9, i32, i64, f32, f64, i32, i64, f32, f64, i32},
},
{
name: "i32i64f32f64i32i64f32f64i32i64",
input: []wasm.ValueType{i32, i64, f32, f64, i32, i64, f32, f64, i32, i64},
expected: []byte{10, i32, i64, f32, f64, i32, i64, f32, f64, i32, i64},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
bytes := EncodeValTypes(tc.input)
require.Equal(t, tc.expected, bytes)
})
}
}

View File

@@ -6,8 +6,8 @@ import (
"github.com/tetratelabs/wazero/experimental"
"github.com/tetratelabs/wazero/experimental/logging"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
"github.com/tetratelabs/wazero/internal/wasm"
binaryformat "github.com/tetratelabs/wazero/internal/wasm/binary"
)
const proxyModuleName = "internal/testing/proxy/proxy.go"
@@ -95,5 +95,5 @@ func NewModuleBinary(moduleName string, proxyTarget wazero.CompiledModule) []byt
})
cnt++
}
return binaryformat.EncodeModule(proxyModule)
return binaryencoding.EncodeModule(proxyModule)
}