From 97c759b9d831f224723c18a4fdecaa75eec51c70 Mon Sep 17 00:00:00 2001 From: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com> Date: Thu, 10 Mar 2022 15:47:49 +0800 Subject: [PATCH] Flattens Memory and Table types and backfills Table encoder (#356) This flattens Memory and Table types, particularly making it a compilation error to add multiple of either. This also backfills binary encoding of Table. Signed-off-by: Adrian Cole --- internal/wasm/binary/decoder.go | 2 +- internal/wasm/binary/decoder_test.go | 12 +- internal/wasm/binary/element.go | 9 +- internal/wasm/binary/encoder.go | 2 +- internal/wasm/binary/encoder_test.go | 15 ++ .../wasm/binary/{types.go => function.go} | 49 ------ .../{types_test.go => function_test.go} | 0 internal/wasm/binary/global.go | 36 ++++- internal/wasm/binary/import.go | 6 +- internal/wasm/binary/limits.go | 47 +++--- internal/wasm/binary/limits_test.go | 24 +-- internal/wasm/binary/memory.go | 22 +-- internal/wasm/binary/memory_test.go | 18 +-- internal/wasm/binary/section.go | 53 +++---- internal/wasm/binary/section_test.go | 91 ++++++++++- internal/wasm/binary/table.go | 45 ++++++ internal/wasm/binary/table_test.go | 100 ++++++++++++ internal/wasm/counts.go | 10 +- internal/wasm/counts_test.go | 16 +- internal/wasm/func_validation.go | 15 +- internal/wasm/module.go | 146 +++++++++--------- internal/wasm/module_test.go | 133 ++++++---------- internal/wasm/store.go | 28 ++-- internal/wasm/store_test.go | 49 +++--- internal/wasm/table.go | 13 ++ internal/wasm/text/decoder.go | 12 +- internal/wasm/text/decoder_test.go | 10 +- internal/wasm/text/memory_parser.go | 2 +- internal/wasm/text/memory_parser_test.go | 22 +-- tests/spectest/spec_test.go | 8 +- vs/bench_fac_iter_test.go | 2 +- vs/codec_test.go | 2 +- 32 files changed, 586 insertions(+), 413 deletions(-) rename internal/wasm/binary/{types.go => function.go} (65%) rename internal/wasm/binary/{types_test.go => function_test.go} (100%) create mode 100644 internal/wasm/binary/table.go create mode 100644 internal/wasm/binary/table_test.go create mode 100644 internal/wasm/table.go diff --git a/internal/wasm/binary/decoder.go b/internal/wasm/binary/decoder.go index c2eb1c6e..301fdf26 100644 --- a/internal/wasm/binary/decoder.go +++ b/internal/wasm/binary/decoder.go @@ -81,7 +81,7 @@ func DecodeModule(binary []byte, features wasm.Features) (*wasm.Module, error) { case wasm.SectionIDMemory: m.MemorySection, err = decodeMemorySection(r) case wasm.SectionIDGlobal: - if m.GlobalSection, err = decodeGlobalSection(r, features); err != nil { + if m.GlobalSection, err = decodeGlobalSection(r); err != nil { return nil, err // avoid re-wrapping the error. } case wasm.SectionIDExport: diff --git a/internal/wasm/binary/decoder_test.go b/internal/wasm/binary/decoder_test.go index b23d3293..cf8d3850 100644 --- a/internal/wasm/binary/decoder_test.go +++ b/internal/wasm/binary/decoder_test.go @@ -57,16 +57,10 @@ func TestDecodeModule(t *testing.T) { }, }, { - name: "memory and export section", + name: "table and memory section", input: &wasm.Module{ - MemorySection: []*wasm.MemoryType{{Min: 0, Max: &zero}}, - ExportSection: map[string]*wasm.Export{ - "mem": { - Name: "mem", - Type: wasm.ExternTypeMemory, - Index: 0, - }, - }, + TableSection: &wasm.Table{Min: 3}, + MemorySection: &wasm.Memory{Min: 1}, }, }, { diff --git a/internal/wasm/binary/element.go b/internal/wasm/binary/element.go index 6b7e0257..fa0c5d06 100644 --- a/internal/wasm/binary/element.go +++ b/internal/wasm/binary/element.go @@ -13,6 +13,9 @@ func decodeElementSegment(r *bytes.Reader) (*wasm.ElementSegment, error) { if err != nil { return nil, fmt.Errorf("get table index: %w", err) } + if ti > 0 { + return nil, fmt.Errorf("at most one table allowed in module, but read index %d", ti) + } expr, err := decodeConstantExpression(r) if err != nil { @@ -33,9 +36,5 @@ func decodeElementSegment(r *bytes.Reader) (*wasm.ElementSegment, error) { init[i] = fIDx } - return &wasm.ElementSegment{ - TableIndex: ti, - OffsetExpr: expr, - Init: init, - }, nil + return &wasm.ElementSegment{OffsetExpr: expr, Init: init}, nil } diff --git a/internal/wasm/binary/encoder.go b/internal/wasm/binary/encoder.go index 0b88b9c6..e5b8d4d6 100644 --- a/internal/wasm/binary/encoder.go +++ b/internal/wasm/binary/encoder.go @@ -25,7 +25,7 @@ func EncodeModule(m *wasm.Module) (bytes []byte) { bytes = append(bytes, encodeFunctionSection(m.FunctionSection)...) } if m.SectionElementCount(wasm.SectionIDTable) > 0 { - panic("TODO: TableSection") + bytes = append(bytes, encodeTableSection(m.TableSection)...) } if m.SectionElementCount(wasm.SectionIDMemory) > 0 { bytes = append(bytes, encodeMemorySection(m.MemorySection)...) diff --git a/internal/wasm/binary/encoder_test.go b/internal/wasm/binary/encoder_test.go index 0a7a6673..97b54836 100644 --- a/internal/wasm/binary/encoder_test.go +++ b/internal/wasm/binary/encoder_test.go @@ -105,6 +105,21 @@ func TestModule_Encode(t *testing.T) { 0x00, // start function index ), }, + { + name: "table and memory section", + input: &wasm.Module{ + TableSection: &wasm.Table{Min: 3}, + MemorySection: &wasm.Memory{Min: 1}, + }, + expected: append(append(Magic, version...), + wasm.SectionIDTable, 0x04, // 4 bytes in this section + 0x01, // 1 table + wasm.ElemTypeFuncref, 0x0, 0x03, // func, only min: 3 + wasm.SectionIDMemory, 0x03, // 3 bytes in this section + 0x01, // 1 memory + 0x0, 0x01, // only min: 01 + ), + }, { name: "exported func with instructions", input: &wasm.Module{ diff --git a/internal/wasm/binary/types.go b/internal/wasm/binary/function.go similarity index 65% rename from internal/wasm/binary/types.go rename to internal/wasm/binary/function.go index c25a59ac..d2533080 100644 --- a/internal/wasm/binary/types.go +++ b/internal/wasm/binary/function.go @@ -1,58 +1,9 @@ package binary import ( - "bytes" - "fmt" - wasm "github.com/tetratelabs/wazero/internal/wasm" ) -func decodeTableType(r *bytes.Reader) (*wasm.TableType, error) { - b, err := r.ReadByte() - if err != nil { - return nil, fmt.Errorf("read leading byte: %v", err) - } - - if b != 0x70 { - return nil, fmt.Errorf("%w: invalid element type %#x != %#x", ErrInvalidByte, b, 0x70) - } - - lm, err := decodeLimitsType(r) - if err != nil { - return nil, fmt.Errorf("read limits: %v", err) - } - - return &wasm.TableType{ - ElemType: 0x70, // funcref - Limit: lm, - }, nil -} - -func decodeGlobalType(r *bytes.Reader, features wasm.Features) (*wasm.GlobalType, error) { - vt, err := decodeValueTypes(r, 1) - if err != nil { - return nil, fmt.Errorf("read value type: %w", err) - } - - ret := &wasm.GlobalType{ - ValType: vt[0], - } - - b, err := r.ReadByte() - if err != nil { - return nil, fmt.Errorf("read mutablity: %w", err) - } - - switch mut := b; mut { - case 0x00: // not mutable - case 0x01: // mutable - ret.Mutable = true - default: - return nil, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut) - } - return ret, nil -} - var nullary = []byte{0x60, 0, 0} // encodedOneParam is a cache of internalwasm.FunctionType values for param length 1 and result length 0 diff --git a/internal/wasm/binary/types_test.go b/internal/wasm/binary/function_test.go similarity index 100% rename from internal/wasm/binary/types_test.go rename to internal/wasm/binary/function_test.go diff --git a/internal/wasm/binary/global.go b/internal/wasm/binary/global.go index bdcf3026..e9c70a69 100644 --- a/internal/wasm/binary/global.go +++ b/internal/wasm/binary/global.go @@ -2,12 +2,16 @@ package binary import ( "bytes" + "fmt" wasm "github.com/tetratelabs/wazero/internal/wasm" ) -func decodeGlobal(r *bytes.Reader, features wasm.Features) (*wasm.Global, error) { - gt, err := decodeGlobalType(r, features) +// decodeGlobal returns the wasm.Global decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global +func decodeGlobal(r *bytes.Reader) (*wasm.Global, error) { + gt, err := decodeGlobalType(r) if err != nil { return nil, err } @@ -20,6 +24,34 @@ func decodeGlobal(r *bytes.Reader, features wasm.Features) (*wasm.Global, error) return &wasm.Global{Type: gt, Init: init}, nil } +// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-globaltype +func decodeGlobalType(r *bytes.Reader) (*wasm.GlobalType, error) { + vt, err := decodeValueTypes(r, 1) + if err != nil { + return nil, fmt.Errorf("read value type: %w", err) + } + + ret := &wasm.GlobalType{ + ValType: vt[0], + } + + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read mutablity: %w", err) + } + + switch mut := b; mut { + case 0x00: // not mutable + case 0x01: // mutable + ret.Mutable = true + default: + return nil, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut) + } + return ret, nil +} + // encodeGlobal returns the internalwasm.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 diff --git a/internal/wasm/binary/import.go b/internal/wasm/binary/import.go index 12679660..0479da10 100644 --- a/internal/wasm/binary/import.go +++ b/internal/wasm/binary/import.go @@ -27,11 +27,11 @@ func decodeImport(r *bytes.Reader, idx uint32, features wasm.Features) (i *wasm. case wasm.ExternTypeFunc: i.DescFunc, _, err = leb128.DecodeUint32(r) case wasm.ExternTypeTable: - i.DescTable, err = decodeTableType(r) + i.DescTable, err = decodeTable(r) case wasm.ExternTypeMemory: - i.DescMem, err = decodeMemoryType(r) + i.DescMem, err = decodeMemory(r) case wasm.ExternTypeGlobal: - i.DescGlobal, err = decodeGlobalType(r, features) + i.DescGlobal, err = decodeGlobalType(r) default: err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b) } diff --git a/internal/wasm/binary/limits.go b/internal/wasm/binary/limits.go index 0c5832d2..c5f07f34 100644 --- a/internal/wasm/binary/limits.go +++ b/internal/wasm/binary/limits.go @@ -5,47 +5,48 @@ import ( "fmt" "github.com/tetratelabs/wazero/internal/leb128" - wasm "github.com/tetratelabs/wazero/internal/wasm" ) -// decodeLimitsType returns the wasm.LimitsType decoded with the WebAssembly 1.0 (20191205) Binary Format. +// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly 1.0 (20191205) Binary Format. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6 -func decodeLimitsType(r *bytes.Reader) (*wasm.LimitsType, error) { - b, err := r.ReadByte() - if err != nil { - return nil, fmt.Errorf("read leading byte: %v", err) +func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, err error) { + var flag byte + if flag, err = r.ReadByte(); err != nil { + err = fmt.Errorf("read leading byte: %v", err) + return } - ret := &wasm.LimitsType{} - switch b { + switch flag { case 0x00: - ret.Min, _, err = leb128.DecodeUint32(r) + min, _, err = leb128.DecodeUint32(r) if err != nil { - return nil, fmt.Errorf("read min of limit: %v", err) + err = fmt.Errorf("read min of limit: %v", err) } case 0x01: - ret.Min, _, err = leb128.DecodeUint32(r) + min, _, err = leb128.DecodeUint32(r) if err != nil { - return nil, fmt.Errorf("read min of limit: %v", err) + err = fmt.Errorf("read min of limit: %v", err) + return } - m, _, err := leb128.DecodeUint32(r) - if err != nil { - return nil, fmt.Errorf("read max of limit: %v", err) + var m uint32 + if m, _, err = leb128.DecodeUint32(r); err != nil { + err = fmt.Errorf("read max of limit: %v", err) + } else { + max = &m } - ret.Max = &m default: - return nil, fmt.Errorf("%v for limits: %#x != 0x00 or 0x01", ErrInvalidByte, b) + err = fmt.Errorf("%v for limits: %#x != 0x00 or 0x01", ErrInvalidByte, flag) } - return ret, nil + return } -// encodeLimitsType returns the internalwasm.LimitsType encoded in WebAssembly 1.0 (20191205) Binary Format. +// encodeLimitsType returns the `limitsType` (min, max) encoded in WebAssembly 1.0 (20191205) Binary Format. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6 -func encodeLimitsType(l *wasm.LimitsType) []byte { - if l.Max == nil { - return append(leb128.EncodeUint32(0x00), leb128.EncodeUint32(l.Min)...) +func encodeLimitsType(min uint32, max *uint32) []byte { + if max == nil { + return append(leb128.EncodeUint32(0x00), leb128.EncodeUint32(min)...) } - return append(leb128.EncodeUint32(0x01), append(leb128.EncodeUint32(l.Min), leb128.EncodeUint32(*l.Max)...)...) + return append(leb128.EncodeUint32(0x01), append(leb128.EncodeUint32(min), leb128.EncodeUint32(*max)...)...) } diff --git a/internal/wasm/binary/limits_test.go b/internal/wasm/binary/limits_test.go index 0075d9f7..50a9e574 100644 --- a/internal/wasm/binary/limits_test.go +++ b/internal/wasm/binary/limits_test.go @@ -7,42 +7,41 @@ import ( "testing" "github.com/stretchr/testify/require" - - wasm "github.com/tetratelabs/wazero/internal/wasm" ) func TestLimitsType(t *testing.T) { zero := uint32(0) - max := uint32(math.MaxUint32) + largest := uint32(math.MaxUint32) tests := []struct { name string - input *wasm.LimitsType + min uint32 + max *uint32 expected []byte }{ { name: "min 0", - input: &wasm.LimitsType{}, expected: []byte{0x0, 0}, }, { name: "min 0, max 0", - input: &wasm.LimitsType{Max: &zero}, + max: &zero, expected: []byte{0x1, 0, 0}, }, { name: "min largest", - input: &wasm.LimitsType{Min: max}, + min: largest, expected: []byte{0x0, 0xff, 0xff, 0xff, 0xff, 0xf}, }, { name: "min 0, max largest", - input: &wasm.LimitsType{Max: &max}, + max: &largest, expected: []byte{0x1, 0, 0xff, 0xff, 0xff, 0xff, 0xf}, }, { name: "min largest max largest", - input: &wasm.LimitsType{Min: max, Max: &max}, + min: largest, + max: &largest, expected: []byte{0x1, 0xff, 0xff, 0xff, 0xff, 0xf, 0xff, 0xff, 0xff, 0xff, 0xf}, }, } @@ -50,15 +49,16 @@ func TestLimitsType(t *testing.T) { for _, tt := range tests { tc := tt - b := encodeLimitsType(tc.input) + b := encodeLimitsType(tc.min, tc.max) t.Run(fmt.Sprintf("encode - %s", tc.name), func(t *testing.T) { require.Equal(t, tc.expected, b) }) t.Run(fmt.Sprintf("decode - %s", tc.name), func(t *testing.T) { - decoded, err := decodeLimitsType(bytes.NewReader(b)) + min, max, err := decodeLimitsType(bytes.NewReader(b)) require.NoError(t, err) - require.Equal(t, decoded, tc.input) + require.Equal(t, min, tc.min) + require.Equal(t, max, tc.max) }) } } diff --git a/internal/wasm/binary/memory.go b/internal/wasm/binary/memory.go index 9d0e9860..2d138902 100644 --- a/internal/wasm/binary/memory.go +++ b/internal/wasm/binary/memory.go @@ -7,30 +7,30 @@ import ( wasm "github.com/tetratelabs/wazero/internal/wasm" ) -// decodeMemoryType returns the wasm.MemoryType decoded with the WebAssembly 1.0 (20191205) Binary Format. +// decodeMemory returns the wasm.Memory decoded with the WebAssembly 1.0 (20191205) Binary Format. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory -func decodeMemoryType(r *bytes.Reader) (*wasm.MemoryType, error) { - ret, err := decodeLimitsType(r) +func decodeMemory(r *bytes.Reader) (*wasm.Memory, error) { + min, max, err := decodeLimitsType(r) if err != nil { return nil, err } - if ret.Min > wasm.MemoryMaxPages { + if min > wasm.MemoryMaxPages { return nil, fmt.Errorf("memory min must be at most 65536 pages (4GiB)") } - if ret.Max != nil { - if *ret.Max < ret.Min { + if max != nil { + if *max < min { return nil, fmt.Errorf("memory size minimum must not be greater than maximum") - } else if *ret.Max > wasm.MemoryMaxPages { + } else if *max > wasm.MemoryMaxPages { return nil, fmt.Errorf("memory max must be at most 65536 pages (4GiB)") } } - return ret, nil + return &wasm.Memory{Min: min, Max: max}, nil } -// encodeMemoryType returns the internalwasm.MemoryType encoded in WebAssembly 1.0 (20191205) Binary Format. +// encodeMemory returns the internalwasm.Memory encoded in WebAssembly 1.0 (20191205) Binary Format. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory -func encodeMemoryType(i *wasm.MemoryType) []byte { - return encodeLimitsType(i) +func encodeMemory(i *wasm.Memory) []byte { + return encodeLimitsType(i.Min, i.Max) } diff --git a/internal/wasm/binary/memory_test.go b/internal/wasm/binary/memory_test.go index 1c0966a0..ab63f673 100644 --- a/internal/wasm/binary/memory_test.go +++ b/internal/wasm/binary/memory_test.go @@ -16,32 +16,32 @@ func TestMemoryType(t *testing.T) { tests := []struct { name string - input *wasm.MemoryType + input *wasm.Memory expected []byte }{ { name: "min 0", - input: &wasm.MemoryType{}, + input: &wasm.Memory{}, expected: []byte{0x0, 0}, }, { name: "min 0, max 0", - input: &wasm.MemoryType{Max: &zero}, + input: &wasm.Memory{Max: &zero}, expected: []byte{0x1, 0, 0}, }, { name: "min largest", - input: &wasm.MemoryType{Min: max}, + input: &wasm.Memory{Min: max}, expected: []byte{0x0, 0x80, 0x80, 0x4}, }, { name: "min 0, max largest", - input: &wasm.MemoryType{Max: &max}, + input: &wasm.Memory{Max: &max}, expected: []byte{0x1, 0, 0x80, 0x80, 0x4}, }, { name: "min largest max largest", - input: &wasm.MemoryType{Min: max, Max: &max}, + input: &wasm.Memory{Min: max, Max: &max}, expected: []byte{0x1, 0x80, 0x80, 0x4, 0x80, 0x80, 0x4}, }, } @@ -49,13 +49,13 @@ func TestMemoryType(t *testing.T) { for _, tt := range tests { tc := tt - b := encodeMemoryType(tc.input) + b := encodeMemory(tc.input) t.Run(fmt.Sprintf("encode - %s", tc.name), func(t *testing.T) { require.Equal(t, tc.expected, b) }) t.Run(fmt.Sprintf("decode - %s", tc.name), func(t *testing.T) { - decoded, err := decodeMemoryType(bytes.NewReader(b)) + decoded, err := decodeMemory(bytes.NewReader(b)) require.NoError(t, err) require.Equal(t, decoded, tc.input) }) @@ -88,7 +88,7 @@ func TestDecodeMemoryType_Errors(t *testing.T) { for _, tt := range tests { tc := tt t.Run(tc.name, func(t *testing.T) { - _, err := decodeMemoryType(bytes.NewReader(tc.input)) + _, err := decodeMemory(bytes.NewReader(tc.input)) require.EqualError(t, err, tc.expectedErr) }) } diff --git a/internal/wasm/binary/section.go b/internal/wasm/binary/section.go index e1ebae1c..fafac522 100644 --- a/internal/wasm/binary/section.go +++ b/internal/wasm/binary/section.go @@ -91,37 +91,31 @@ func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) { return result, err } -func decodeTableSection(r *bytes.Reader) ([]*wasm.TableType, error) { +func decodeTableSection(r *bytes.Reader) (*wasm.Table, error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { - return nil, fmt.Errorf("get size of vector: %w", err) + return nil, fmt.Errorf("error reading size") + } + if vs > 1 { + return nil, fmt.Errorf("at most one table allowed in module, but read %d", vs) } - result := make([]*wasm.TableType, vs) - for i := uint32(0); i < vs; i++ { - if result[i], err = decodeTableType(r); err != nil { - return nil, fmt.Errorf("read table type: %w", err) - } - } - return result, nil + return decodeTable(r) } -func decodeMemorySection(r *bytes.Reader) ([]*wasm.MemoryType, error) { +func decodeMemorySection(r *bytes.Reader) (*wasm.Memory, error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { - return nil, fmt.Errorf("get size of vector: %w", err) + return nil, fmt.Errorf("error reading size") + } + if vs > 1 { + return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs) } - result := make([]*wasm.MemoryType, vs) - for i := uint32(0); i < vs; i++ { - if result[i], err = decodeMemoryType(r); err != nil { - return nil, fmt.Errorf("read memory type: %w", err) - } - } - return result, nil + return decodeMemory(r) } -func decodeGlobalSection(r *bytes.Reader, features wasm.Features) ([]*wasm.Global, error) { +func decodeGlobalSection(r *bytes.Reader) ([]*wasm.Global, error) { vs, _, err := leb128.DecodeUint32(r) if err != nil { return nil, fmt.Errorf("get size of vector: %w", err) @@ -129,7 +123,7 @@ func decodeGlobalSection(r *bytes.Reader, features wasm.Features) ([]*wasm.Globa result := make([]*wasm.Global, vs) for i := uint32(0); i < vs; i++ { - if result[i], err = decodeGlobal(r, features); err != nil { + if result[i], err = decodeGlobal(r); err != nil { return nil, fmt.Errorf("global[%d]: %w", i, err) } } @@ -266,16 +260,23 @@ func encodeCodeSection(code []*wasm.Code) []byte { return encodeSection(wasm.SectionIDCode, contents) } +// encodeTableSection encodes a internalwasm.SectionIDTable for the module-defined function in WebAssembly 1.0 +// (20191205) Binary Format. +// +// See encodeTable +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0 +func encodeTableSection(table *wasm.Table) []byte { + contents := append([]byte{1}, encodeTable(table)...) + return encodeSection(wasm.SectionIDTable, contents) +} + // encodeMemorySection encodes a internalwasm.SectionIDMemory for the module-defined function in WebAssembly 1.0 // (20191205) Binary Format. // -// See encodeMemoryType +// See encodeMemory // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0 -func encodeMemorySection(memories []*wasm.MemoryType) []byte { - contents := leb128.EncodeUint32(uint32(len(memories))) - for _, i := range memories { - contents = append(contents, encodeMemoryType(i)...) - } +func encodeMemorySection(memory *wasm.Memory) []byte { + contents := append([]byte{1}, encodeMemory(memory)...) return encodeSection(wasm.SectionIDMemory, contents) } diff --git a/internal/wasm/binary/section_test.go b/internal/wasm/binary/section_test.go index b5e87724..2de8c54a 100644 --- a/internal/wasm/binary/section_test.go +++ b/internal/wasm/binary/section_test.go @@ -9,21 +9,75 @@ import ( wasm "github.com/tetratelabs/wazero/internal/wasm" ) +func TestTableSection(t *testing.T) { + three := uint32(3) + tests := []struct { + name string + input []byte + expected *wasm.Table + }{ + { + name: "min and min with max", + input: []byte{ + 0x01, // 1 table + wasm.ElemTypeFuncref, 0x01, 2, 3, // (table 2 3) + }, + expected: &wasm.Table{Min: 2, Max: &three}, + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + tables, err := decodeTableSection(bytes.NewReader(tc.input)) + require.NoError(t, err) + require.Equal(t, tc.expected, tables) + }) + } +} + +func TestTableSection_Errors(t *testing.T) { + tests := []struct { + name string + input []byte + expectedErr string + }{ + { + name: "min and min with max", + input: []byte{ + 0x02, // 2 tables + wasm.ElemTypeFuncref, 0x00, 0x01, // (table 1) + wasm.ElemTypeFuncref, 0x01, 0x02, 0x03, // (table 2 3) + }, + expectedErr: "at most one table allowed in module, but read 2", + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + _, err := decodeTableSection(bytes.NewReader(tc.input)) + require.EqualError(t, err, tc.expectedErr) + }) + } +} + func TestMemorySection(t *testing.T) { three := uint32(3) tests := []struct { name string input []byte - expected []*wasm.MemoryType + expected *wasm.Memory }{ { name: "min and min with max", input: []byte{ - 0x02, // 2 memories - 0x00, 1, // (memory 1) - 0x01, 2, 3, // (memory 2, 3) + 0x01, // 1 memory + 0x01, 0x02, 0x03, // (memory 2 3) }, - expected: []*wasm.MemoryType{{Min: 1}, {Min: 2, Max: &three}}, + expected: &wasm.Memory{Min: 2, Max: &three}, }, } @@ -38,6 +92,33 @@ func TestMemorySection(t *testing.T) { } } +func TestMemorySection_Errors(t *testing.T) { + tests := []struct { + name string + input []byte + expectedErr string + }{ + { + name: "min and min with max", + input: []byte{ + 0x02, // 2 memories + 0x01, // (memory 1) + 0x02, 0x03, // (memory 2 3) + }, + expectedErr: "at most one memory allowed in module, but read 2", + }, + } + + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + _, err := decodeMemorySection(bytes.NewReader(tc.input)) + require.EqualError(t, err, tc.expectedErr) + }) + } +} + func TestDecodeExportSection(t *testing.T) { tests := []struct { name string diff --git a/internal/wasm/binary/table.go b/internal/wasm/binary/table.go new file mode 100644 index 00000000..89a86c33 --- /dev/null +++ b/internal/wasm/binary/table.go @@ -0,0 +1,45 @@ +package binary + +import ( + "bytes" + "fmt" + + wasm "github.com/tetratelabs/wazero/internal/wasm" +) + +// decodeTable returns the wasm.Table decoded with the WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table +func decodeTable(r *bytes.Reader) (*wasm.Table, error) { + b, err := r.ReadByte() + if err != nil { + return nil, fmt.Errorf("read leading byte: %v", err) + } + + if b != wasm.ElemTypeFuncref { + return nil, fmt.Errorf("invalid element type %#x != funcref(%#x)", b, wasm.ElemTypeFuncref) + } + + min, max, err := decodeLimitsType(r) + if err != nil { + return nil, fmt.Errorf("read limits: %v", err) + } + if min > wasm.MaximumFunctionIndex { + return nil, fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex) + } + if max != nil { + if *max < min { + return nil, fmt.Errorf("table size minimum must not be greater than maximum") + } else if *max > wasm.MaximumFunctionIndex { + return nil, fmt.Errorf("table max must be at most %d", wasm.MaximumFunctionIndex) + } + } + return &wasm.Table{Min: min, Max: max}, nil +} + +// encodeTable returns the internalwasm.Table encoded in WebAssembly 1.0 (20191205) Binary Format. +// +// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table +func encodeTable(i *wasm.Table) []byte { + return append([]byte{wasm.ElemTypeFuncref}, encodeLimitsType(i.Min, i.Max)...) +} diff --git a/internal/wasm/binary/table_test.go b/internal/wasm/binary/table_test.go new file mode 100644 index 00000000..4b91770e --- /dev/null +++ b/internal/wasm/binary/table_test.go @@ -0,0 +1,100 @@ +package binary + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + wasm "github.com/tetratelabs/wazero/internal/wasm" +) + +func TestTableType(t *testing.T) { + zero := uint32(0) + max := wasm.MaximumFunctionIndex + + tests := []struct { + name string + input *wasm.Table + expected []byte + }{ + { + name: "min 0", + input: &wasm.Table{}, + expected: []byte{wasm.ElemTypeFuncref, 0x0, 0}, + }, + { + name: "min 0, max 0", + input: &wasm.Table{Max: &zero}, + expected: []byte{wasm.ElemTypeFuncref, 0x1, 0, 0}, + }, + { + name: "min largest", + input: &wasm.Table{Min: max}, + expected: []byte{wasm.ElemTypeFuncref, 0x0, 0x80, 0x80, 0x80, 0x40}, + }, + { + name: "min 0, max largest", + input: &wasm.Table{Max: &max}, + expected: []byte{wasm.ElemTypeFuncref, 0x1, 0, 0x80, 0x80, 0x80, 0x40}, + }, + { + name: "min largest max largest", + input: &wasm.Table{Min: max, Max: &max}, + expected: []byte{wasm.ElemTypeFuncref, 0x1, 0x80, 0x80, 0x80, 0x40, 0x80, 0x80, 0x80, 0x40}, + }, + } + + for _, tt := range tests { + tc := tt + + b := encodeTable(tc.input) + t.Run(fmt.Sprintf("encode - %s", tc.name), func(t *testing.T) { + require.Equal(t, tc.expected, b) + }) + + t.Run(fmt.Sprintf("decode - %s", tc.name), func(t *testing.T) { + decoded, err := decodeTable(bytes.NewReader(b)) + require.NoError(t, err) + require.Equal(t, decoded, tc.input) + }) + } +} + +func TestDecodeTableType_Errors(t *testing.T) { + tests := []struct { + name string + input []byte + expectedErr string + }{ + { + name: "not func ref", + input: []byte{0x50, 0x1, 0x80, 0x80, 0x4, 0}, + expectedErr: "invalid element type 0x50 != funcref(0x70)", + }, + { + name: "max < min", + input: []byte{wasm.ElemTypeFuncref, 0x1, 0x80, 0x80, 0x4, 0}, + expectedErr: "table size minimum must not be greater than maximum", + }, + { + name: "min > limit", + input: []byte{wasm.ElemTypeFuncref, 0x0, 0xff, 0xff, 0xff, 0xff, 0xf}, + expectedErr: "table min must be at most 134217728", + }, + { + name: "max > limit", + input: []byte{wasm.ElemTypeFuncref, 0x1, 0, 0xff, 0xff, 0xff, 0xff, 0xf}, + expectedErr: "table max must be at most 134217728", + }, + } + + for _, tt := range tests { + tc := tt + t.Run(tc.name, func(t *testing.T) { + _, err := decodeTable(bytes.NewReader(tc.input)) + require.EqualError(t, err, tc.expectedErr) + }) + } +} diff --git a/internal/wasm/counts.go b/internal/wasm/counts.go index 14260e55..34d21e37 100644 --- a/internal/wasm/counts.go +++ b/internal/wasm/counts.go @@ -58,9 +58,15 @@ func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as case SectionIDFunction: return uint32(len(m.FunctionSection)) case SectionIDTable: - return uint32(len(m.TableSection)) + if m.TableSection != nil { + return 1 + } + return 0 case SectionIDMemory: - return uint32(len(m.MemorySection)) + if m.MemorySection != nil { + return 1 + } + return 0 case SectionIDGlobal: return uint32(len(m.GlobalSection)) case SectionIDExport: diff --git a/internal/wasm/counts_test.go b/internal/wasm/counts_test.go index 4e3c58a6..78489ed5 100644 --- a/internal/wasm/counts_test.go +++ b/internal/wasm/counts_test.go @@ -67,7 +67,7 @@ func TestModule_ImportTableCount(t *testing.T) { }, { name: "none with table section", - input: &Module{TableSection: []*TableType{{0x70, &LimitsType{1, nil}}}}, + input: &Module{TableSection: &Table{1, nil}}, }, { name: "one", @@ -78,7 +78,7 @@ func TestModule_ImportTableCount(t *testing.T) { name: "one with table section", input: &Module{ ImportSection: []*Import{{Type: ExternTypeTable}}, - TableSection: []*TableType{{0x70, &LimitsType{1, nil}}}, + TableSection: &Table{1, nil}, }, expected: 1, }, @@ -116,7 +116,7 @@ func TestModule_ImportMemoryCount(t *testing.T) { }, { name: "none with memory section", - input: &Module{MemorySection: []*MemoryType{{Min: 1}}}, + input: &Module{MemorySection: &Memory{Min: 1}}, }, { name: "one", @@ -127,7 +127,7 @@ func TestModule_ImportMemoryCount(t *testing.T) { name: "one with memory section", input: &Module{ ImportSection: []*Import{{Type: ExternTypeMemory}}, - MemorySection: []*MemoryType{{Min: 1}}, + MemorySection: &Memory{Min: 1}, }, expected: 1, }, @@ -281,16 +281,16 @@ func TestModule_SectionElementCount(t *testing.T) { { name: "MemorySection and DataSection", input: &Module{ - MemorySection: []*MemoryType{{Min: 1}}, - DataSection: []*DataSegment{{MemoryIndex: 0, OffsetExpression: empty}}, + MemorySection: &Memory{Min: 1}, + DataSection: []*DataSegment{{OffsetExpression: empty}}, }, expected: map[string]uint32{"data": 1, "memory": 1}, }, { name: "TableSection and ElementSection", input: &Module{ - TableSection: []*TableType{{ElemType: 0x70, Limit: &LimitsType{Min: 1}}}, - ElementSection: []*ElementSegment{{TableIndex: 0, OffsetExpr: empty}}, + TableSection: &Table{Min: 1}, + ElementSection: []*ElementSegment{{OffsetExpr: empty}}, }, expected: map[string]uint32{"element": 1, "table": 1}, }, diff --git a/internal/wasm/func_validation.go b/internal/wasm/func_validation.go index 4bd75e68..81449f73 100644 --- a/internal/wasm/func_validation.go +++ b/internal/wasm/func_validation.go @@ -28,16 +28,11 @@ func validateFunction( localTypes []ValueType, functions []Index, globals []*GlobalType, - memories []*MemoryType, - tables []*TableType, + memory *Memory, + table *Table, types []*FunctionType, maxStackValues int, ) error { - // Note: In WebAssembly 1.0 (20191205), multiple memories are not allowed. - hasMemory := len(memories) > 0 - // Note: In WebAssembly 1.0 (20191205), multiple tables are not allowed. - hasTable := len(tables) > 0 - // We start with the outermost control block which is for function return if the code branches into it. controlBlockStack := []*controlBlock{{blockType: functionType}} // Create the valueTypeStack to track the state of Wasm value stacks at anypoint of execution. @@ -48,7 +43,7 @@ func validateFunction( for pc := uint64(0); pc < uint64(len(body)); pc++ { op := body[pc] if OpcodeI32Load <= op && op <= OpcodeI64Store32 { - if !hasMemory { + if memory == nil { return fmt.Errorf("unknown memory access") } pc++ @@ -236,7 +231,7 @@ func validateFunction( } pc += num - 1 } else if OpcodeMemorySize <= op && op <= OpcodeMemoryGrow { - if !hasMemory { + if memory == nil { return fmt.Errorf("unknown memory access") } pc++ @@ -486,7 +481,7 @@ func validateFunction( if body[pc] != 0x00 { return fmt.Errorf("call_indirect reserved bytes not zero but got %d", body[pc]) } - if !hasTable { + if table == nil { return fmt.Errorf("table not given while having call_indirect") } if err := valueTypeStack.popAndVerifyType(ValueTypeI32); err != nil { diff --git a/internal/wasm/module.go b/internal/wasm/module.go index dd98cd5a..3fe1ccbe 100644 --- a/internal/wasm/module.go +++ b/internal/wasm/module.go @@ -80,7 +80,7 @@ type Module struct { // Note: In the Binary Format, this is SectionIDTable. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0 - TableSection []*TableType + TableSection *Table // MemorySection contains each memory defined in this module. // @@ -88,13 +88,13 @@ type Module struct { // For example, if there are two imported memories and one defined in this module, the memory Index 3 is defined in // this module at TableSection[0]. // - // Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one memory definition per module, so the length of - // the MemorySection can be zero or one, and can only be one if there is no imported memory. + // Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one memory definition per module, so the + // length of the MemorySection can be zero or one, and can only be one if there is no imported memory. // // Note: In the Binary Format, this is SectionIDMemory. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0 - MemorySection []*MemoryType + MemorySection *Memory // GlobalSection contains each global defined in this module. // @@ -156,9 +156,10 @@ type Module struct { } // The wazero specific limitation described at RATIONALE.md. +// TL;DR; We multiply by 8 (to get offsets in bytes) and the multiplication result must be less than 32bit max const ( - maximumGlobals = 1 << 27 - maximumFunctionIndex = 1 << 27 + MaximumGlobals = uint32(1 << 27) + MaximumFunctionIndex = uint32(1 << 27) ) // TypeOfFunction returns the internalwasm.SectionIDType index for the given function namespace index or nil. @@ -201,35 +202,38 @@ func (m *Module) Validate(enabledFeatures Features) error { return errors.New("cannot mix functions and host functions in the same module") } - functions, globals, memories, tables := m.allDeclarations() - - if err := m.validateGlobals(globals, maximumGlobals); err != nil { + functions, globals, memory, table, err := m.allDeclarations() + if err != nil { return err } - if err := m.validateTables(tables, globals); err != nil { + if err = m.validateGlobals(globals, MaximumGlobals); err != nil { return err } - if err := m.validateMemories(memories, globals); err != nil { + if err = m.validateMemory(memory, globals); err != nil { + return err + } + + if err = m.validateTable(table, globals); err != nil { return err } if m.CodeSection != nil { - if err := m.validateFunctions(enabledFeatures, functions, globals, memories, tables, maximumFunctionIndex); err != nil { + if err = m.validateFunctions(enabledFeatures, functions, globals, memory, table, MaximumFunctionIndex); err != nil { return err } } else { - if err := m.validateHostFunctions(); err != nil { + if err = m.validateHostFunctions(); err != nil { return err } } - if err := m.validateImports(enabledFeatures); err != nil { + if err = m.validateImports(enabledFeatures); err != nil { return err } - if err := m.validateExports(enabledFeatures, functions, globals, memories, tables); err != nil { + if err = m.validateExports(enabledFeatures, functions, globals, memory, table); err != nil { return err } @@ -252,8 +256,8 @@ func (m *Module) validateStartSection() error { return nil } -func (m *Module) validateGlobals(globals []*GlobalType, maxGlobals int) error { - if len(globals) > maxGlobals { +func (m *Module) validateGlobals(globals []*GlobalType, maxGlobals uint32) error { + if uint32(len(globals)) > maxGlobals { return fmt.Errorf("too many globals in a module") } @@ -268,8 +272,8 @@ func (m *Module) validateGlobals(globals []*GlobalType, maxGlobals int) error { return nil } -func (m *Module) validateFunctions(enabledFeatures Features, functions []Index, globals []*GlobalType, memories []*MemoryType, tables []*TableType, maximumFunctionIndex int) error { - if len(functions) > maximumFunctionIndex { +func (m *Module) validateFunctions(enabledFeatures Features, functions []Index, globals []*GlobalType, memory *Memory, table *Table, maximumFunctionIndex uint32) error { + if uint32(len(functions)) > maximumFunctionIndex { return fmt.Errorf("too many functions in a store") } @@ -296,7 +300,7 @@ func (m *Module) validateFunctions(enabledFeatures Features, functions []Index, m.TypeSection[typeIndex], m.CodeSection[idx].Body, m.CodeSection[idx].LocalTypes, - functions, globals, memories, tables, m.TypeSection, maximumValuesOnStack); err != nil { + functions, globals, memory, table, m.TypeSection, maximumValuesOnStack); err != nil { return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err) } @@ -321,13 +325,9 @@ func (m *Module) funcDesc(sectionID SectionID, sectionIndex Index) string { return fmt.Sprintf("%s[%d] export[%s]", sectionIDName, sectionIndex, strings.Join(exportNames, ",")) } -func (m *Module) validateTables(tables []*TableType, globals []*GlobalType) error { - if len(tables) > 1 { - return fmt.Errorf("multiple tables are not supported") - } - +func (m *Module) validateTable(table *Table, globals []*GlobalType) error { for _, elem := range m.ElementSection { - if int(elem.TableIndex) >= len(tables) { + if table == nil { return fmt.Errorf("table index out of range") } err := validateConstExpression(globals, elem.OffsetExpr, ValueTypeI32) @@ -338,17 +338,12 @@ func (m *Module) validateTables(tables []*TableType, globals []*GlobalType) erro return nil } -func (m *Module) validateMemories(memories []*MemoryType, globals []*GlobalType) error { - if len(memories) > 1 { - return fmt.Errorf("multiple memories are not supported") - } else if len(m.DataSection) > 0 && len(memories) == 0 { +func (m *Module) validateMemory(memory *Memory, globals []*GlobalType) error { + if len(m.DataSection) > 0 && memory == nil { return fmt.Errorf("unknown memory") } for _, d := range m.DataSection { - if d.MemoryIndex != 0 { - return fmt.Errorf("memory index must be zero") - } if err := validateConstExpression(globals, d.OffsetExpression, ValueTypeI32); err != nil { return fmt.Errorf("calculate offset: %w", err) } @@ -371,7 +366,7 @@ func (m *Module) validateImports(enabledFeatures Features) error { return nil } -func (m *Module) validateExports(enabledFeatures Features, functions []Index, globals []*GlobalType, memories []*MemoryType, tables []*TableType) error { +func (m *Module) validateExports(enabledFeatures Features, functions []Index, globals []*GlobalType, memory *Memory, table *Table) error { for name, exp := range m.ExportSection { index := exp.Index switch exp.Type { @@ -390,12 +385,12 @@ func (m *Module) validateExports(enabledFeatures Features, functions []Index, gl return fmt.Errorf("invalid export[%q] global[%d]: %w", name, index, err) } case ExternTypeMemory: - if index != 0 || len(memories) == 0 { - return fmt.Errorf("unknown memory for export[%q]", name) + if index > 0 || memory == nil { + return fmt.Errorf("memory for export[%q] out of range", name) } case ExternTypeTable: - if index >= uint32(len(tables)) { - return fmt.Errorf("unknown table for export[%q]", name) + if index > 0 || table == nil { + return fmt.Errorf("table for export[%q] out of range", name) } } } @@ -452,8 +447,6 @@ func validateConstExpression(globals []*GlobalType, expr *ConstantExpression, ex func (m *Module) buildGlobals(importedGlobals []*GlobalInstance) (globals []*GlobalInstance) { for _, gs := range m.GlobalSection { var gv uint64 - // Global initialization constant expression can only reference the imported globals. - // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 switch v := executeConstExpression(importedGlobals, gs.Init).(type) { case int32: gv = uint64(v) @@ -508,7 +501,8 @@ func (m *Module) buildFunctions() (functions []*FunctionInstance) { } func (m *Module) buildMemory() (mem *MemoryInstance) { - for _, memSec := range m.MemorySection { + memSec := m.MemorySection + if memSec != nil { mem = &MemoryInstance{ Buffer: make([]byte, MemoryPagesToBytesNum(memSec.Min)), Min: memSec.Min, @@ -518,18 +512,6 @@ func (m *Module) buildMemory() (mem *MemoryInstance) { return } -func (m *Module) buildTable() (table *TableInstance) { - for _, tableSeg := range m.TableSection { - table = &TableInstance{ - Table: make([]uintptr, tableSeg.Limit.Min), - Min: tableSeg.Limit.Min, - Max: tableSeg.Limit.Max, - ElemType: 0x70, // funcref - } - } - return -} - // Index is the offset in an index namespace, not necessarily an absolute position in a Module section. This is because // index namespaces are often preceded by a corresponding type in the Module.ImportSection. // @@ -599,25 +581,24 @@ type Import struct { Name string // DescFunc is the index in Module.TypeSection when Type equals ExternTypeFunc DescFunc Index - // DescTable is the inlined TableType when Type equals ExternTypeTable - DescTable *TableType - // DescMem is the inlined MemoryType when Type equals ExternTypeMemory - DescMem *MemoryType + // DescTable is the inlined Table when Type equals ExternTypeTable + DescTable *Table + // DescMem is the inlined Memory when Type equals ExternTypeMemory + DescMem *Memory // DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal DescGlobal *GlobalType } -type LimitsType struct { +type limitsType struct { Min uint32 Max *uint32 } -type TableType struct { - ElemType byte - Limit *LimitsType -} +// Table describes the limits of (function) elements in a table. +type Table = limitsType -type MemoryType = LimitsType +// Memory describes the limits of pages (64KB) in a memory. +type Memory = limitsType type GlobalType struct { ValType ValueType @@ -638,8 +619,10 @@ type ConstantExpression struct { // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-export type Export struct { Type ExternType + // Name is what the host refers to this definition as. Name string + // Index is the index of the definition to export, the index namespace is by Type // Ex. If ExternTypeFunc, this is a position in the function index namespace. Index Index @@ -649,9 +632,12 @@ type Export struct { // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-elem type ElementSegment struct { - TableIndex Index + // OffsetExpr returns the table element offset to apply to Init indices. + // Note: This can be validated prior to instantiation unless it includes OpcodeGlobalGet (an imported global). OffsetExpr *ConstantExpression - // Init are positions in the function index namespace. + + // Init indices are table elements relative to the result of OffsetExpr. The values are positions in the function + // index namespace that initialize the corresponding element. Init []Index } @@ -668,7 +654,6 @@ type Code struct { } type DataSegment struct { - MemoryIndex Index // supposed to be zero OffsetExpression *ConstantExpression Init []byte } @@ -735,7 +720,7 @@ type NameMapAssoc struct { } // allDeclarations returns all declarations for functions, globals, memories and tables in a module including imported ones. -func (m *Module) allDeclarations() (functions []Index, globals []*GlobalType, memories []*MemoryType, tables []*TableType) { +func (m *Module) allDeclarations() (functions []Index, globals []*GlobalType, memory *Memory, table *Table, err error) { for _, imp := range m.ImportSection { switch imp.Type { case ExternTypeFunc: @@ -743,9 +728,9 @@ func (m *Module) allDeclarations() (functions []Index, globals []*GlobalType, me case ExternTypeGlobal: globals = append(globals, imp.DescGlobal) case ExternTypeMemory: - memories = append(memories, imp.DescMem) + memory = imp.DescMem case ExternTypeTable: - tables = append(tables, imp.DescTable) + table = imp.DescTable } } @@ -753,8 +738,20 @@ func (m *Module) allDeclarations() (functions []Index, globals []*GlobalType, me for _, g := range m.GlobalSection { globals = append(globals, g.Type) } - memories = append(memories, m.MemorySection...) - tables = append(tables, m.TableSection...) + if m.MemorySection != nil { + if memory != nil { // shouldn't be possible due to Validate + err = errors.New("at most one table allowed in module") + return + } + memory = m.MemorySection + } + if m.TableSection != nil { + if table != nil { // shouldn't be possible due to Validate + err = errors.New("at most one table allowed in module") + return + } + table = m.TableSection + } return } @@ -836,6 +833,13 @@ func ValueTypeName(t ValueType) string { return publicwasm.ValueTypeName(t) } +// ElemType is fixed to ElemTypeFuncref until post 20191205 reference type is implemented. +type ElemType = byte + +const ( + ElemTypeFuncref ElemType = 0x70 +) + // ExternType classifies imports and exports with their respective types. // // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0 diff --git a/internal/wasm/module_test.go b/internal/wasm/module_test.go index 0e658571..de40615e 100644 --- a/internal/wasm/module_test.go +++ b/internal/wasm/module_test.go @@ -95,8 +95,8 @@ func TestModule_allDeclarations(t *testing.T) { module *Module expectedFunctions []Index expectedGlobals []*GlobalType - expectedMemories []*MemoryType - expectedTables []*TableType + expectedMemory *Memory + expectedTable *Table }{ // Functions. { @@ -141,51 +141,38 @@ func TestModule_allDeclarations(t *testing.T) { // Memories. { module: &Module{ - ImportSection: []*Import{{Type: ExternTypeMemory, DescMem: &LimitsType{Min: 1}}}, - MemorySection: []*MemoryType{{Min: 100}}, + ImportSection: []*Import{{Type: ExternTypeMemory, DescMem: &limitsType{Min: 1}}}, }, - expectedMemories: []*MemoryType{{Min: 1}, {Min: 100}}, + expectedMemory: &Memory{Min: 1}, }, { module: &Module{ - ImportSection: []*Import{{Type: ExternTypeMemory, DescMem: &LimitsType{Min: 1}}}, + MemorySection: &Memory{Min: 100}, }, - expectedMemories: []*MemoryType{{Min: 1}}, - }, - { - module: &Module{ - MemorySection: []*MemoryType{{Min: 100}}, - }, - expectedMemories: []*MemoryType{{Min: 100}}, + expectedMemory: &Memory{Min: 100}, }, // Tables. { module: &Module{ - ImportSection: []*Import{{Type: ExternTypeTable, DescTable: &TableType{Limit: &LimitsType{Min: 1}}}}, - TableSection: []*TableType{{Limit: &LimitsType{Min: 10}}}, + ImportSection: []*Import{{Type: ExternTypeTable, DescTable: &Table{Min: 1}}}, }, - expectedTables: []*TableType{{Limit: &LimitsType{Min: 1}}, {Limit: &LimitsType{Min: 10}}}, + expectedTable: &Table{Min: 1}, }, { module: &Module{ - ImportSection: []*Import{{Type: ExternTypeTable, DescTable: &TableType{Limit: &LimitsType{Min: 1}}}}, + TableSection: &Table{Min: 10}, }, - expectedTables: []*TableType{{Limit: &LimitsType{Min: 1}}}, - }, - { - module: &Module{ - TableSection: []*TableType{{Limit: &LimitsType{Min: 10}}}, - }, - expectedTables: []*TableType{{Limit: &LimitsType{Min: 10}}}, + expectedTable: &Table{Min: 10}, }, } { tc := tc t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - functions, globals, memories, tables := tc.module.allDeclarations() + functions, globals, memory, table, err := tc.module.allDeclarations() + require.NoError(t, err) require.Equal(t, tc.expectedFunctions, functions) require.Equal(t, tc.expectedGlobals, globals) - require.Equal(t, tc.expectedTables, tables) - require.Equal(t, tc.expectedMemories, memories) + require.Equal(t, tc.expectedTable, table) + require.Equal(t, tc.expectedMemory, memory) }) } } @@ -414,7 +401,7 @@ func TestModule_validateFunctions(t *testing.T) { FunctionSection: []uint32{0}, CodeSection: []*Code{{Body: []byte{OpcodeI32Const, 0, OpcodeDrop, OpcodeEnd}}}, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.NoError(t, err) }) t.Run("too many functions", func(t *testing.T) { @@ -429,7 +416,7 @@ func TestModule_validateFunctions(t *testing.T) { FunctionSection: []Index{0}, CodeSection: nil, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) require.EqualError(t, err, "code count (0) != function count (1)") }) @@ -439,7 +426,7 @@ func TestModule_validateFunctions(t *testing.T) { FunctionSection: []Index{1}, CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) require.EqualError(t, err, "invalid function[0]: type section index 1 out of range") }) @@ -449,7 +436,7 @@ func TestModule_validateFunctions(t *testing.T) { FunctionSection: []Index{0}, CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}}, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) require.Contains(t, err.Error(), "invalid function[0]: cannot pop the 1st f32 operand") }) @@ -460,7 +447,7 @@ func TestModule_validateFunctions(t *testing.T) { CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}}, ExportSection: map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 0}}, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) require.Contains(t, err.Error(), `invalid function[0] export["f1"]: cannot pop the 1st f32`) }) @@ -472,7 +459,7 @@ func TestModule_validateFunctions(t *testing.T) { CodeSection: []*Code{{Body: []byte{OpcodeF32Abs}}}, ExportSection: map[string]*Export{"f1": {Name: "f1", Type: ExternTypeFunc, Index: 1}}, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) require.Contains(t, err.Error(), `invalid function[0] export["f1"]: cannot pop the 1st f32`) }) @@ -486,87 +473,60 @@ func TestModule_validateFunctions(t *testing.T) { "f2": {Name: "f2", Type: ExternTypeFunc, Index: 0}, }, } - err := m.validateFunctions(Features20191205, nil, nil, nil, nil, maximumFunctionIndex) + err := m.validateFunctions(Features20191205, nil, nil, nil, nil, MaximumFunctionIndex) require.Error(t, err) require.Contains(t, err.Error(), `invalid function[0] export["f1","f2"]: cannot pop the 1st f32`) }) } -func TestModule_validateTables(t *testing.T) { - t.Run("multiple tables", func(t *testing.T) { - m := Module{} - err := m.validateTables(make([]*TableType, 10), nil) - require.Error(t, err) - require.EqualError(t, err, "multiple tables are not supported") - }) - t.Run("table index out of range", func(t *testing.T) { - m := Module{ElementSection: []*ElementSegment{{TableIndex: 1000}}} - err := m.validateTables(make([]*TableType, 1), nil) - require.Error(t, err) - require.EqualError(t, err, "table index out of range") - }) +func TestModule_validateTable(t *testing.T) { t.Run("invalid const expr", func(t *testing.T) { m := Module{ElementSection: []*ElementSegment{{ - TableIndex: 0, OffsetExpr: &ConstantExpression{ Opcode: OpcodeUnreachable, // Invalid! }, }}} - err := m.validateTables(make([]*TableType, 1), nil) + err := m.validateTable(&Table{}, nil) require.Error(t, err) require.EqualError(t, err, "invalid const expression for element: invalid opcode for const expression: 0x0") }) t.Run("ok", func(t *testing.T) { m := Module{ElementSection: []*ElementSegment{{ - TableIndex: 0, OffsetExpr: &ConstantExpression{ Opcode: OpcodeI32Const, Data: []byte{0x1}, }, }}} - err := m.validateTables(make([]*TableType, 1), nil) + err := m.validateTable(&Table{}, nil) require.NoError(t, err) }) } -func TestModule_validateMemories(t *testing.T) { - t.Run("multiple memory", func(t *testing.T) { - m := Module{} - err := m.validateMemories(make([]*LimitsType, 10), nil) - require.Error(t, err) - require.EqualError(t, err, "multiple memories are not supported") - }) +func TestModule_validateMemory(t *testing.T) { t.Run("data section exits but memory not declared", func(t *testing.T) { m := Module{DataSection: make([]*DataSegment, 1)} - err := m.validateMemories(nil, nil) + err := m.validateMemory(nil, nil) require.Error(t, err) require.Contains(t, "unknown memory", err.Error()) }) - t.Run("non zero memory index data", func(t *testing.T) { - m := Module{DataSection: []*DataSegment{{MemoryIndex: 1}}} - err := m.validateMemories(make([]*LimitsType, 1), nil) - require.Error(t, err) - require.EqualError(t, err, "memory index must be zero") - }) t.Run("invalid const expr", func(t *testing.T) { m := Module{DataSection: []*DataSegment{{ - MemoryIndex: 0, OffsetExpression: &ConstantExpression{ Opcode: OpcodeUnreachable, // Invalid! }, }}} - err := m.validateMemories(make([]*LimitsType, 1), nil) + err := m.validateMemory(&Memory{}, nil) require.EqualError(t, err, "calculate offset: invalid opcode for const expression: 0x0") }) t.Run("ok", func(t *testing.T) { m := Module{DataSection: []*DataSegment{{ - MemoryIndex: 0, Init: []byte{0x1}, + Init: []byte{0x1}, OffsetExpression: &ConstantExpression{ Opcode: OpcodeI32Const, Data: []byte{0x1}, }, }}} - err := m.validateMemories(make([]*LimitsType, 1), nil) + err := m.validateMemory(&Memory{}, nil) require.NoError(t, err) }) } @@ -602,7 +562,7 @@ func TestModule_validateImports(t *testing.T) { Module: "m", Name: "n", Type: ExternTypeTable, - DescTable: &TableType{ElemType: 0x70 /* funcref */, Limit: &LimitsType{Min: 1}}, + DescTable: &Table{Min: 1}, }, }, { @@ -612,7 +572,7 @@ func TestModule_validateImports(t *testing.T) { Module: "m", Name: "n", Type: ExternTypeMemory, - DescMem: &MemoryType{Min: 1}, + DescMem: &Memory{Min: 1}, }, }, } { @@ -639,8 +599,8 @@ func TestModule_validateExports(t *testing.T) { exportSection map[string]*Export functions []Index globals []*GlobalType - memories []*MemoryType - tables []*TableType + memory *Memory + table *Table expectedErr string }{ {name: "empty export section", exportSection: map[string]*Export{}}, @@ -687,40 +647,33 @@ func TestModule_validateExports(t *testing.T) { name: "table", enabledFeatures: Features20191205, exportSection: map[string]*Export{"e1": {Type: ExternTypeTable, Index: 0}}, - tables: []*TableType{{}}, + table: &Table{}, }, { name: "table out of range", enabledFeatures: Features20191205, exportSection: map[string]*Export{"e1": {Type: ExternTypeTable, Index: 1}}, - tables: []*TableType{{}}, - expectedErr: `unknown table for export["e1"]`, + table: &Table{}, + expectedErr: `table for export["e1"] out of range`, }, { name: "memory", enabledFeatures: Features20191205, exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 0}}, - memories: []*MemoryType{{}}, + memory: &Memory{}, }, { name: "memory out of range", enabledFeatures: Features20191205, exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 0}}, - expectedErr: `unknown memory for export["e1"]`, - }, - { - name: "second memory unknown", - enabledFeatures: Features20191205, - exportSection: map[string]*Export{"e1": {Type: ExternTypeMemory, Index: 1}}, - // Multiple memories are not valid. - memories: []*MemoryType{{}, {}}, - expectedErr: `unknown memory for export["e1"]`, + table: &Memory{}, + expectedErr: `memory for export["e1"] out of range`, }, } { tc := tc t.Run(tc.name, func(t *testing.T) { m := Module{ExportSection: tc.exportSection} - err := m.validateExports(tc.enabledFeatures, tc.functions, tc.globals, tc.memories, tc.tables) + err := m.validateExports(tc.enabledFeatures, tc.functions, tc.globals, tc.memory, tc.table) if tc.expectedErr != "" { require.EqualError(t, err, tc.expectedErr) } else { @@ -783,14 +736,14 @@ func TestModule_buildFunctionInstances(t *testing.T) { func TestModule_buildMemoryInstance(t *testing.T) { t.Run("nil", func(t *testing.T) { - m := Module{MemorySection: []*MemoryType{}} + m := Module{} mem := m.buildMemory() require.Nil(t, mem) }) t.Run("non-nil", func(t *testing.T) { min := uint32(1) max := uint32(10) - m := Module{MemorySection: []*MemoryType{{Min: min, Max: &max}}} + m := Module{MemorySection: &Memory{Min: min, Max: &max}} mem := m.buildMemory() require.Equal(t, min, mem.Min) require.Equal(t, max, *mem.Max) @@ -798,7 +751,7 @@ func TestModule_buildMemoryInstance(t *testing.T) { } func TestModule_buildTableInstance(t *testing.T) { - m := Module{TableSection: []*TableType{{Limit: &LimitsType{Min: 1}}}} + m := Module{TableSection: &Table{Min: 1}} table := m.buildTable() require.Equal(t, uint32(1), table.Min) } diff --git a/internal/wasm/store.go b/internal/wasm/store.go index db9d3a1c..1bb07729 100644 --- a/internal/wasm/store.go +++ b/internal/wasm/store.go @@ -158,15 +158,15 @@ type ( // TableInstance represents a table instance in a store. // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0 - // - // Note this is fixed to function type until post 20191205 reference type is implemented. TableInstance struct { // Table holds the table elements managed by this table instance. Table []uintptr - Min uint32 - Max *uint32 - // Currently fixed to 0x70 (funcref type). - ElemType byte + + // Min is the minimum (function) elements in this table. + Min uint32 + + // Max if present is the maximum (function) elements in this table, or nil if unbounded. + Max *uint32 } // FunctionTypeID is a uniquely assigned integer for a function type. @@ -533,19 +533,13 @@ func (s *Store) resolveImports(module *Module) ( expected := i.DescTable importedTable = imported.Table - if importedTable.ElemType != expected.ElemType { - err = errorInvalidImport(i, idx, fmt.Errorf("element type mismatch: %s != %s", - ValueTypeName(expected.ElemType), ValueTypeName(importedTable.ElemType))) + if expected.Min > importedTable.Min { + err = errorMinSizeMismatch(i, idx, expected.Min, importedTable.Min) return } - if expected.Limit.Min > importedTable.Min { - err = errorMinSizeMismatch(i, idx, expected.Limit.Min, importedTable.Min) - return - } - - if expected.Limit.Max != nil { - expectedMax := *expected.Limit.Max + if expected.Max != nil { + expectedMax := *expected.Max if importedTable.Max == nil { err = errorNoMax(i, idx, expectedMax) return @@ -610,6 +604,8 @@ func errorInvalidImport(i *Import, idx int, err error) error { return fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, ExternTypeName(i.Type), i.Module, i.Name, err) } +// Global initialization constant expression can only reference the imported globals. +// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 func executeConstExpression(globals []*GlobalInstance, expr *ConstantExpression) (v interface{}) { r := bytes.NewReader(expr.Data) switch expr.Opcode { diff --git a/internal/wasm/store_test.go b/internal/wasm/store_test.go index 4427a5da..23e67973 100644 --- a/internal/wasm/store_test.go +++ b/internal/wasm/store_test.go @@ -27,23 +27,23 @@ func TestModuleInstance_Memory(t *testing.T) { }, { name: "memory not exported", - input: &Module{MemorySection: []*MemoryType{{1, nil}}}, + input: &Module{MemorySection: &Memory{Min: 1}}, }, { name: "memory not exported, one page", - input: &Module{MemorySection: []*MemoryType{{1, nil}}}, + input: &Module{MemorySection: &Memory{Min: 1}}, }, { name: "memory exported, different name", input: &Module{ - MemorySection: []*MemoryType{{1, nil}}, + MemorySection: &Memory{Min: 1}, ExportSection: map[string]*Export{"momory": {Type: ExternTypeMemory, Name: "momory", Index: 0}}, }, }, { name: "memory exported, but zero length", input: &Module{ - MemorySection: []*MemoryType{{0, nil}}, + MemorySection: &Memory{}, ExportSection: map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory", Index: 0}}, }, expected: true, @@ -51,7 +51,7 @@ func TestModuleInstance_Memory(t *testing.T) { { name: "memory exported, one page", input: &Module{ - MemorySection: []*MemoryType{{1, nil}}, + MemorySection: &Memory{Min: 1}, ExportSection: map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory", Index: 0}}, }, expected: true, @@ -60,7 +60,7 @@ func TestModuleInstance_Memory(t *testing.T) { { name: "memory exported, two pages", input: &Module{ - MemorySection: []*MemoryType{{2, nil}}, + MemorySection: &Memory{Min: 2}, ExportSection: map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory", Index: 0}}, }, expected: true, @@ -135,9 +135,9 @@ func TestStore_ReleaseModule(t *testing.T) { _, err := s.Instantiate(&Module{ TypeSection: []*FunctionType{{}}, ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}}, - MemorySection: []*MemoryType{{1, nil}}, + MemorySection: &Memory{Min: 1}, GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}}}, - TableSection: []*TableType{{Limit: &LimitsType{Min: 10}}}, + TableSection: &Table{Min: 10}, }, importingModuleName) require.NoError(t, err) @@ -180,9 +180,9 @@ func TestStore_concurrent(t *testing.T) { TypeSection: []*FunctionType{{}}, FunctionSection: []uint32{0}, CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, - MemorySection: []*MemoryType{{1, nil}}, + MemorySection: &Memory{Min: 1}, GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x1}}}}, - TableSection: []*TableType{{Limit: &LimitsType{Min: 10}}}, + TableSection: &Table{Min: 10}, ImportSection: []*Import{ {Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}, }, @@ -341,7 +341,7 @@ func TestStore_ExportImportedHostFunction(t *testing.T) { _, err = s.Instantiate(&Module{ TypeSection: []*FunctionType{{}}, ImportSection: []*Import{{Type: ExternTypeFunc, Name: "host_fn", DescFunc: 0}}, - MemorySection: []*MemoryType{{1, nil}}, + MemorySection: &Memory{Min: 1}, ExportSection: map[string]*Export{"host.fn": {Type: ExternTypeFunc, Name: "host.fn", Index: 0}}, }, "test") require.NoError(t, err) @@ -406,7 +406,7 @@ func TestFunctionInstance_Call(t *testing.T) { Name: functionName, DescFunc: 0, }}, - MemorySection: []*MemoryType{{1, nil}}, + MemorySection: &Memory{Min: 1}, ExportSection: map[string]*Export{functionName: {Type: ExternTypeFunc, Name: functionName, Index: 0}}, }, "test") require.NoError(t, err) @@ -671,26 +671,17 @@ func TestStore_resolveImports(t *testing.T) { Type: ExternTypeTable, Table: tableInst, }}, Name: moduleName} - _, _, table, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &TableType{Limit: &LimitsType{Max: &max}}}}}) + _, _, table, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &Table{Max: &max}}}}) require.NoError(t, err) require.Equal(t, table, tableInst) require.Equal(t, 1, s.modules[moduleName].dependentCount) }) - t.Run("element type", func(t *testing.T) { - s := newStore() - s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: { - Type: ExternTypeTable, - Table: &TableInstance{ElemType: 0x00}, // Unknown! - }}, Name: moduleName} - _, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: &TableType{ElemType: 0x1}}}}) - require.EqualError(t, err, "import[0] table[test.target]: element type mismatch: unknown != unknown") - }) t.Run("minimum size mismatch", func(t *testing.T) { s := newStore() - importTableType := &TableType{Limit: &LimitsType{Min: 2}} + importTableType := &Table{Min: 2} s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: { Type: ExternTypeTable, - Table: &TableInstance{Min: importTableType.Limit.Min - 1}, + Table: &TableInstance{Min: importTableType.Min - 1}, }}, Name: moduleName} _, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}) require.EqualError(t, err, "import[0] table[test.target]: minimum size mismatch: 2 > 1") @@ -698,10 +689,10 @@ func TestStore_resolveImports(t *testing.T) { t.Run("maximum size mismatch", func(t *testing.T) { s := newStore() max := uint32(10) - importTableType := &TableType{Limit: &LimitsType{Max: &max}} + importTableType := &Table{Max: &max} s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: { Type: ExternTypeTable, - Table: &TableInstance{Min: importTableType.Limit.Min - 1}, + Table: &TableInstance{Min: importTableType.Min - 1}, }}, Name: moduleName} _, _, _, _, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}}) require.EqualError(t, err, "import[0] table[test.target]: maximum size mismatch: 10, but actual has no max") @@ -716,14 +707,14 @@ func TestStore_resolveImports(t *testing.T) { Type: ExternTypeMemory, Memory: memoryInst, }}, Name: moduleName} - _, _, _, memory, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &MemoryType{Max: &max}}}}) + _, _, _, memory, _, err := s.resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: &max}}}}) require.NoError(t, err) require.Equal(t, memory, memoryInst) require.Equal(t, 1, s.modules[moduleName].dependentCount) }) t.Run("minimum size mismatch", func(t *testing.T) { s := newStore() - importMemoryType := &MemoryType{Min: 2} + importMemoryType := &Memory{Min: 2} s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: { Type: ExternTypeMemory, Memory: &MemoryInstance{Min: importMemoryType.Min - 1}, @@ -734,7 +725,7 @@ func TestStore_resolveImports(t *testing.T) { t.Run("maximum size mismatch", func(t *testing.T) { s := newStore() max := uint32(10) - importMemoryType := &MemoryType{Max: &max} + importMemoryType := &Memory{Max: &max} s.modules[moduleName] = &ModuleInstance{Exports: map[string]*ExportInstance{name: { Type: ExternTypeMemory, Memory: &MemoryInstance{}, diff --git a/internal/wasm/table.go b/internal/wasm/table.go new file mode 100644 index 00000000..96c29774 --- /dev/null +++ b/internal/wasm/table.go @@ -0,0 +1,13 @@ +package internalwasm + +func (m *Module) buildTable() *TableInstance { + table := m.TableSection + if table != nil { + return &TableInstance{ + Table: make([]uintptr, table.Min), + Min: table.Min, + Max: table.Max, + } + } + return nil +} diff --git a/internal/wasm/text/decoder.go b/internal/wasm/text/decoder.go index cf9e98b7..516fda02 100644 --- a/internal/wasm/text/decoder.go +++ b/internal/wasm/text/decoder.go @@ -97,7 +97,7 @@ 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, fieldCountMemory uint32 + fieldCountFunc uint32 } // DecodeModule implements internalwasm.DecodeModule for the WebAssembly 1.0 (20191205) Text Format @@ -192,7 +192,7 @@ func (p *moduleParser) beginModuleField(tok tokenType, tokenBytes []byte, _, _ u case wasm.ExternTypeTableName: return nil, fmt.Errorf("TODO: %s", tokenBytes) case wasm.ExternTypeMemoryName: - if p.memoryNamespace.count > 0 { + if p.module.SectionElementCount(wasm.SectionIDMemory) > 0 { return nil, moreThanOneInvalidInSection(wasm.SectionIDMemory) } p.pos = positionMemory @@ -450,11 +450,7 @@ 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 uint32, max *uint32) tokenParser { - p.module.MemorySection = append(p.module.MemorySection, &wasm.MemoryType{Min: min, Max: max}) - - // Multiple memories are allowed, so advance in case there's a next. - p.memoryNamespace.count++ - p.fieldCountMemory++ + p.module.MemorySection = &wasm.Memory{Min: min, Max: max} p.pos = positionModule return p.parseModule } @@ -754,7 +750,7 @@ func (p *moduleParser) errorContext() string { idx := p.fieldCountFunc return fmt.Sprintf("module.%s[%d]%s", wasm.ExternTypeFuncName, idx, p.typeUseParser.errorContext()) case positionMemory: - return fmt.Sprintf("module.%s[%d]", wasm.ExternTypeMemoryName, p.fieldCountMemory) + return fmt.Sprintf("module.%s[0]", wasm.ExternTypeMemoryName) case positionExport, positionExportFunc: // TODO: table, memory or global idx := p.module.SectionElementCount(wasm.SectionIDExport) if p.pos == positionExport { diff --git a/internal/wasm/text/decoder_test.go b/internal/wasm/text/decoder_test.go index 8f060309..74e40cb1 100644 --- a/internal/wasm/text/decoder_test.go +++ b/internal/wasm/text/decoder_test.go @@ -1170,14 +1170,14 @@ func TestDecodeModule(t *testing.T) { name: "memory", input: "(module (memory 1))", expected: &wasm.Module{ - MemorySection: []*wasm.MemoryType{{Min: 1}}, + MemorySection: &wasm.Memory{Min: 1}, }, }, { name: "memory ID", input: "(module (memory $mem 1))", expected: &wasm.Module{ - MemorySection: []*wasm.MemoryType{{Min: 1}}, + MemorySection: &wasm.Memory{Min: 1}, }, }, { @@ -1364,7 +1364,7 @@ func TestDecodeModule(t *testing.T) { (export "foo" (memory 0)) )`, expected: &wasm.Module{ - MemorySection: []*wasm.MemoryType{{Min: 0}}, + MemorySection: &wasm.Memory{Min: 0}, ExportSection: map[string]*wasm.Export{ "foo": {Name: "foo", Type: wasm.ExternTypeMemory, Index: 0}, }, @@ -1377,7 +1377,7 @@ func TestDecodeModule(t *testing.T) { (memory 0) )`, expected: &wasm.Module{ - MemorySection: []*wasm.MemoryType{{Min: 0}}, + MemorySection: &wasm.Memory{Min: 0}, ExportSection: map[string]*wasm.Export{ "foo": {Name: "foo", Type: wasm.ExternTypeMemory, Index: 0}, }, @@ -1409,7 +1409,7 @@ func TestDecodeModule(t *testing.T) { (export "memory" (memory $mem)) )`, expected: &wasm.Module{ - MemorySection: []*wasm.MemoryType{{Min: 1}}, + MemorySection: &wasm.Memory{Min: 1}, ExportSection: map[string]*wasm.Export{ "memory": {Name: "memory", Type: wasm.ExternTypeMemory, Index: 0}, }, diff --git a/internal/wasm/text/memory_parser.go b/internal/wasm/text/memory_parser.go index 47c4a04b..574b5104 100644 --- a/internal/wasm/text/memory_parser.go +++ b/internal/wasm/text/memory_parser.go @@ -13,7 +13,7 @@ func newMemoryParser(memoryNamespace *indexNamespace, onMemory onMemory) *memory type onMemory func(min uint32, max *uint32) tokenParser -// memoryParser parses a wasm.MemoryType from and dispatches to onMemory. +// memoryParser parses a wasm.Memory from and dispatches to onMemory. // // Ex. `(module (memory 0 1024))` // starts here --^ ^ diff --git a/internal/wasm/text/memory_parser_test.go b/internal/wasm/text/memory_parser_test.go index 3fbf421a..10dece5a 100644 --- a/internal/wasm/text/memory_parser_test.go +++ b/internal/wasm/text/memory_parser_test.go @@ -14,44 +14,44 @@ func TestMemoryParser(t *testing.T) { tests := []struct { name string input string - expected *wasm.MemoryType + expected *wasm.Memory expectedID string }{ { name: "min 0", input: "(memory 0)", - expected: &wasm.MemoryType{}, + expected: &wasm.Memory{}, }, { name: "min 0, max 0", input: "(memory 0 0)", - expected: &wasm.MemoryType{Max: &zero}, + expected: &wasm.Memory{Max: &zero}, }, { name: "min largest", input: "(memory 65536)", - expected: &wasm.MemoryType{Min: max}, + expected: &wasm.Memory{Min: max}, }, { name: "min largest - ID", input: "(memory $mem 65536)", - expected: &wasm.MemoryType{Min: max}, + expected: &wasm.Memory{Min: max}, expectedID: "mem", }, { name: "min 0, max largest", input: "(memory 0 65536)", - expected: &wasm.MemoryType{Max: &max}, + expected: &wasm.Memory{Max: &max}, }, { name: "min largest max largest", input: "(memory 65536 65536)", - expected: &wasm.MemoryType{Min: max, Max: &max}, + expected: &wasm.Memory{Min: max, Max: &max}, }, { name: "min largest max largest - ID", input: "(memory $mem 65536 65536)", - expected: &wasm.MemoryType{Min: max, Max: &max}, + expected: &wasm.Memory{Min: max, Max: &max}, expectedID: "mem", }, } @@ -161,10 +161,10 @@ func TestMemoryParser_Errors(t *testing.T) { }) } -func parseMemoryType(memoryNamespace *indexNamespace, input string) (*wasm.MemoryType, *memoryParser, error) { - var parsed *wasm.MemoryType +func parseMemoryType(memoryNamespace *indexNamespace, input string) (*wasm.Memory, *memoryParser, error) { + var parsed *wasm.Memory var setFunc onMemory = func(min uint32, max *uint32) tokenParser { - parsed = &wasm.MemoryType{Min: min, Max: max} + parsed = &wasm.Memory{Min: min, Max: max} return parseErr } tp := newMemoryParser(memoryNamespace, setFunc) diff --git a/tests/spectest/spec_test.go b/tests/spectest/spec_test.go index 8253384a..1c30ab9e 100644 --- a/tests/spectest/spec_test.go +++ b/tests/spectest/spec_test.go @@ -244,11 +244,11 @@ func addSpectestModule(t *testing.T, store *wasm.Store) { Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeF32Const, Data: []byte{0x40, 0x84, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00}}, }, }, - MemorySection: []*wasm.LimitsType{{ + MemorySection: &wasm.Memory{ Min: 1, Max: &memoryLimitMax, - }}, - TableSection: []*wasm.TableType{ - {Limit: &wasm.LimitsType{Min: 10, Max: &tableLimitMax}}, + }, + TableSection: &wasm.Table{ + Min: 10, Max: &tableLimitMax, }, ExportSection: map[string]*wasm.Export{ "print": {Name: "print", Index: 0, Type: wasm.ExternTypeFunc}, diff --git a/vs/bench_fac_iter_test.go b/vs/bench_fac_iter_test.go index cab83c19..3641faaa 100644 --- a/vs/bench_fac_iter_test.go +++ b/vs/bench_fac_iter_test.go @@ -21,7 +21,7 @@ import ( // ensureJITFastest is overridable via ldflags. Ex. // -ldflags '-X github.com/tetratelabs/wazero/vs.ensureJITFastest=true' -var ensureJITFastest string = "false" +var ensureJITFastest = "false" // facWasm is compiled from testdata/fac.wat //go:embed testdata/fac.wasm diff --git a/vs/codec_test.go b/vs/codec_test.go index 21e5200e..1188e447 100644 --- a/vs/codec_test.go +++ b/vs/codec_test.go @@ -59,7 +59,7 @@ func newExample() *wasm.Module { {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeLocalGet, 1, wasm.OpcodeI32Add, wasm.OpcodeEnd}}, {Body: []byte{wasm.OpcodeLocalGet, 0, wasm.OpcodeI64Extend16S, wasm.OpcodeEnd}}, }, - MemorySection: []*wasm.MemoryType{{Min: 1, Max: &three}}, + MemorySection: &wasm.Memory{Min: 1, Max: &three}, ExportSection: map[string]*wasm.Export{ "AddInt": {Name: "AddInt", Type: wasm.ExternTypeFunc, Index: wasm.Index(4)}, "": {Name: "", Type: wasm.ExternTypeFunc, Index: wasm.Index(3)},