Repository: tetratelabs/wazero
Pull request #463 (2022-04-14 03:46:14)

binary: complete encoder
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>

mathetake: I need this to accelerate writing and debugging the random module generator for testing
mathetake: ok 100% now binary.EncodeModule  produces exactly the same binary as the original source module custom sections 🕺 
mathetake: for all the binaries in spectests
mathetake: seems like I need to fix more 😄 

mathetake: ok fixed 🤞 

Patch:
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/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/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/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/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/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/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/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/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/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
 }


Test patch:
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/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/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/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_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_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_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_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_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_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_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,