From e3035b5a76e08eed824331099e67bf7ab324ac69 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Thu, 14 Apr 2022 16:55:53 +0900 Subject: [PATCH] binary: complete encoder (#463) Signed-off-by: Takeshi Yoneda --- builder_test.go | 74 +++++++++++----------- internal/wasm/binary/const_expr.go | 9 ++- internal/wasm/binary/data.go | 9 +++ internal/wasm/binary/decoder_test.go | 2 +- internal/wasm/binary/element.go | 14 ++++ internal/wasm/binary/encoder.go | 4 +- internal/wasm/binary/encoder_test.go | 10 +-- internal/wasm/binary/global.go | 8 +-- internal/wasm/binary/import.go | 9 ++- internal/wasm/binary/import_test.go | 50 +++++++++++++++ internal/wasm/binary/memory.go | 11 +++- internal/wasm/binary/memory_test.go | 15 +++-- internal/wasm/binary/section.go | 37 +++++++++-- internal/wasm/binary/section_test.go | 10 +-- internal/wasm/counts_test.go | 4 +- internal/wasm/global_test.go | 16 ++--- internal/wasm/host.go | 30 +++++---- internal/wasm/host_test.go | 46 +++++++------- internal/wasm/jit/engine_test.go | 6 +- internal/wasm/module.go | 16 +++-- internal/wasm/module_test.go | 44 ++++++------- internal/wasm/store.go | 2 +- internal/wasm/store_test.go | 14 ++-- internal/wasm/text/decoder.go | 19 +++--- internal/wasm/text/decoder_test.go | 60 +++++++++--------- internal/wasm/text/memory_parser.go | 6 +- internal/wasm/text/memory_parser_test.go | 12 ++-- tests/spectest/encoder_test.go | 81 ++++++++++++++++++++++++ tests/spectest/spec_test.go | 8 +-- vs/codec_test.go | 12 ++-- wasm_test.go | 6 +- 31 files changed, 431 insertions(+), 213 deletions(-) create mode 100644 tests/spectest/encoder_test.go diff --git a/builder_test.go b/builder_test.go index eb96b8b7..503aa3d7 100644 --- a/builder_test.go +++ b/builder_test.go @@ -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}, }, }, }, diff --git a/internal/wasm/binary/const_expr.go b/internal/wasm/binary/const_expr.go index dcde3006..ee0e2b0d 100644 --- a/internal/wasm/binary/const_expr.go +++ b/internal/wasm/binary/const_expr.go @@ -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 +} diff --git a/internal/wasm/binary/data.go b/internal/wasm/binary/data.go index ebced30f..03c78bb5 100644 --- a/internal/wasm/binary/data.go +++ b/internal/wasm/binary/data.go @@ -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 +} diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go index 0a46cd37..ab450ca3 100644 --- a/internal/wasm/binary/decoder_test.go +++ b/internal/wasm/binary/decoder_test.go @@ -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}, }, }, { diff --git a/internal/wasm/binary/element.go b/internal/wasm/binary/element.go index e1c64310..a94eda47 100644 --- a/internal/wasm/binary/element.go +++ b/internal/wasm/binary/element.go @@ -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 +} diff --git a/internal/wasm/binary/encoder.go b/internal/wasm/binary/encoder.go index ee636c3e..74890ead 100644 --- a/internal/wasm/binary/encoder.go +++ b/internal/wasm/binary/encoder.go @@ -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. diff --git a/internal/wasm/binary/encoder_test.go b/internal/wasm/binary/encoder_test.go index 90aaf066..49e2b979 100644 --- a/internal/wasm/binary/encoder_test.go +++ b/internal/wasm/binary/encoder_test.go @@ -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...), diff --git a/internal/wasm/binary/global.go b/internal/wasm/binary/global.go index 82c03b1e..b96840fe 100644 --- a/internal/wasm/binary/global.go +++ b/internal/wasm/binary/global.go @@ -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 } diff --git a/internal/wasm/binary/import.go b/internal/wasm/binary/import.go index d423f370..e756107b 100644 --- a/internal/wasm/binary/import.go +++ b/internal/wasm/binary/import.go @@ -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 diff --git a/internal/wasm/binary/import_test.go b/internal/wasm/binary/import_test.go index 2c43fec2..c8cf4502 100644 --- a/internal/wasm/binary/import_test.go +++ b/internal/wasm/binary/import_test.go @@ -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 { diff --git a/internal/wasm/binary/memory.go b/internal/wasm/binary/memory.go index 555fdc8a..bbed69ef 100644 --- a/internal/wasm/binary/memory.go +++ b/internal/wasm/binary/memory.go @@ -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) } diff --git a/internal/wasm/binary/memory_test.go b/internal/wasm/binary/memory_test.go index b6bec63f..341a4240 100644 --- a/internal/wasm/binary/memory_test.go +++ b/internal/wasm/binary/memory_test.go @@ -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}, }, } diff --git a/internal/wasm/binary/section.go b/internal/wasm/binary/section.go index 440a2e07..96f763a9 100644 --- a/internal/wasm/binary/section.go +++ b/internal/wasm/binary/section.go @@ -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) +} diff --git a/internal/wasm/binary/section_test.go b/internal/wasm/binary/section_test.go index aed37240..0bafef43 100644 --- a/internal/wasm/binary/section_test.go +++ b/internal/wasm/binary/section_test.go @@ -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)}, }, }, } diff --git a/internal/wasm/counts_test.go b/internal/wasm/counts_test.go index def97b73..b406ec09 100644 --- a/internal/wasm/counts_test.go +++ b/internal/wasm/counts_test.go @@ -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, }, diff --git a/internal/wasm/global_test.go b/internal/wasm/global_test.go index c142fa95..9a216789 100644 --- a/internal/wasm/global_test.go +++ b/internal/wasm/global_test.go @@ -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)}, diff --git a/internal/wasm/host.go b/internal/wasm/host.go index d80a97c6..38053261 100644 --- a/internal/wasm/host.go +++ b/internal/wasm/host.go @@ -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 } diff --git a/internal/wasm/host_test.go b/internal/wasm/host_test.go index ff746617..ce12c932 100644 --- a/internal/wasm/host_test.go +++ b/internal/wasm/host_test.go @@ -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", }, } diff --git a/internal/wasm/jit/engine_test.go b/internal/wasm/jit/engine_test.go index daa9dd65..6f44f25b 100644 --- a/internal/wasm/jit/engine_test.go +++ b/internal/wasm/jit/engine_test.go @@ -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}, }, } diff --git a/internal/wasm/module.go b/internal/wasm/module.go index ed51a881..ea30c4a3 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -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 { diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index 1a3b0f1b..69a5a4f2 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -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 diff --git a/internal/wasm/store.go b/internal/wasm/store.go index 2d120b0c..67827e63 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -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 diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index 6f1a027c..ea56d13d 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -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() diff --git a/internal/wasm/text/decoder.go b/internal/wasm/text/decoder.go index 4ba86017..8be7db03 100644 --- a/internal/wasm/text/decoder.go +++ b/internal/wasm/text/decoder.go @@ -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 } diff --git a/internal/wasm/text/decoder_test.go b/internal/wasm/text/decoder_test.go index 790f3e22..a7b3fa88 100644 --- a/internal/wasm/text/decoder_test.go +++ b/internal/wasm/text/decoder_test.go @@ -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}, }, }, }, diff --git a/internal/wasm/text/memory_parser.go b/internal/wasm/text/memory_parser.go index c4cf6f22..28884472 100644 --- a/internal/wasm/text/memory_parser.go +++ b/internal/wasm/text/memory_parser.go @@ -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 } diff --git a/internal/wasm/text/memory_parser_test.go b/internal/wasm/text/memory_parser_test.go index c7600adf..842b1a8e 100644 --- a/internal/wasm/text/memory_parser_test.go +++ b/internal/wasm/text/memory_parser_test.go @@ -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) diff --git a/tests/spectest/encoder_test.go b/tests/spectest/encoder_test.go new file mode 100644 index 00000000..641330d7 --- /dev/null +++ b/tests/spectest/encoder_test.go @@ -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) + }) + } + } + } + } +} diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go index 22a8650e..f3ab99bf 100644 --- a/tests/spectest/spec_test.go +++ b/tests/spectest/spec_test.go @@ -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) diff --git a/vs/codec_test.go b/vs/codec_test.go index 57bff59f..6c745ed8 100644 --- a/vs/codec_test.go +++ b/vs/codec_test.go @@ -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{ diff --git a/wasm_test.go b/wasm_test.go index 12d4a9c2..ed559175 100644 --- a/wasm_test.go +++ b/wasm_test.go @@ -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,