binary: complete encoder (#463)

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2022-04-14 16:55:53 +09:00
committed by GitHub
parent bd328a3355
commit e3035b5a76
31 changed files with 431 additions and 213 deletions

View File

@@ -55,8 +55,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
FunctionSection: []wasm.Index{0},
HostFunctionSection: []*reflect.Value{&fnUint32_uint32},
ExportSection: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
ExportSection: []*wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}},
@@ -74,8 +74,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
FunctionSection: []wasm.Index{0},
HostFunctionSection: []*reflect.Value{&fnUint64_uint32},
ExportSection: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
ExportSection: []*wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}},
@@ -95,9 +95,9 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
FunctionSection: []wasm.Index{0, 1},
HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32},
ExportSection: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
"2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "2", Type: wasm.ExternTypeFunc, Index: 1},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}},
@@ -119,9 +119,9 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
FunctionSection: []wasm.Index{0, 1},
HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32},
ExportSection: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
"2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "2", Type: wasm.ExternTypeFunc, Index: 1},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}},
@@ -144,9 +144,9 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
FunctionSection: []wasm.Index{0, 1},
HostFunctionSection: []*reflect.Value{&fnUint32_uint32, &fnUint64_uint32},
ExportSection: map[string]*wasm.Export{
"1": {Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
"2": {Name: "2", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "1", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "2", Type: wasm.ExternTypeFunc, Index: 1},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}},
@@ -160,8 +160,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Max: wasm.MemoryMaxPages},
ExportSection: map[string]*wasm.Export{
"memory": {Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
@@ -172,8 +172,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 2, Max: wasm.MemoryMaxPages},
ExportSection: map[string]*wasm.Export{
"memory": {Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
@@ -184,8 +184,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Max: 1},
ExportSection: map[string]*wasm.Export{
"memory": {Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
@@ -196,8 +196,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
},
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Max: 2},
ExportSection: map[string]*wasm.Export{
"memory": {Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
@@ -213,8 +213,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(1024)},
},
},
ExportSection: map[string]*wasm.Export{
"canvas_width": {Name: "canvas_width", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "canvas_width", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -230,8 +230,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeUint32(math.MaxInt32)},
},
},
ExportSection: map[string]*wasm.Export{
"canvas_width": {Name: "canvas_width", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "canvas_width", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -247,8 +247,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeUint64(1620216263544)},
},
},
ExportSection: map[string]*wasm.Export{
"start_epoch": {Name: "start_epoch", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "start_epoch", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -264,8 +264,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(math.MaxInt64)},
},
},
ExportSection: map[string]*wasm.Export{
"start_epoch": {Name: "start_epoch", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "start_epoch", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -281,8 +281,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(3.1415926536))},
},
},
ExportSection: map[string]*wasm.Export{
"math/pi": {Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -298,8 +298,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(math.MaxFloat32))},
},
},
ExportSection: map[string]*wasm.Export{
"math/pi": {Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -315,8 +315,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(math.Pi))},
},
},
ExportSection: map[string]*wasm.Export{
"math/pi": {Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},
@@ -332,8 +332,8 @@ func TestNewModuleBuilder_Build(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64))},
},
},
ExportSection: map[string]*wasm.Export{
"math/pi": {Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
ExportSection: []*wasm.Export{
{Name: "math/pi", Type: wasm.ExternTypeGlobal, Index: 0},
},
},
},

View File

@@ -48,10 +48,17 @@ func decodeConstantExpression(r *bytes.Reader) (*wasm.ConstantExpression, error)
return nil, fmt.Errorf("constant expression has been not terminated")
}
data := make([]byte, remainingBeforeData-int64(r.Len()))
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
}

View File

@@ -39,3 +39,12 @@ func decodeDataSegment(r *bytes.Reader) (*wasm.DataSegment, error) {
Init: b,
}, nil
}
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

@@ -59,7 +59,7 @@ func TestDecodeModule(t *testing.T) {
name: "table and memory section",
input: &wasm.Module{
TableSection: &wasm.Table{Min: 3},
MemorySection: &wasm.Memory{Min: 1, Max: 1},
MemorySection: &wasm.Memory{Min: 1, Max: 1, IsMaxEncoded: true},
},
},
{

View File

@@ -38,3 +38,17 @@ func decodeElementSegment(r *bytes.Reader) (*wasm.ElementSegment, error) {
return &wasm.ElementSegment{OffsetExpr: expr, Init: init}, 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) {
// Currently multiple tables are not supported.
ret = append(ret, leb128.EncodeInt32(0)...)
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))...)
}
return
}

View File

@@ -40,13 +40,13 @@ func EncodeModule(m *wasm.Module) (bytes []byte) {
bytes = append(bytes, encodeStartSection(*m.StartSection)...)
}
if m.SectionElementCount(wasm.SectionIDElement) > 0 {
panic("TODO: ElementSection")
bytes = append(bytes, encodeElementSection(m.ElementSection)...)
}
if m.SectionElementCount(wasm.SectionIDCode) > 0 {
bytes = append(bytes, encodeCodeSection(m.CodeSection)...)
}
if m.SectionElementCount(wasm.SectionIDData) > 0 {
panic("TODO: DataSection")
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.

View File

@@ -109,7 +109,7 @@ func TestModule_Encode(t *testing.T) {
name: "table and memory section",
input: &wasm.Module{
TableSection: &wasm.Table{Min: 3},
MemorySection: &wasm.Memory{Min: 1, Max: 1},
MemorySection: &wasm.Memory{Min: 1, Max: 1, IsMaxEncoded: true},
},
expected: append(append(Magic, version...),
wasm.SectionIDTable, 0x04, // 4 bytes in this section
@@ -130,8 +130,8 @@ func TestModule_Encode(t *testing.T) {
CodeSection: []*wasm.Code{
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}},
},
ExportSection: map[string]*wasm.Export{
"AddInt": {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(0)},
ExportSection: []*wasm.Export{
{Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(0)},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: wasm.Index(0), Name: "addInt"}},
@@ -183,8 +183,8 @@ func TestModule_Encode(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(0)},
},
},
ExportSection: map[string]*wasm.Export{
"sp": {Name: "sp", Type: wasm.ExternTypeGlobal, Index: wasm.Index(0)},
ExportSection: []*wasm.Export{
{Name: "sp", Type: wasm.ExternTypeGlobal, Index: wasm.Index(0)},
},
},
expected: append(append(Magic, version...),

View File

@@ -55,12 +55,12 @@ func decodeGlobalType(r *bytes.Reader) (*wasm.GlobalType, error) {
// 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) []byte {
func encodeGlobal(g *wasm.Global) (data []byte) {
var mutable byte
if g.Type.Mutable {
mutable = 1
}
data := []byte{g.Type.ValType, mutable, g.Init.Opcode}
data = append(data, g.Init.Data...)
return append(data, wasm.OpcodeEnd)
data = []byte{g.Type.ValType, mutable}
data = append(data, encodeConstantExpression(g.Init)...)
return
}

View File

@@ -52,9 +52,14 @@ func encodeImport(i *wasm.Import) []byte {
case wasm.ExternTypeFunc:
data = append(data, leb128.EncodeUint32(i.DescFunc)...)
case wasm.ExternTypeTable:
panic("TODO: encodeExternTypeTable")
data = append(data, wasm.ElemTypeFuncref)
data = append(data, encodeLimitsType(i.DescTable.Min, i.DescTable.Max)...)
case wasm.ExternTypeMemory:
panic("TODO: encodeExternTypeMemory")
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

View File

@@ -8,6 +8,10 @@ import (
)
func TestEncodeImport(t *testing.T) {
ptrOfUint32 := func(v uint32) *uint32 {
return &v
}
tests := []struct {
name string
input *wasm.Import
@@ -98,6 +102,52 @@ func TestEncodeImport(t *testing.T) {
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.ElemTypeFuncref,
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.MemoryMaxPages, 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 {

View File

@@ -15,8 +15,11 @@ func decodeMemory(r *bytes.Reader, memoryMaxPages uint32) (*wasm.Memory, error)
if err != nil {
return nil, err
}
var max uint32
var isMaxEncoded bool
if maxP != nil {
isMaxEncoded = true
max = *maxP
} else {
max = memoryMaxPages
@@ -28,12 +31,16 @@ func decodeMemory(r *bytes.Reader, memoryMaxPages uint32) (*wasm.Memory, error)
} else if min > max {
return nil, fmt.Errorf("min %d pages (%s) > max %d pages (%s)", min, wasm.PagesToUnitOfBytes(min), max, wasm.PagesToUnitOfBytes(max))
}
return &wasm.Memory{Min: min, Max: max}, nil
return &wasm.Memory{Min: min, Max: max, IsMaxEncoded: isMaxEncoded}, nil
}
// 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 {
return encodeLimitsType(i.Min, &i.Max)
maxPtr := &i.Max
if !i.IsMaxEncoded {
maxPtr = nil
}
return encodeLimitsType(i.Min, maxPtr)
}

View File

@@ -20,27 +20,32 @@ func TestMemoryType(t *testing.T) {
}{
{
name: "min 0",
input: &wasm.Memory{Max: wasm.MemoryMaxPages},
input: &wasm.Memory{Max: wasm.MemoryMaxPages, IsMaxEncoded: true},
expected: []byte{0x1, 0, 0x80, 0x80, 0x4},
},
{
name: "min 0 - default max",
input: &wasm.Memory{Max: wasm.MemoryMaxPages},
expected: []byte{0x0, 0},
},
{
name: "min 0, max 0",
input: &wasm.Memory{Max: zero},
input: &wasm.Memory{Max: zero, IsMaxEncoded: true},
expected: []byte{0x1, 0, 0},
},
{
name: "min=max",
input: &wasm.Memory{Min: 1, Max: 1},
input: &wasm.Memory{Min: 1, Max: 1, IsMaxEncoded: true},
expected: []byte{0x1, 1, 1},
},
{
name: "min 0, max largest",
input: &wasm.Memory{Max: max},
input: &wasm.Memory{Max: max, IsMaxEncoded: true},
expected: []byte{0x1, 0, 0x80, 0x80, 0x4},
},
{
name: "min largest max largest",
input: &wasm.Memory{Min: max, Max: max},
input: &wasm.Memory{Min: max, Max: max, IsMaxEncoded: true},
expected: []byte{0x1, 0x80, 0x80, 0x4, 0x80, 0x80, 0x4},
},
}

View File

@@ -92,22 +92,25 @@ func decodeGlobalSection(r *bytes.Reader) ([]*wasm.Global, error) {
return result, nil
}
func decodeExportSection(r *bytes.Reader) (map[string]*wasm.Export, error) {
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)
}
exportSection := make(map[string]*wasm.Export, vs)
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 := exportSection[export.Name]; ok {
if _, ok := usedName[export.Name]; ok {
return nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name)
} else {
usedName[export.Name] = struct{}{}
}
exportSection[export.Name] = export
exportSection = append(exportSection, export)
}
return exportSection, nil
}
@@ -260,7 +263,7 @@ func encodeGlobalSection(globals []*wasm.Global) []byte {
//
// See encodeExport
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#export-section%E2%91%A0
func encodeExportSection(exports map[string]*wasm.Export) []byte {
func encodeExportSection(exports []*wasm.Export) []byte {
contents := leb128.EncodeUint32(uint32(len(exports)))
for _, e := range exports {
contents = append(contents, encodeExport(e)...)
@@ -275,3 +278,27 @@ func encodeExportSection(exports map[string]*wasm.Export) []byte {
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

@@ -76,7 +76,7 @@ func TestMemorySection(t *testing.T) {
0x01, // 1 memory
0x01, 0x02, 0x03, // (memory 2 3)
},
expected: &wasm.Memory{Min: 2, Max: three},
expected: &wasm.Memory{Min: 2, Max: three, IsMaxEncoded: true},
},
}
@@ -127,7 +127,7 @@ func TestDecodeExportSection(t *testing.T) {
tests := []struct {
name string
input []byte
expected map[string]*wasm.Export
expected []*wasm.Export
}{
{
name: "empty and non-empty name",
@@ -138,9 +138,9 @@ func TestDecodeExportSection(t *testing.T) {
0x01, 'a', // Size of name, name
wasm.ExternTypeFunc, 0x01, // func[1]
},
expected: map[string]*wasm.Export{
"": {Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(2)},
"a": {Name: "a", Type: wasm.ExternTypeFunc, Index: wasm.Index(1)},
expected: []*wasm.Export{
{Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(2)},
{Name: "a", Type: wasm.ExternTypeFunc, Index: wasm.Index(1)},
},
},
}

View File

@@ -270,8 +270,8 @@ func TestModule_SectionElementCount(t *testing.T) {
CodeSection: []*Code{
{Body: []byte{OpcodeLocalGet, 0, OpcodeLocalGet, 1, OpcodeI32Add, OpcodeEnd}},
},
ExportSection: map[string]*Export{
"AddInt": {Name: "AddInt", Type: ExternTypeFunc, Index: Index(0)},
ExportSection: []*Export{
{Name: "AddInt", Type: ExternTypeFunc, Index: Index(0)},
},
StartSection: &zero,
},

View File

@@ -170,7 +170,7 @@ func TestPublicModule_Global(t *testing.T) {
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: globalI32(1),
},
@@ -183,7 +183,7 @@ func TestPublicModule_Global(t *testing.T) {
Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: leb128.EncodeInt64(1)},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: globalI64(1),
},
@@ -198,7 +198,7 @@ func TestPublicModule_Global(t *testing.T) {
},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: globalF32(api.EncodeF32(1.0)),
},
@@ -213,7 +213,7 @@ func TestPublicModule_Global(t *testing.T) {
},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: globalF64(api.EncodeF64(1.0)),
},
@@ -226,7 +226,7 @@ func TestPublicModule_Global(t *testing.T) {
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: &mutableGlobal{
g: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32, Mutable: true}, Val: 1},
@@ -241,7 +241,7 @@ func TestPublicModule_Global(t *testing.T) {
Init: &ConstantExpression{Opcode: OpcodeI64Const, Data: leb128.EncodeInt64(1)},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: &mutableGlobal{
g: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI64, Mutable: true}, Val: 1},
@@ -258,7 +258,7 @@ func TestPublicModule_Global(t *testing.T) {
},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: &mutableGlobal{
g: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeF32, Mutable: true}, Val: api.EncodeF32(1.0)},
@@ -275,7 +275,7 @@ func TestPublicModule_Global(t *testing.T) {
},
},
},
ExportSection: map[string]*Export{"global": {Type: ExternTypeGlobal, Name: "global"}},
ExportSection: []*Export{{Type: ExternTypeGlobal, Name: "global"}},
},
expected: &mutableGlobal{
g: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(1.0)},

View File

@@ -28,7 +28,22 @@ func NewHostModule(
globalCount := uint32(len(nameToGlobal))
exportCount := funcCount + memoryCount + globalCount
if exportCount > 0 {
m.ExportSection = make(map[string]*Export, exportCount)
m.ExportSection = make([]*Export, 0, exportCount)
}
// Check name collision as exports cannot collide on names, regardless of type.
for name := range nameToGoFunc {
if _, ok := nameToMemory[name]; ok {
return nil, fmt.Errorf("func[%s] exports the same name as a memory", name)
}
if _, ok := nameToGlobal[name]; ok {
return nil, fmt.Errorf("func[%s] exports the same name as a global", name)
}
}
for name := range nameToMemory {
if _, ok := nameToGlobal[name]; ok {
return nil, fmt.Errorf("memory[%s] exports the same name as a global", name)
}
}
if funcCount > 0 {
@@ -78,7 +93,7 @@ func addFuncs(m *Module, nameToGoFunc map[string]interface{}, enabledFeatures Fe
m.FunctionSection = append(m.FunctionSection, m.maybeAddType(functionType))
m.HostFunctionSection = append(m.HostFunctionSection, &fn)
m.ExportSection[name] = &Export{Type: ExternTypeFunc, Name: name, Index: idx}
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeFunc, Name: name, Index: idx})
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, &NameAssoc{Index: idx, Name: name})
}
return nil
@@ -107,11 +122,7 @@ func addMemory(m *Module, nameToMemory map[string]*Memory) error {
m.MemorySection = v
}
if e, ok := m.ExportSection[name]; ok { // Exports cannot collide on names, regardless of type.
return fmt.Errorf("memory[%s] exports the same name as a %s", name, ExternTypeName(e.Type))
}
m.ExportSection[name] = &Export{Type: ExternTypeMemory, Name: name, Index: 0}
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeMemory, Name: name, Index: 0})
return nil
}
@@ -121,16 +132,13 @@ func addGlobals(m *Module, globals map[string]*Global) error {
globalNames := make([]string, 0, globalCount)
for name := range globals {
if e, ok := m.ExportSection[name]; ok { // Exports cannot collide on names, regardless of type.
return fmt.Errorf("global[%s] exports the same name as a %s", name, ExternTypeName(e.Type))
}
globalNames = append(globalNames, name)
}
sort.Strings(globalNames) // For consistent iteration order
for i, name := range globalNames {
m.GlobalSection = append(m.GlobalSection, globals[name])
m.ExportSection[name] = &Export{Type: ExternTypeGlobal, Name: name, Index: Index(i)}
m.ExportSection = append(m.ExportSection, &Export{Type: ExternTypeGlobal, Name: name, Index: Index(i)})
}
return nil
}

View File

@@ -70,9 +70,9 @@ func TestNewHostModule(t *testing.T) {
},
FunctionSection: []Index{0, 1},
HostFunctionSection: []*reflect.Value{&fnArgsSizesGet, &fnFdWrite},
ExportSection: map[string]*Export{
"args_sizes_get": {Name: "args_sizes_get", Type: ExternTypeFunc, Index: 0},
"fd_write": {Name: "fd_write", Type: ExternTypeFunc, Index: 1},
ExportSection: []*Export{
{Name: "args_sizes_get", Type: ExternTypeFunc, Index: 0},
{Name: "fd_write", Type: ExternTypeFunc, Index: 1},
},
NameSection: &NameSection{
ModuleName: "wasi_snapshot_preview1",
@@ -93,18 +93,16 @@ func TestNewHostModule(t *testing.T) {
TypeSection: []*FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}},
FunctionSection: []Index{0},
HostFunctionSection: []*reflect.Value{&fnSwap},
ExportSection: map[string]*Export{"swap": {Name: "swap", Type: ExternTypeFunc, Index: 0}},
ExportSection: []*Export{{Name: "swap", Type: ExternTypeFunc, Index: 0}},
NameSection: &NameSection{ModuleName: "swapper", FunctionNames: NameMap{{Index: 0, Name: "swap"}}},
},
},
{
name: "memory",
nameToMemory: map[string]*Memory{"memory": {1, 2}},
nameToMemory: map[string]*Memory{"memory": {Min: 1, Max: 2}},
expected: &Module{
MemorySection: &Memory{Min: 1, Max: 2},
ExportSection: map[string]*Export{
"memory": {Name: "memory", Type: ExternTypeMemory, Index: 0},
},
ExportSection: []*Export{{Name: "memory", Type: ExternTypeMemory, Index: 0}},
},
},
{
@@ -130,9 +128,9 @@ func TestNewHostModule(t *testing.T) {
Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)},
},
},
ExportSection: map[string]*Export{
"g1": {Name: "g1", Type: ExternTypeGlobal, Index: 0},
"g2": {Name: "g2", Type: ExternTypeGlobal, Index: 1},
ExportSection: []*Export{
{Name: "g1", Type: ExternTypeGlobal, Index: 0},
{Name: "g2", Type: ExternTypeGlobal, Index: 1},
},
},
},
@@ -143,7 +141,7 @@ func TestNewHostModule(t *testing.T) {
functionArgsSizesGet: a.ArgsSizesGet,
},
nameToMemory: map[string]*Memory{
"memory": {1, 1},
"memory": {Min: 1, Max: 1},
},
nameToGlobal: map[string]*Global{
"g": {
@@ -164,10 +162,10 @@ func TestNewHostModule(t *testing.T) {
},
},
MemorySection: &Memory{Min: 1, Max: 1},
ExportSection: map[string]*Export{
"args_sizes_get": {Name: "args_sizes_get", Type: ExternTypeFunc, Index: 0},
"memory": {Name: "memory", Type: ExternTypeMemory, Index: 0},
"g": {Name: "g", Type: ExternTypeGlobal, Index: 0},
ExportSection: []*Export{
{Name: "args_sizes_get", Type: ExternTypeFunc, Index: 0},
{Name: "memory", Type: ExternTypeMemory, Index: 0},
{Name: "g", Type: ExternTypeGlobal, Index: 0},
},
NameSection: &NameSection{
ModuleName: "env",
@@ -234,30 +232,30 @@ func TestNewHostModule_Errors(t *testing.T) {
{
name: "function has multiple results",
nameToGoFunc: map[string]interface{}{"fn": func() (uint32, uint32) { return 0, 0 }},
nameToMemory: map[string]*Memory{"fn": {1, 1}},
nameToMemory: map[string]*Memory{"mem": {Min: 1, Max: 1}},
expectedErr: "func[fn] multiple result types invalid as feature \"multi-value\" is disabled",
},
{
name: "memory collides on func name",
name: "func collides on memory name",
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
nameToMemory: map[string]*Memory{"fn": {1, 1}},
expectedErr: "memory[fn] exports the same name as a func",
nameToMemory: map[string]*Memory{"fn": {Min: 1, Max: 1}},
expectedErr: "func[fn] exports the same name as a memory",
},
{
name: "multiple memories",
nameToMemory: map[string]*Memory{"memory": {1, 1}, "mem": {2, 2}},
nameToMemory: map[string]*Memory{"memory": {Min: 1, Max: 1}, "mem": {Min: 2, Max: 2}},
expectedErr: "only one memory is allowed, but configured: mem, memory",
},
{
name: "memory max < min",
nameToMemory: map[string]*Memory{"memory": {1, 0}},
nameToMemory: map[string]*Memory{"memory": {Min: 1, Max: 0}},
expectedErr: "memory[memory] min 1 pages (64 Ki) > max 0 pages (0 Ki)",
},
{
name: "global collides on func name",
name: "func collides on global name",
nameToGoFunc: map[string]interface{}{"fn": ArgsSizesGet},
nameToGlobal: map[string]*Global{"fn": {}},
expectedErr: "global[fn] exports the same name as a func",
expectedErr: "func[fn] exports the same name as a global",
},
}

View File

@@ -436,9 +436,9 @@ func TestJIT_SliceAllocatedOnHeap(t *testing.T) {
{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
},
ImportSection: []*wasm.Import{{Module: hostModuleName, Name: hostFnName, DescFunc: 1}},
ExportSection: map[string]*wasm.Export{
valueStackCorruption: {Type: wasm.ExternTypeFunc, Index: 1, Name: valueStackCorruption},
callStackCorruption: {Type: wasm.ExternTypeFunc, Index: 2, Name: callStackCorruption},
ExportSection: []*wasm.Export{
{Type: wasm.ExternTypeFunc, Index: 1, Name: valueStackCorruption},
{Type: wasm.ExternTypeFunc, Index: 2, Name: callStackCorruption},
},
}

View File

@@ -113,7 +113,7 @@ type Module struct {
// Note: In the Binary Format, this is SectionIDExport.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
ExportSection map[string]*Export
ExportSection []*Export
// StartSection is the index of a function to call before returning from Store.Instantiate.
//
@@ -350,30 +350,30 @@ func (m *Module) validateImports(enabledFeatures Features) error {
}
func (m *Module) validateExports(enabledFeatures Features, functions []Index, globals []*GlobalType, memory *Memory, table *Table) error {
for name, exp := range m.ExportSection {
for _, exp := range m.ExportSection {
index := exp.Index
switch exp.Type {
case ExternTypeFunc:
if index >= uint32(len(functions)) {
return fmt.Errorf("unknown function for export[%q]", name)
return fmt.Errorf("unknown function for export[%q]", exp.Name)
}
case ExternTypeGlobal:
if index >= uint32(len(globals)) {
return fmt.Errorf("unknown global for export[%q]", name)
return fmt.Errorf("unknown global for export[%q]", exp.Name)
}
if !globals[index].Mutable {
continue
}
if err := enabledFeatures.Require(FeatureMutableGlobal); err != nil {
return fmt.Errorf("invalid export[%q] global[%d]: %w", name, index, err)
return fmt.Errorf("invalid export[%q] global[%d]: %w", exp.Name, index, err)
}
case ExternTypeMemory:
if index > 0 || memory == nil {
return fmt.Errorf("memory for export[%q] out of range", name)
return fmt.Errorf("memory for export[%q] out of range", exp.Name)
}
case ExternTypeTable:
if index > 0 || table == nil {
return fmt.Errorf("table for export[%q] out of range", name)
return fmt.Errorf("table for export[%q] out of range", exp.Name)
}
}
}
@@ -582,6 +582,8 @@ type limitsType struct {
// Memory describes the limits of pages (64KB) in a memory.
type Memory struct {
Min, Max uint32
// IsMaxEncoded true if the Max is encoded in the orignial source (binary or text).
IsMaxEncoded bool
}
type GlobalType struct {

View File

@@ -444,7 +444,7 @@ func TestModule_validateFunctions(t *testing.T) {
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
ExportSection: map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0}},
ExportSection: []*Export{{Name: "f1", Type: ExternTypeFunc, Index: 0}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
@@ -456,7 +456,7 @@ func TestModule_validateFunctions(t *testing.T) {
ImportSection: []*Import{{Type: ExternTypeFunc}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
ExportSection: map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 1}},
ExportSection: []*Export{{Name: "f1", Type: ExternTypeFunc, Index: 1}},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
require.Error(t, err)
@@ -467,9 +467,9 @@ func TestModule_validateFunctions(t *testing.T) {
TypeSection: []*FunctionType{{}},
FunctionSection: []Index{0},
CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}},
ExportSection: map[string]*Export{
"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0},
"f2": {Name: "f2", Type: ExternTypeFunc, Index: 0},
ExportSection: []*Export{
{Name: "f1", Type: ExternTypeFunc, Index: 0},
{Name: "f2", Type: ExternTypeFunc, Index: 0},
},
}
err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex)
@@ -572,78 +572,78 @@ func TestModule_validateExports(t *testing.T) {
for _, tc := range []struct {
name string
enabledFeatures Features
exportSection map[string]*Export
exportSection []*Export
functions []Index
globals []*GlobalType
memory *Memory
table *Table
expectedErr string
}{
{name: "empty export section", exportSection: map[string]*Export{}},
{name: "empty export section", exportSection: []*Export{}},
{
name: "func",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeFunc, Index: 0}},
exportSection: []*Export{{Type: ExternTypeFunc, Index: 0}},
functions: []Index{100 /* arbitrary type id*/},
},
{
name: "func out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeFunc, Index: 1}},
exportSection: []*Export{{Type: ExternTypeFunc, Index: 1, Name: "e"}},
functions: []Index{100 /* arbitrary type id*/},
expectedErr: `unknown function for export["e1"]`,
expectedErr: `unknown function for export["e"]`,
},
{
name: "global const",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}},
globals: []*GlobalType{{ValType: ValueTypeI32}},
},
{
name: "global var",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 0}},
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
},
{
name: "global var disabled",
enabledFeatures: Features20191205.Set(FeatureMutableGlobal, false),
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 0}},
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 0, Name: "e"}},
globals: []*GlobalType{{ValType: ValueTypeI32, Mutable: true}},
expectedErr: `invalid export["e1"] global[0]: feature "mutable-global" is disabled`,
expectedErr: `invalid export["e"] global[0]: feature "mutable-global" is disabled`,
},
{
name: "global out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeGlobal, Index: 1}},
exportSection: []*Export{{Type: ExternTypeGlobal, Index: 1, Name: "e"}},
globals: []*GlobalType{{}},
expectedErr: `unknown global for export["e1"]`,
expectedErr: `unknown global for export["e"]`,
},
{
name: "table",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeTable, Index: 0}},
exportSection: []*Export{{Type: ExternTypeTable, Index: 0}},
table: &Table{},
},
{
name: "table out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeTable, Index: 1}},
exportSection: []*Export{{Type: ExternTypeTable, Index: 1, Name: "e"}},
table: &Table{},
expectedErr: `table for export["e1"] out of range`,
expectedErr: `table for export["e"] out of range`,
},
{
name: "memory",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 0}},
exportSection: []*Export{{Type: ExternTypeMemory, Index: 0}},
memory: &Memory{},
},
{
name: "memory out of range",
enabledFeatures: Features20191205,
exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 0}},
exportSection: []*Export{{Type: ExternTypeMemory, Index: 0, Name: "e"}},
table: &limitsType{},
expectedErr: `memory for export["e1"] out of range`,
expectedErr: `memory for export["e"] out of range`,
},
} {
tc := tc

View File

@@ -176,7 +176,7 @@ func (m *ModuleInstance) addSections(module *Module, importedFunctions, function
m.buildExports(module.ExportSection)
}
func (m *ModuleInstance) buildExports(exports map[string]*Export) {
func (m *ModuleInstance) buildExports(exports []*Export) {
m.Exports = make(map[string]*ExportInstance, len(exports))
for _, exp := range exports {
index := exp.Index

View File

@@ -38,14 +38,14 @@ func TestModuleInstance_Memory(t *testing.T) {
name: "memory exported, different name",
input: &Module{
MemorySection: &Memory{Min: 1},
ExportSection: map[string]*Export{"momory": {Type: ExternTypeMemory, Name: "momory", Index: 0}},
ExportSection: []*Export{{Type: ExternTypeMemory, Name: "momory", Index: 0}},
},
},
{
name: "memory exported, but zero length",
input: &Module{
MemorySection: &Memory{},
ExportSection: map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory", Index: 0}},
ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}},
},
expected: true,
},
@@ -53,7 +53,7 @@ func TestModuleInstance_Memory(t *testing.T) {
name: "memory exported, one page",
input: &Module{
MemorySection: &Memory{Min: 1},
ExportSection: map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory", Index: 0}},
ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}},
},
expected: true,
expectedLen: 65536,
@@ -62,7 +62,7 @@ func TestModuleInstance_Memory(t *testing.T) {
name: "memory exported, two pages",
input: &Module{
MemorySection: &Memory{Min: 2},
ExportSection: map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory", Index: 0}},
ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}},
},
expected: true,
expectedLen: 65536 * 2,
@@ -145,7 +145,7 @@ func TestStore_CloseModule(t *testing.T) {
TypeSection: []*FunctionType{{}},
FunctionSection: []uint32{0},
CodeSection: []*Code{{Body: []byte{OpcodeEnd}}},
ExportSection: map[string]*Export{"fn": {Type: ExternTypeFunc, Index: 0, Name: "fn"}},
ExportSection: []*Export{{Type: ExternTypeFunc, Index: 0, Name: "fn"}},
}, importedModuleName, nil)
require.NoError(t, err)
},
@@ -357,7 +357,7 @@ func TestModuleContext_ExportedFunction(t *testing.T) {
TypeSection: []*FunctionType{{}},
ImportSection: []*Import{{Type: ExternTypeFunc, Module: "host", Name: "host_fn", DescFunc: 0}},
MemorySection: &Memory{Min: 1},
ExportSection: map[string]*Export{"host.fn": {Type: ExternTypeFunc, Name: "host.fn", Index: 0}},
ExportSection: []*Export{{Type: ExternTypeFunc, Name: "host.fn", Index: 0}},
}, "test", nil)
require.NoError(t, err)
defer importing.Close()
@@ -401,7 +401,7 @@ func TestFunctionInstance_Call(t *testing.T) {
DescFunc: 0,
}},
MemorySection: &Memory{Min: 1},
ExportSection: map[string]*Export{functionName: {Type: ExternTypeFunc, Name: functionName, Index: 0}},
ExportSection: []*Export{{Type: ExternTypeFunc, Name: functionName, Index: 0}},
}, "test", nil)
require.NoError(t, err)
defer imported.Close()

View File

@@ -98,6 +98,8 @@ type moduleParser struct {
// field counts can be different from the count in a section when abbreviated imports exist. To give an accurate
// errorContext, we count explicitly.
fieldCountFunc uint32
exportedName map[string]struct{}
}
// DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Text Format
@@ -449,8 +451,8 @@ func (p *moduleParser) endFunc(typeIdx wasm.Index, code *wasm.Code, name string,
// endMemory adds the limits for the current memory, and increments memoryNamespace as it is shared across imported and
// module-defined memories. Finally, this returns parseModule to prepare for the next field.
func (p *moduleParser) endMemory(min, max uint32) tokenParser {
p.module.MemorySection = &wasm.Memory{Min: min, Max: max}
func (p *moduleParser) endMemory(min, max uint32, maxDecoded bool) tokenParser {
p.module.MemorySection = &wasm.Memory{Min: min, Max: max, IsMaxEncoded: maxDecoded}
p.pos = positionModule
return p.parseModule
}
@@ -468,8 +470,13 @@ func (p *moduleParser) parseExportName(tok tokenType, tokenBytes []byte, _, _ ui
switch tok {
case tokenString: // Ex. "" or "PI"
name := string(tokenBytes[1 : len(tokenBytes)-1]) // strip quotes
if _, ok := p.module.ExportSection[name]; ok {
if p.exportedName == nil {
p.exportedName = map[string]struct{}{}
}
if _, ok := p.exportedName[name]; ok {
return nil, fmt.Errorf("%q already exported", name)
} else {
p.exportedName[name] = struct{}{}
}
p.currentModuleField = &wasm.Export{Name: name}
return p.parseExport, nil
@@ -567,11 +574,7 @@ func (p *moduleParser) parseExportEnd(tok tokenType, tokenBytes []byte, _, _ uin
e := p.currentModuleField.(*wasm.Export)
p.currentModuleField = nil
if p.module.ExportSection == nil {
p.module.ExportSection = map[string]*wasm.Export{e.Name: e}
} else {
p.module.ExportSection[e.Name] = e
}
p.module.ExportSection = append(p.module.ExportSection, e)
p.pos = positionModule
return p.parseModule, nil
}

View File

@@ -1232,8 +1232,8 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{
{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0},
},
ExportSection: map[string]*wasm.Export{
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 0},
ExportSection: []*wasm.Export{
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 0},
},
NameSection: &wasm.NameSection{FunctionNames: wasm.NameMap{{Index: 0, Name: "bar"}}},
},
@@ -1249,8 +1249,8 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{
{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0},
},
ExportSection: map[string]*wasm.Export{
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 0},
ExportSection: []*wasm.Export{
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 0},
},
},
},
@@ -1266,9 +1266,9 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{
{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0},
},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 0},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 0},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{&wasm.NameAssoc{Index: 0, Name: "bar"}},
@@ -1288,9 +1288,9 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: end}},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{
@@ -1313,9 +1313,9 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: end}},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{
@@ -1338,9 +1338,9 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: end}},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
},
},
},
@@ -1357,9 +1357,9 @@ func TestDecodeModule(t *testing.T) {
ImportSection: []*wasm.Import{{Module: "foo", Name: "bar", Type: wasm.ExternTypeFunc, DescFunc: 0}},
FunctionSection: []wasm.Index{0},
CodeSection: []*wasm.Code{{Body: end}},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
"bar": {Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeFunc, Index: 0},
{Name: "bar", Type: wasm.ExternTypeFunc, Index: 1},
},
},
},
@@ -1384,8 +1384,8 @@ func TestDecodeModule(t *testing.T) {
CodeSection: []*wasm.Code{
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}},
},
ExportSection: map[string]*wasm.Export{
"AddInt": {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: 0},
ExportSection: []*wasm.Export{
{Name: "AddInt", Type: wasm.ExternTypeFunc, Index: 0},
},
NameSection: &wasm.NameSection{
FunctionNames: wasm.NameMap{{Index: 0, Name: "addInt"}},
@@ -1406,8 +1406,8 @@ func TestDecodeModule(t *testing.T) {
)`,
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 0, Max: wasm.MemoryMaxPages},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
@@ -1419,8 +1419,8 @@ func TestDecodeModule(t *testing.T) {
)`,
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 0, Max: wasm.MemoryMaxPages},
ExportSection: map[string]*wasm.Export{
"foo": {Name: "foo", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "foo", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},
@@ -1437,9 +1437,9 @@ func TestDecodeModule(t *testing.T) {
TypeSection: []*wasm.FunctionType{v_v},
FunctionSection: []wasm.Index{0, 0, 0},
CodeSection: []*wasm.Code{{Body: end}, {Body: end}, {Body: end}},
ExportSection: map[string]*wasm.Export{
"": {Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(2)},
"a": {Name: "a", Type: wasm.ExternTypeFunc, Index: 1},
ExportSection: []*wasm.Export{
{Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(2)},
{Name: "a", Type: wasm.ExternTypeFunc, Index: 1},
},
},
},
@@ -1451,8 +1451,8 @@ func TestDecodeModule(t *testing.T) {
)`,
expected: &wasm.Module{
MemorySection: &wasm.Memory{Min: 1, Max: wasm.MemoryMaxPages},
ExportSection: map[string]*wasm.Export{
"memory": {Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
ExportSection: []*wasm.Export{
{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0},
},
},
},

View File

@@ -11,7 +11,7 @@ func newMemoryParser(memoryMaxPages uint32, memoryNamespace *indexNamespace, onM
return &memoryParser{memoryMaxPages: memoryMaxPages, memoryNamespace: memoryNamespace, onMemory: onMemory}
}
type onMemory func(min, max uint32) tokenParser
type onMemory func(min, max uint32, maxDecooded bool) tokenParser
// memoryParser parses a api.Memory from and dispatches to onMemory.
//
@@ -24,6 +24,7 @@ type onMemory func(min, max uint32) tokenParser
type memoryParser struct {
// memoryMaxPages is the limit of pages (not bytes) for each wasm.Memory.
memoryMaxPages uint32
maxDecoded bool
memoryNamespace *indexNamespace
@@ -89,6 +90,7 @@ func (p *memoryParser) beginMax(tok tokenType, tokenBytes []byte, line, col uint
} else if i < p.currentMin {
return nil, fmt.Errorf("min %d pages (%s) > max %d pages (%s)", p.currentMin, wasm.PagesToUnitOfBytes(p.currentMin), i, wasm.PagesToUnitOfBytes(i))
}
p.maxDecoded = true
p.currentMax = i
return p.end, nil
case tokenRParen:
@@ -104,5 +106,5 @@ func (p *memoryParser) end(tok tokenType, tokenBytes []byte, _, _ uint32) (token
return nil, unexpectedToken(tok, tokenBytes)
}
p.memoryNamespace.count++
return p.onMemory(p.currentMin, p.currentMax), nil
return p.onMemory(p.currentMin, p.currentMax, p.maxDecoded), nil
}

View File

@@ -24,7 +24,7 @@ func TestMemoryParser(t *testing.T) {
{
name: "min 0, max 0",
input: "(memory 0 0)",
expected: &wasm.Memory{Max: zero},
expected: &wasm.Memory{Max: zero, IsMaxEncoded: true},
},
{
name: "min largest",
@@ -40,17 +40,17 @@ func TestMemoryParser(t *testing.T) {
{
name: "min 0, max largest",
input: "(memory 0 65536)",
expected: &wasm.Memory{Max: max},
expected: &wasm.Memory{Max: max, IsMaxEncoded: true},
},
{
name: "min largest max largest",
input: "(memory 65536 65536)",
expected: &wasm.Memory{Min: max, Max: max},
expected: &wasm.Memory{Min: max, Max: max, IsMaxEncoded: true},
},
{
name: "min largest max largest - ID",
input: "(memory $mem 65536 65536)",
expected: &wasm.Memory{Min: max, Max: max},
expected: &wasm.Memory{Min: max, Max: max, IsMaxEncoded: true},
expectedID: "mem",
},
}
@@ -162,8 +162,8 @@ func TestMemoryParser_Errors(t *testing.T) {
func parseMemoryType(memoryNamespace *indexNamespace, input string) (*wasm.Memory, *memoryParser, error) {
var parsed *wasm.Memory
var setFunc onMemory = func(min, max uint32) tokenParser {
parsed = &wasm.Memory{Min: min, Max: max}
var setFunc onMemory = func(min, max uint32, maxDecoded bool) tokenParser {
parsed = &wasm.Memory{Min: min, Max: max, IsMaxEncoded: maxDecoded}
return parseErr
}
tp := newMemoryParser(wasm.MemoryMaxPages, memoryNamespace, setFunc)

View File

@@ -0,0 +1,81 @@
package spectests
import (
"bytes"
"encoding/json"
"io"
"strings"
"testing"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wasm/binary"
)
// requireStripCustomSections strips all the custom sections from the given binary.
func requireStripCustomSections(t *testing.T, binary []byte) []byte {
r := bytes.NewReader(binary)
out := bytes.NewBuffer(nil)
_, err := io.CopyN(out, r, 8)
require.NoError(t, err)
for {
sectionID, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
require.NoError(t, err)
}
sectionSize, _, err := leb128.DecodeUint32(r)
require.NoError(t, err)
switch sectionID {
case wasm.SectionIDCustom:
_, err = io.CopyN(io.Discard, r, int64(sectionSize))
require.NoError(t, err)
default:
out.WriteByte(sectionID)
out.Write(leb128.EncodeUint32(sectionSize))
_, err := io.CopyN(out, r, int64(sectionSize))
require.NoError(t, err)
}
}
return out.Bytes()
}
// TestBinaryEncoder ensures that binary.EncodeModule produces exactly the same binaries
// for wasm.Module via binary.DecodeModule modulo custom sections for all the valid binaries in spectests.
func TestBinaryEncoder(t *testing.T) {
files, err := testcases.ReadDir("testdata")
require.NoError(t, err)
for _, f := range files {
filename := f.Name()
if strings.HasSuffix(filename, ".json") {
raw, err := testcases.ReadFile(testdataPath(filename))
require.NoError(t, err)
var base testbase
require.NoError(t, json.Unmarshal(raw, &base))
for _, c := range base.Commands {
if c.CommandType == "module" {
t.Run(c.Filename, func(t *testing.T) {
buf, err := testcases.ReadFile(testdataPath(c.Filename))
require.NoError(t, err)
buf = requireStripCustomSections(t, buf)
mod, err := binary.DecodeModule(buf, wasm.Features20191205, wasm.MemoryMaxPages)
require.NoError(t, err)
encodedBuf := binary.EncodeModule(mod)
require.Equal(t, buf, encodedBuf)
})
}
}
}
}
}

View File

@@ -246,26 +246,26 @@ func addSpectestModule(t *testing.T, store *wasm.Store) {
Type: &wasm.GlobalType{ValType: wasm.ValueTypeI32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: leb128.EncodeInt32(666)},
})
mod.ExportSection["global_i32"] = &wasm.Export{Name: "global_i32", Index: 0, Type: wasm.ExternTypeGlobal}
mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_i32", Index: 0, Type: wasm.ExternTypeGlobal})
// (global (export "global_f32") f32 (f32.const 666))
mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF32},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: u64.LeBytes(api.EncodeF32(666))},
})
mod.ExportSection["global_f32"] = &wasm.Export{Name: "global_f32", Index: 1, Type: wasm.ExternTypeGlobal}
mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_f32", Index: 1, Type: wasm.ExternTypeGlobal})
// (global (export "global_f64") f64 (f64.const 666))
mod.GlobalSection = append(mod.GlobalSection, &wasm.Global{
Type: &wasm.GlobalType{ValType: wasm.ValueTypeF64},
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF64Const, Data: u64.LeBytes(api.EncodeF64(666))},
})
mod.ExportSection["global_f64"] = &wasm.Export{Name: "global_f64", Index: 2, Type: wasm.ExternTypeGlobal}
mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "global_f64", Index: 2, Type: wasm.ExternTypeGlobal})
// (table (export "table") 10 20 funcref)
tableLimitMax := uint32(20)
mod.TableSection = &wasm.Table{Min: 10, Max: &tableLimitMax}
mod.ExportSection["table"] = &wasm.Export{Name: "table", Index: 0, Type: wasm.ExternTypeTable}
mod.ExportSection = append(mod.ExportSection, &wasm.Export{Name: "table", Index: 0, Type: wasm.ExternTypeTable})
_, err = store.Instantiate(context.Background(), mod, mod.NameSection.ModuleName, wasm.DefaultSysContext())
require.NoError(t, err)

View File

@@ -59,12 +59,12 @@ func newExample() *wasm.Module {
{Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeI64Extend16S, wasm.OpcodeEnd}},
{Body: []byte{wasm.OpcodeLocalGet, 1, wasm.OpcodeLocalGet, 0, wasm.OpcodeEnd}},
},
MemorySection: &wasm.Memory{Min: 1, Max: three},
ExportSection: map[string]*wasm.Export{
"": {Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(3)},
"AddInt": {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)},
"swap": {Name: "swap", Type: wasm.ExternTypeFunc, Index: wasm.Index(6)},
"mem": {Name: "mem", Type: wasm.ExternTypeMemory, Index: wasm.Index(0)},
MemorySection: &wasm.Memory{Min: 1, Max: three, IsMaxEncoded: true},
ExportSection: []*wasm.Export{
{Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)},
{Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(3)},
{Name: "mem", Type: wasm.ExternTypeMemory, Index: wasm.Index(0)},
{Name: "swap", Type: wasm.ExternTypeFunc, Index: wasm.Index(6)},
},
StartSection: &three,
NameSection: &wasm.NameSection{

View File

@@ -100,7 +100,7 @@ func TestRuntime_DecodeModule_Errors(t *testing.T) {
{
name: "memory has too many pages - binary",
runtime: NewRuntimeWithConfig(NewRuntimeConfig().WithMemoryMaxPages(2)),
source: binary.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Max: 3}}),
source: binary.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true}}),
expectedErr: "section memory: max 3 pages (192 Ki) outside range of 2 pages (128 Ki)",
},
}
@@ -205,8 +205,8 @@ func TestModule_Global(t *testing.T) {
Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
},
},
ExportSection: map[string]*wasm.Export{
"global": {Type: wasm.ExternTypeGlobal, Name: "global"},
ExportSection: []*wasm.Export{
{Type: wasm.ExternTypeGlobal, Name: "global"},
},
},
expected: true,