Repository: tetratelabs/wazero
Pull request #130 (2022-01-13 03:42:06)

wat: adds param name parsing
parameter name parsing is a little tricky due to a number of spec
features:
 * you can choose to only name some parameters and not others
 * abbreviated params cannot be named
 * you can mix named params with abbreviated names
 * override rules are needed when inlined params are named for a type
   index which is also named.

Note: this does not encode the name section because it is currently
unverifiable due to missing features in both wabt and wasm-tools. Next
PR will be a wasm encoder so we can proceed with accurrate benchmarks.


Patch:
diff --git a/wasm/wat/bind.go b/wasm/wat/bind.go
index 831a457b..15556d18 100644
--- a/wasm/wat/bind.go
+++ b/wasm/wat/bind.go
@@ -8,12 +8,12 @@ import (
 func bindIndices(m *module) error {
 	typeToIndex := map[*typeFunc]uint32{}
 	typeNameToIndex := map[string]uint32{}
-	for i, t := range m.typeFuncs {
-		index := uint32(i)
+	for i, t := range m.types {
+		ui := uint32(i)
 		if t.name != "" {
-			typeNameToIndex[t.name] = index
+			typeNameToIndex[t.name] = ui
 		}
-		typeToIndex[t] = index
+		typeToIndex[t] = ui
 	}
 
 	funcNameToIndex, err := bindFunctionTypes(m, typeToIndex, typeNameToIndex)
@@ -37,7 +37,7 @@ func bindIndices(m *module) error {
 //  or (import "Math" "PI" (func (type 32))) exists, but there are only 10 types.
 func bindFunctionTypes(m *module, typeToIndex map[*typeFunc]uint32, typeNameToIndex map[string]uint32) (map[string]uint32, error) {
 	funcNameToIndex := map[string]uint32{}
-	typeCount := uint32(len(m.typeFuncs))
+	typeCount := uint32(len(m.types))
 	for i, f := range m.importFuncs {
 		if f.funcName != "" {
 			funcNameToIndex[f.funcName] = uint32(i)
@@ -45,13 +45,12 @@ func bindFunctionTypes(m *module, typeToIndex map[*typeFunc]uint32, typeNameToIn
 
 		idx := f.typeIndex
 		if idx == nil { // inlined type
-			f.typeIndex = &index{numeric: typeToIndex[f.typeInlined] /* TODO: 0, 0 aren't valid line/col */}
+			ti := f.typeInlined
+			f.typeIndex = &index{numeric: typeToIndex[ti.typeFunc], line: ti.line, col: ti.col}
 			f.typeInlined = nil
 			continue
 		}
 
-		// TODO: inlined types can contain "verification types" basically where typeIndex != nil if there is a type we need
-		// to verify it matches the corresponding index exactly.
 		if idx.ID == "" { // already bound to a numeric index: verify it is in range
 			if err := checkIndexInRange(idx, typeCount, "module.import[%d].func.type", i); err != nil {
 				return nil, err
@@ -60,6 +59,16 @@ func bindFunctionTypes(m *module, typeToIndex map[*typeFunc]uint32, typeNameToIn
 			return nil, err
 		}
 
+		// If there's an inlined type now, it must contain the same signature as the index, and may contain names.
+		if f.typeInlined != nil { // TODO: test me please!
+			realType := m.types[idx.numeric]
+			ti := f.typeInlined
+			if !funcTypeEquals(realType, ti.typeFunc.params, ti.typeFunc.result) {
+				return nil, &FormatError{ti.line, ti.col, fmt.Sprintf("module.import[%d].func.type", i),
+					fmt.Errorf("inlined type doesn't match type index %d", idx.numeric),
+				}
+			}
+		}
 	}
 	return funcNameToIndex, nil
 }
diff --git a/wasm/wat/convert.go b/wasm/wat/convert.go
index b2907fd0..4b358bd7 100644
--- a/wasm/wat/convert.go
+++ b/wasm/wat/convert.go
@@ -21,7 +21,7 @@ func TextToBinary(source []byte) (result *wasm.Module, err error) {
 	// Next, we need to convert the types from the text format into the binary one. This is easy because the only
 	// difference is that the text format has type names and the binary format does not.
 	result = &wasm.Module{}
-	for _, t := range m.typeFuncs {
+	for _, t := range m.types {
 		var results []wasm.ValueType
 		if t.result != 0 {
 			results = []wasm.ValueType{t.result}
diff --git a/wasm/wat/parser.go b/wasm/wat/parser.go
index 3e44f9cd..df72f7a0 100644
--- a/wasm/wat/parser.go
+++ b/wasm/wat/parser.go
@@ -62,7 +62,7 @@ type moduleParser struct {
 	// currentImportIndex allows us to track the relative position of module.importFuncs regardless of position in the source.
 	currentImportIndex uint32
 
-	// currentTypeIndex allows us to track the relative position of module.typeFuncs regardless of position in the source.
+	// currentTypeIndex allows us to track the relative position of module.types regardless of position in the source.
 	currentTypeIndex uint32
 
 	typeParser  *typeParser
@@ -96,7 +96,7 @@ func parseModule(source []byte) (*module, error) {
 	}
 
 	// Add any types implicitly defined from type use. Ex. (module (import (func (param i32)...
-	p.module.typeFuncs = append(p.module.typeFuncs, p.typeParser.inlinedTypes...)
+	p.module.types = append(p.module.types, p.typeParser.inlinedTypes...)
 
 	// Ensure indices only point to numeric values
 	if err = bindIndices(p.module); err != nil {
@@ -255,16 +255,18 @@ func (p *moduleParser) parseTypeName(tok tokenType, tokenBytes []byte, line, col
 //                      errs here --^
 func (p *moduleParser) parseType(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	switch tok {
+	case tokenID:
+		return errors.New("redundant name")
 	case tokenLParen: // start fields, ex. (func
 		// Err if there's a second func. Ex. (type (func) (func))
-		if uint32(len(p.module.typeFuncs)) > p.currentTypeIndex {
+		if uint32(len(p.module.types)) > p.currentTypeIndex {
 			return unexpectedToken(tok, tokenBytes)
 		}
 		p.tokenParser = p.beginField
 		return nil
 	case tokenRParen: // end of this type
 		// Err if we never reached a description...
-		if uint32(len(p.module.typeFuncs)) == p.currentTypeIndex {
+		if uint32(len(p.module.types)) == p.currentTypeIndex {
 			return errors.New("missing func field")
 		}
 
@@ -299,7 +301,11 @@ func (p *moduleParser) parseTypeFunc(tok tokenType, tokenBytes []byte, line, col
 // the token is tokenRParen and sets the next parser to parseType on tokenRParen.
 func (p *moduleParser) parseTypeFuncEnd(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	if tok == tokenRParen {
-		p.module.typeFuncs = append(p.module.typeFuncs, p.typeParser.getType(string(p.currentValue0)))
+		sig, names := p.typeParser.getType(string(p.currentValue0))
+		if names != nil {
+			p.module.typeParamNames = append(p.module.typeParamNames, &typeParamNames{uint32(len(p.module.types)), names})
+		}
+		p.module.types = append(p.module.types, sig)
 		p.currentValue0 = nil
 		p.currentField = fieldModuleType
 		p.tokenParser = p.parseType
@@ -436,7 +442,7 @@ func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, line, c
 func (p *moduleParser) parseImportFuncEnd(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	if tok == tokenRParen {
 		fn := p.module.importFuncs[len(p.module.importFuncs)-1]
-		fn.typeIndex, fn.typeInlined = p.typeParser.getTypeUse()
+		fn.typeIndex, fn.typeInlined, fn.paramNames = p.typeParser.getTypeUse()
 		p.currentField = fieldModuleImport
 		p.tokenParser = p.parseImport
 		return nil
diff --git a/wasm/wat/type_parser.go b/wasm/wat/type_parser.go
index 575be401..f49ce01d 100644
--- a/wasm/wat/type_parser.go
+++ b/wasm/wat/type_parser.go
@@ -1,7 +1,6 @@
 package wat
 
 import (
-	"bytes"
 	"errors"
 	"fmt"
 
@@ -68,15 +67,32 @@ type typeParser struct {
 	// See https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A2
 	currentParams []wasm.ValueType
 
-	// currentParamIndex is used to give an appropriate errorContext
-	currentParamIndex uint32
+	// currentParamNames accumulates any typeFunc.params for the current type. Ex. $x names (param $x i32)
+	//
+	// Note: names can be missing because they were never assigned, ex. (param i32), or due to abbreviated format which
+	// does not support names. Ex. (param i32 i32)
+	// See https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A2
+	currentParamNames paramNames
+
+	// currentParamField is a field index and used to give an appropriate errorContext. Due to abbreviation it may be
+	// unrelated to the length of currentParams
+	currentParamField uint32
+
+	// currentParamName is the name of the currentParamField, added on tokenRParen to currentParamNames
+	currentParamName []byte
 
-	// foundParam allows us to check if we found a type in a "param" field. We can't use currentParamIndex because when
-	// parameters are abbreviated, ex. (param i32 i32), the currentParamIndex will be less than the type count.
+	// foundParam allows us to check if we found a type in a "param" field. We can't use currentParamField because when
+	// parameters are abbreviated, ex. (param i32 i32), the currentParamField will be less than the type count.
 	foundParam bool
 
 	// currentResult is zero until set, and only set once as WebAssembly 1.0 only supports up to one result.
 	currentResult wasm.ValueType
+
+	// currentTypeUseStartLine tracks the start column of a type use in case there's an error later
+	currentTypeUseStartLine uint32
+
+	// currentTypeUseStartCol tracks the start column of a type use in case there's an error later
+	currentTypeUseStartCol uint32
 }
 
 // beginTypeUse sets the next parser to beginTypeParamOrResult. reset must be called prior to this.
@@ -99,10 +115,16 @@ func (p *typeParser) beginTypeUse(onTypeEnd tokenParser) {
 // (tokenKeyword).
 func (p *typeParser) beginTypeParamOrResult(tok tokenType, tokenBytes []byte, line, col uint32) error {
 	if tok == tokenKeyword && string(tokenBytes) == "type" {
+		// If we see a "type" field, there's a chance there's an inlined type following it. We record the position, as
+		// we can't verify the signature until all types are read. If there's a signature mismatch later, we need to
+		// know where in the source it was wrong!
+		p.currentTypeUseStartLine = line
+		p.currentTypeUseStartCol = col
 		p.state = parsingType
 		p.m.tokenParser = p.m.indexParser.beginParsingIndex(p.parseTypeIndexEnd)
 		return nil
 	}
+	p.state = parsingParamOrResult
 	return p.beginParamOrResult(tok, tokenBytes, line, col)
 }
 
@@ -124,7 +146,8 @@ func (p *typeParser) beginType(onTypeEnd tokenParser) {
 func (p *typeParser) reset() {
 	p.currentTypeIndex = nil
 	p.currentParams = nil
-	p.currentParamIndex = 0
+	p.currentParamNames = nil
+	p.currentParamField = 0
 	p.currentResult = 0
 }
 
@@ -142,7 +165,8 @@ func (p *typeParser) beginParamOrResult(tok tokenType, tokenBytes []byte, line,
 		case "param":
 			p.state = parsingParam
 			p.foundParam = false
-			p.m.tokenParser = p.parseParam
+			p.currentParamName = nil
+			p.m.tokenParser = p.parseParamName
 		case "result":
 			p.state = parsingResult
 			p.m.tokenParser = p.parseResult
@@ -165,15 +189,49 @@ func (p *typeParser) parseMoreParamsOrResult(tok tokenType, tokenBytes []byte, l
 	return p.onTypeEnd(tok, tokenBytes, line, col)
 }
 
+// parseParamName is the first parser inside a param field. This records the name if present or calls parseParam if not
+// found.
+//
+// Ex. A param name is present `(param $x i32)`
+//                        records $x --^  ^
+//              parseParam resumes here --+
+//
+// Ex. No param name `(param i32)`
+//        calls parseParam --^
+func (p *typeParser) parseParamName(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	if tok == tokenID { // Ex. $len
+		p.currentParamName = tokenBytes
+		p.m.tokenParser = p.parseParam
+		return nil
+	}
+	return p.parseParam(tok, tokenBytes, line, col)
+}
+
+// parseParam is the last parser inside the param field. This records value type and continues if it is an abbreviated
+// form with multiple value types. When complete, this sets the next parser to parseMoreParamsOrResult.
+//
+// Ex. One param type is present `(param i32)`
+//                         records i32 --^  ^
+//   parseMoreParamsOrResult resumes here --+
+//
+// Ex. One param type is present `(param i32)`
+//                         records i32 --^  ^
+//   parseMoreParamsOrResult resumes here --+
+//
+// Ex. type is missing `(param)`
+//                errs here --^
 func (p *typeParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	switch tok {
 	case tokenID: // Ex. $len
-		return errors.New("TODO param name ex (param $len i32), but not in abbreviation ex (param $x i32 $y i32)")
+		return errors.New("redundant name")
 	case tokenKeyword: // Ex. i32
 		vt, err := parseValueType(tokenBytes)
 		if err != nil {
 			return err
 		}
+		if p.foundParam && p.currentParamName != nil {
+			return errors.New("cannot name parameters in abbreviated form")
+		}
 		p.currentParams = append(p.currentParams, vt)
 		p.foundParam = true
 	case tokenRParen: // end of this field
@@ -181,8 +239,15 @@ func (p *typeParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) e
 			return errors.New("expected a type")
 		}
 
+		// Note: currentParamField is the index of the param field, but due to mixing and matching of abbreviated params
+		// it can be less than the param index. Ex. (param i32 i32) (param $v i32) is param field 2, but the 3rd param.
+		if p.currentParamName != nil {
+			nameIndex := &paramNameIndex{uint32(len(p.currentParams) - 1), p.currentParamName}
+			p.currentParamNames = append(p.currentParamNames, nameIndex)
+		}
+
 		// since multiple param fields are valid, ex `(func (param i32) (param i64))`, prepare for any next.
-		p.currentParamIndex++
+		p.currentParamField++
 		p.state = parsingParamOrResult
 		p.m.tokenParser = p.parseMoreParamsOrResult
 	default:
@@ -219,7 +284,7 @@ func (p *typeParser) parseResult(tok tokenType, tokenBytes []byte, _, _ uint32)
 func (p *typeParser) errorContext() string {
 	switch p.state {
 	case parsingParam:
-		return fmt.Sprintf(".param[%d]", p.currentParamIndex)
+		return fmt.Sprintf(".param[%d]", p.currentParamField)
 	case parsingResult:
 		return ".result"
 	case parsingType:
@@ -230,48 +295,56 @@ func (p *typeParser) errorContext() string {
 
 var typeFuncEmpty = &typeFunc{}
 
-// getTypeUse finalizes any current params or result and returns the current typeIndex and/or type
-func (p *typeParser) getTypeUse() (typeIndex *index, sig *typeFunc) {
+// getTypeUse finalizes any current params or result and returns the current typeIndex and/or type. paramNames are only
+// returned if defined inline.
+func (p *typeParser) getTypeUse() (typeIndex *index, inlined *inlinedTypeFunc, paramNames paramNames) {
 	typeIndex = p.currentTypeIndex
+	paramNames = p.currentParamNames
 
 	// Don't conflate lack of verification type with nullary
-	if typeIndex != nil && p.currentEqualsType(typeFuncEmpty) {
+	if typeIndex != nil && funcTypeEquals(typeFuncEmpty, p.currentParams, p.currentResult) {
 		return
 	}
 
 	// Search for an existing signature that matches the current type in the module types.
-	for _, t := range p.m.module.typeFuncs {
-		if p.currentEqualsType(t) {
-			sig = t
+	for _, t := range p.m.module.types {
+		if funcTypeEquals(t, p.currentParams, p.currentResult) {
+			inlined = &inlinedTypeFunc{t, p.currentTypeUseStartLine, p.currentTypeUseStartCol}
 			return
 		}
 	}
 
 	// Search for an existing signature that matches the current type in the pending inlined types
 	for _, t := range p.inlinedTypes {
-		if p.currentEqualsType(t) {
-			sig = t
+		if funcTypeEquals(t, p.currentParams, p.currentResult) {
+			inlined = &inlinedTypeFunc{t, p.currentTypeUseStartLine, p.currentTypeUseStartCol}
 			return
 		}
 	}
 
-	sig = &typeFunc{"", p.currentParams, p.currentResult}
+	inlined = &inlinedTypeFunc{
+		typeFunc: &typeFunc{"", p.currentParams, p.currentResult},
+		line:     p.currentTypeUseStartLine,
+		col:      p.currentTypeUseStartCol,
+	}
 
 	// If we didn't find a match, we need to insert an inlined type to use it. We don't do this when there is a type
 	// index because in this case, the signature is only used for verification on an existing type.
 	if typeIndex == nil {
-		p.inlinedTypes = append(p.inlinedTypes, sig)
+		p.inlinedTypes = append(p.inlinedTypes, inlined.typeFunc)
 	}
 	return
 }
 
-// getType finalizes any current params or result and returns the current type.
+// getType finalizes any current params or result and returns the current type and any paramNames for it.
 //
 // If the current type is in typeParser.inlinedTypes, it is removed prior to returning.
-func (p *typeParser) getType(typeName string) (sig *typeFunc) {
+func (p *typeParser) getType(typeName string) (sig *typeFunc, paramNames paramNames) {
+	paramNames = p.currentParamNames
+
 	// Search inlined types in case a matching type was found after its type use.
 	for i, t := range p.inlinedTypes {
-		if p.currentEqualsType(t) {
+		if funcTypeEquals(t, p.currentParams, p.currentResult) {
 			// If we got here, we found a type field after a type use. This means it wasn't an inlined type, rather an
 			// out-of-order type. Hence, remove it from the inlined types and add it to the module types.
 			p.inlinedTypes = append(p.inlinedTypes[:i], p.inlinedTypes[i+1:]...)
@@ -281,17 +354,13 @@ func (p *typeParser) getType(typeName string) (sig *typeFunc) {
 	}
 
 	// While inlined types are supposed to re-use an existing type index, there's no no unique constraint on explicitly
-	// defined module types. This means a duplicate type is not a bug: we don't check module.typeFuncs first.
+	// defined module types. This means a duplicate type is not a bug: we don't check module.types first.
 	if sig == nil {
 		sig = &typeFunc{typeName, p.currentParams, p.currentResult}
 	}
 	return
 }
 
-func (p *typeParser) currentEqualsType(t *typeFunc) bool {
-	return bytes.Equal(p.currentParams, t.params) && p.currentResult == t.result
-}
-
 func parseValueType(tokenBytes []byte) (wasm.ValueType, error) {
 	t := string(tokenBytes)
 	switch t {
diff --git a/wasm/wat/types.go b/wasm/wat/types.go
index aafa7e04..a90cb1b9 100644
--- a/wasm/wat/types.go
+++ b/wasm/wat/types.go
@@ -1,6 +1,7 @@
 package wat
 
 import (
+	"bytes"
 	"fmt"
 
 	"github.com/tetratelabs/wazero/wasm"
@@ -26,7 +27,10 @@ type module struct {
 	// In the future, other types may be introduced to support features such as module linking.
 	//
 	// See https://www.w3.org/TR/wasm-core-1/#types%E2%91%A0%E2%91%A0
-	typeFuncs []*typeFunc
+	types []*typeFunc
+
+	// typeParamNames include any parameter names for the corresponding index of types.
+	typeParamNames []*typeParamNames
 
 	// importFuncs are imports describing functions added in insertion order. Ex (import... (func...))
 	importFuncs []*importFunc
@@ -38,6 +42,22 @@ type module struct {
 	startFunction *index
 }
 
+// typeParamNames include any parameter names for the corresponding index of module.types.
+type typeParamNames struct {
+	index      uint32
+	paramNames paramNames
+}
+
+type inlinedTypeFunc struct {
+	typeFunc *typeFunc
+
+	// line is the line in the source where the typeFunc was defined.
+	line uint32
+
+	// col is the column on the line where the typeFunc was defined.
+	col uint32
+}
+
 // index is symbolic ID, such as "$main", or its equivalent numeric value, such as "2".
 //
 // Note: line and col used for lazy validation of index. These are attached to an error if later found to be invalid
@@ -71,10 +91,10 @@ type index struct {
 //
 // See https://www.w3.org/TR/wasm-core-1/#text-functype
 type typeFunc struct {
-	// name starts with '$'. For example, "$v_v", and only set when explicitly defined in module.typeFuncs
+	// name starts with '$'. For example, "$v_v", and only set when explicitly defined in module.types
 	//
 	// name is only used for debugging. At runtime, types are called based on raw numeric index. The type index space
-	// begins those explicitly defined in module.typeFuncs, followed by any inlined ones.
+	// begins those explicitly defined in module.types, followed by any inlined ones.
 	name string // TODO: presumably, this must be unique as it is a symbolic identifier?
 
 	// params are the possibly empty sequence of value types accepted by a function with this signature.
@@ -90,6 +110,28 @@ type typeFunc struct {
 	result wasm.ValueType
 }
 
+// funcTypeEquals allows you to compare signatures ignoring names
+func funcTypeEquals(t *typeFunc, params []wasm.ValueType, result wasm.ValueType) bool {
+	return bytes.Equal(t.params, params) && t.result == result
+}
+
+// paramNames are the possibly empty association of names that correspond with params. The index is to params and
+// the name will never be empty and always begin with '$' (ex. "$len") (tokenID). Ex. $x names (param $x i32)
+//
+// paramNames are only used for debugging. At runtime, parameters are called based on raw numeric index.
+//
+// Note: paramNames may be stored in the wasm.Module CustomSection under the key "name" subsection 2 (locals). For
+// example, `wat2wasm --debug-names` will do this.
+// See https://www.w3.org/TR/wasm-core-1/#binary-namesec
+type paramNames []*paramNameIndex // type is only to re-use documentation
+
+type paramNameIndex struct {
+	// index is in currentParams, not necessarily related to currentParamField due to abbreviated format.
+	index uint32
+	// name is the tokenID of the param field.
+	name []byte
+}
+
 // importFunc corresponds to the text format of a WebAssembly function import.
 //
 // Note: nothing is required per specification. Ex `(import "" "" (func))` is valid!
@@ -99,17 +141,20 @@ type importFunc struct {
 	// importIndex is the zero-based index in module.imports. This is needed because imports are not always functions.
 	importIndex uint32
 
-	// typeIndex is the optional index in module.typeFuncs for the function signature. If index.ID is set, it must match
+	// typeIndex is the optional index in module.types for the function signature. If index.ID is set, it must match
 	// typeFunc.name.
 	//
 	// See https://www.w3.org/TR/wasm-core-1/#text-typeuse
 	typeIndex *index
 
 	// typeInlined is set if there are any "param" or "result" fields. When set and typeIndex is also set, the signature
-	// in module.typeFuncs must exist and match this.
+	// in module.types must exist and match this.
 	//
 	// See https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A6
-	typeInlined *typeFunc
+	typeInlined *inlinedTypeFunc
+
+	// paramNames include any parameter names for typeInlined.
+	paramNames paramNames
 
 	// module is the possibly empty module name to import. Ex. "" or "Math"
 	//


Test patch:
diff --git a/wasm/wat/bind_test.go b/wasm/wat/bind_test.go
index 02b66e87..050ce1a9 100644
--- a/wasm/wat/bind_test.go
+++ b/wasm/wat/bind_test.go
@@ -21,14 +21,14 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "import function: inlined type to numeric index",
 			input: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
-						typeInlined: paramI32I32I32I32ResultI32},
+						typeInlined: &inlinedTypeFunc{paramI32I32I32I32ResultI32, 0, 0}},
 				},
 			},
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
 						typeIndex: indexZero},
@@ -38,16 +38,16 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "import function: multiple inlined types to numeric indices",
 			input: &module{
-				typeFuncs: []*typeFunc{paramI32I32ResultI32, paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32ResultI32, paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
-						typeInlined: paramI32I32ResultI32},
+						typeInlined: &inlinedTypeFunc{paramI32I32ResultI32, 0, 0}},
 					{importIndex: 1, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
-						typeInlined: paramI32I32I32I32ResultI32},
+						typeInlined: &inlinedTypeFunc{paramI32I32I32I32ResultI32, 0, 0}},
 				},
 			},
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32ResultI32, paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32ResultI32, paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
 						typeIndex: indexZero},
@@ -59,16 +59,16 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "import function: multiple inlined types to same numeric index",
 			input: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get",
-						typeInlined: paramI32I32ResultI32},
+						typeInlined: &inlinedTypeFunc{paramI32I32ResultI32, 0, 0}},
 					{importIndex: 1, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
-						typeInlined: paramI32I32ResultI32},
+						typeInlined: &inlinedTypeFunc{paramI32I32ResultI32, 0, 0}},
 				},
 			},
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get",
 						typeIndex: indexOne},
@@ -80,7 +80,7 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "import function: multiple type names to numeric indices",
 			input: &module{
-				typeFuncs: []*typeFunc{
+				types: []*typeFunc{
 					typeFuncEmpty,
 					{name: "$i32i32_i32", params: []wasm.ValueType{i32, i32}, result: i32},
 					{name: "$i32i32i32i32_i32", params: []wasm.ValueType{i32, i32, i32, i32}, result: i32},
@@ -93,7 +93,7 @@ func TestBindIndices(t *testing.T) {
 				},
 			},
 			expected: &module{
-				typeFuncs: []*typeFunc{
+				types: []*typeFunc{
 					typeFuncEmpty,
 					{name: "$i32i32_i32", params: []wasm.ValueType{i32, i32}, result: i32},
 					{name: "$i32i32i32i32_i32", params: []wasm.ValueType{i32, i32, i32, i32}, result: i32},
@@ -109,7 +109,7 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "import function: multiple type numeric indices left alone",
 			input: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32, paramI32I32I32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32, paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
 						typeIndex: &index{numeric: 1, line: 5, col: 86}},
@@ -118,7 +118,7 @@ func TestBindIndices(t *testing.T) {
 				},
 			},
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32, paramI32I32I32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32, paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
 						typeIndex: &index{numeric: 1, line: 5, col: 86}},
@@ -130,7 +130,7 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "start: imported function name to numeric index",
 			input: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty},
+				types: []*typeFunc{typeFuncEmpty},
 				importFuncs: []*importFunc{
 					{funcName: "$one", typeIndex: indexZero},
 					{funcName: "$two", typeIndex: indexZero},
@@ -138,7 +138,7 @@ func TestBindIndices(t *testing.T) {
 				startFunction: &index{ID: "$two", line: 3, col: 9},
 			},
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty},
+				types: []*typeFunc{typeFuncEmpty},
 				importFuncs: []*importFunc{
 					{funcName: "$one", typeIndex: indexZero},
 					{funcName: "$two", typeIndex: indexZero},
@@ -149,12 +149,12 @@ func TestBindIndices(t *testing.T) {
 		{
 			name: "start: imported function numeric index left alone",
 			input: &module{
-				typeFuncs:     []*typeFunc{typeFuncEmpty},
+				types:         []*typeFunc{typeFuncEmpty},
 				importFuncs:   []*importFunc{{name: "hello", importIndex: 0, typeIndex: indexZero}},
 				startFunction: &index{numeric: 0, line: 3, col: 9},
 			},
 			expected: &module{
-				typeFuncs:     []*typeFunc{typeFuncEmpty},
+				types:         []*typeFunc{typeFuncEmpty},
 				importFuncs:   []*importFunc{{name: "hello", importIndex: 0, typeIndex: indexZero}},
 				startFunction: &index{numeric: 0, line: 3, col: 9},
 			},
@@ -183,7 +183,7 @@ func TestBindIndices_Errors(t *testing.T) {
 		{
 			name: "function type points out of range",
 			input: &module{
-				typeFuncs:   []*typeFunc{typeFuncEmpty},
+				types:       []*typeFunc{typeFuncEmpty},
 				importFuncs: []*importFunc{{name: "hello", typeIndex: &index{numeric: 1, line: 3, col: 9}}},
 			},
 			expectedErr: "3:9: index 1 is out of range [0..0] in module.import[0].func.type",
@@ -191,7 +191,7 @@ func TestBindIndices_Errors(t *testing.T) {
 		{
 			name: "function type points nowhere",
 			input: &module{
-				typeFuncs:   []*typeFunc{typeFuncEmpty},
+				types:       []*typeFunc{typeFuncEmpty},
 				importFuncs: []*importFunc{{name: "hello", typeIndex: &index{ID: "$main", line: 3, col: 9}}},
 			},
 			expectedErr: "3:9: unknown ID $main in module.import[0].func.type",
@@ -199,7 +199,7 @@ func TestBindIndices_Errors(t *testing.T) {
 		{
 			name: "start points out of range",
 			input: &module{
-				typeFuncs:     []*typeFunc{typeFuncEmpty},
+				types:         []*typeFunc{typeFuncEmpty},
 				importFuncs:   []*importFunc{{name: "hello", importIndex: 0, typeIndex: indexZero}},
 				startFunction: &index{numeric: 1, line: 3, col: 9},
 			},
diff --git a/wasm/wat/convert_test.go b/wasm/wat/convert_test.go
index 6baad809..e1e33c19 100644
--- a/wasm/wat/convert_test.go
+++ b/wasm/wat/convert_test.go
@@ -12,8 +12,8 @@ import (
 )
 
 func TestTextToBinary(t *testing.T) {
-	zero, two := uint32(0), uint32(2)
-	i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
+	zero, four := uint32(0), uint32(4)
+	f32, i32, i64 := wasm.ValueTypeF32, wasm.ValueTypeI32, wasm.ValueTypeI64
 	tests := []struct {
 		name     string
 		input    string
@@ -144,6 +144,7 @@ func TestTextToBinary(t *testing.T) {
 					{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32}},
 					{},
 					{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
+					{Params: []wasm.ValueType{f32, f32}, Results: []wasm.ValueType{f32}},
 				},
 				ImportSection: []*wasm.ImportSegment{
 					{
@@ -158,6 +159,18 @@ func TestTextToBinary(t *testing.T) {
 							Kind:          wasm.ImportKindFunction,
 							FuncTypeIndex: 2,
 						},
+					}, {
+						Module: "Math", Name: "Mul",
+						Desc: &wasm.ImportDesc{
+							Kind:          wasm.ImportKindFunction,
+							FuncTypeIndex: 3,
+						},
+					}, {
+						Module: "Math", Name: "Add",
+						Desc: &wasm.ImportDesc{
+							Kind:          wasm.ImportKindFunction,
+							FuncTypeIndex: 0,
+						},
 					}, {
 						Module: "", Name: "hello",
 						Desc: &wasm.ImportDesc{
@@ -166,7 +179,7 @@ func TestTextToBinary(t *testing.T) {
 						},
 					},
 				},
-				StartSection: &two,
+				StartSection: &four,
 			},
 		},
 	}
diff --git a/wasm/wat/names_test.go b/wasm/wat/names_test.go
index 3f2673f0..9baf8b69 100644
--- a/wasm/wat/names_test.go
+++ b/wasm/wat/names_test.go
@@ -39,7 +39,7 @@ func TestEncodeNameSection_OnlyFuncName(t *testing.T) {
 	type1 := &typeFunc{params: []wasm.ValueType{i32, i32}, result: i32}
 	type2 := &typeFunc{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32}
 	m := &module{
-		typeFuncs: []*typeFunc{type1, type2},
+		types: []*typeFunc{type1, type2},
 		importFuncs: []*importFunc{
 			{importIndex: 0, module: "wasi_snapshot_preview1", name: "args_sizes_get", funcName: "$" + func0,
 				typeIndex: &index{ /* TODO 0*/ }},
diff --git a/wasm/wat/parser_test.go b/wasm/wat/parser_test.go
index bd60332c..33b96caa 100644
--- a/wasm/wat/parser_test.go
+++ b/wasm/wat/parser_test.go
@@ -11,20 +11,25 @@ import (
 // example has a work-in-progress of supported functionality, used primarily for benchmarking. This includes:
 // * module and function names
 // * explicit, and inlined type definitions (including anonymous)
+// * inlined and overridden type parameter names
 // * start function
 //
-// NOTE: this is different from exampleWat because the parser doesn't yet support all features
+// Note: This is different from exampleWat because the parser doesn't yet support all features.
+// Note: When changing this, regenerate example.wasm like `wat2wasm --debug-names --debug-parser -v example.wat`
+// TODO: this has drifted because neither wabt, nor wasm-tools properly encodes the name section.
 var example = []byte(`(module $example
 	(type $i32i32_i32 (func (param i32 i32) (result i32)))
 	(import "wasi_snapshot_preview1" "arg_sizes_get" (func $runtime.arg_sizes_get (type $i32i32_i32)))
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
+	(import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32)))
+	(import "Math" "Add" (func $add (type $i32i32_i32) (param $l i32) (param $r i32) (result i32)))
 	(type (func))
 	(import "" "hello" (func $hello (type 1)))
 	(start $hello)
 )`)
 
 func TestParseModule(t *testing.T) {
-	i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
+	f32, i32, i64 := wasm.ValueTypeF32, wasm.ValueTypeI32, wasm.ValueTypeI64
 	paramI32 := &typeFunc{params: []wasm.ValueType{i32}}
 	paramI32I32ResultI32 := &typeFunc{params: []wasm.ValueType{i32, i32}, result: i32}
 	paramI32I32I32I32ResultI32 := &typeFunc{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32}
@@ -51,23 +56,86 @@ func TestParseModule(t *testing.T) {
 			expected: &module{name: "$tools"},
 		},
 		{
-			name:  "type one empty",
+			name:  "type func one empty",
 			input: "(module (type $i32i32_i32 (func (param i32 i32) (result i32))) (type (func)))",
 			expected: &module{
-				typeFuncs: []*typeFunc{
+				types: []*typeFunc{
 					{name: "$i32i32_i32", params: []wasm.ValueType{i32, i32}, result: i32},
 					typeFuncEmpty,
 				},
 			},
 		},
 		{
-			name: "type empty after inlined", // ensures the parser was reset properly
+			name:  "type func param names",
+			input: "(module (type $mul (func (param $x f32) (param $y f32) (result f32))))",
+			expected: &module{
+				types: []*typeFunc{
+					{name: "$mul", params: []wasm.ValueType{f32, f32}, result: f32},
+				},
+				typeParamNames: []*typeParamNames{{
+					index: uint32(0),
+					paramNames: paramNames{
+						&paramNameIndex{uint32(0), []byte{'$', 'x'}},
+						&paramNameIndex{uint32(1), []byte{'$', 'y'}},
+					},
+				}},
+			},
+		},
+		{
+			name:  "type func mixed param names", // Checks paramNameIndex when there are less param fields than params
+			input: "(module (type (func (param i32 i32) (param $v i32) (param i64) (param $t f32))))",
+			expected: &module{
+				types: []*typeFunc{
+					{params: []wasm.ValueType{i32, i32, i32, i64, f32}},
+				},
+				typeParamNames: []*typeParamNames{{
+					index: uint32(0),
+					paramNames: paramNames{
+						&paramNameIndex{uint32(2), []byte{'$', 'v'}},
+						&paramNameIndex{uint32(4), []byte{'$', 't'}},
+					},
+				}},
+			},
+		},
+		{
+			name: "type funcs same param types different names",
+			input: `(module
+	(type $mul (func (param $x f32) (param $y f32) (result f32)))
+	(type (func) (; here to ensure sparse indexes work ;))
+	(type $add (func (param $l f32) (param $r f32) (result f32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{
+					{name: "$mul", params: []wasm.ValueType{f32, f32}, result: f32},
+					typeFuncEmpty,
+					{name: "$add", params: []wasm.ValueType{f32, f32}, result: f32},
+				},
+				typeParamNames: []*typeParamNames{
+					{
+						index: uint32(0),
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'x'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'y'}},
+						},
+					},
+					{
+						index: uint32(2),
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'l'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'r'}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "type func empty after inlined", // ensures the parser was reset properly
 			input: `(module
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
 	(type (func))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{
+				types: []*typeFunc{
 					typeFuncEmpty, // module types are always before inlined types
 					paramI32I32I32I32ResultI32,
 				},
@@ -81,10 +149,47 @@ func TestParseModule(t *testing.T) {
 			name:  "import func empty",
 			input: "(module (import \"foo\" \"bar\" (func)))", // ok empty sig
 			expected: &module{
-				typeFuncs:   []*typeFunc{typeFuncEmpty},
+				types:       []*typeFunc{typeFuncEmpty},
+				importFuncs: []*importFunc{{module: "foo", name: "bar", typeIndex: indexZero}},
+			},
+		},
+		{
+			name: "import func redundant",
+			input: `(module
+	(type (func))
+	(import "foo" "bar" (func))
+)`,
+			expected: &module{
+				types:       []*typeFunc{typeFuncEmpty},
+				importFuncs: []*importFunc{{module: "foo", name: "bar", typeIndex: indexZero}},
+			},
+		},
+		{
+			name: "import func redundant - late",
+			input: `(module
+	(import "foo" "bar" (func))
+	(type (func))
+)`,
+			expected: &module{
+				types:       []*typeFunc{typeFuncEmpty},
 				importFuncs: []*importFunc{{module: "foo", name: "bar", typeIndex: indexZero}},
 			},
 		},
+		{
+			name: "import func redundant - two late", // pun intended
+			input: `(module
+	(import "foo" "bar" (func))
+	(import "baz" "qux" (func))
+	(type (func))
+)`,
+			expected: &module{
+				types: []*typeFunc{typeFuncEmpty},
+				importFuncs: []*importFunc{
+					{importIndex: 0, module: "foo", name: "bar", typeIndex: indexZero},
+					{importIndex: 1, module: "baz", name: "qux", typeIndex: indexZero},
+				},
+			},
+		},
 		{
 			name: "import func empty after non-empty", // ensures the parser was reset properly
 			input: `(module
@@ -92,7 +197,7 @@ func TestParseModule(t *testing.T) {
 	(import "foo" "bar" (func))
 )`,
 			expected: &module{
-				typeFuncs:   []*typeFunc{paramI32I32I32I32ResultI32, typeFuncEmpty},
+				types:       []*typeFunc{paramI32I32I32I32ResultI32, typeFuncEmpty},
 				importFuncs: []*importFunc{{module: "foo", name: "bar", typeIndex: indexOne}},
 			},
 		},
@@ -100,7 +205,7 @@ func TestParseModule(t *testing.T) {
 			name:  "import func empty twice",
 			input: "(module (import \"foo\" \"bar\" (func)) (import \"baz\" \"qux\" (func)))", // ok empty sig
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty},
+				types: []*typeFunc{typeFuncEmpty},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "foo", name: "bar", typeIndex: indexZero},
 					{importIndex: 1, module: "baz", name: "qux", typeIndex: indexZero},
@@ -113,7 +218,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32) (param i32) (param i32) (param i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
 						typeIndex: indexZero},
@@ -126,7 +231,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
 						typeIndex: indexZero},
@@ -141,7 +246,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32) (param i32 i32) (param i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
 						typeIndex: indexZero},
@@ -154,7 +259,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "proc_exit" (func $runtime.proc_exit (param i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32},
+				types: []*typeFunc{paramI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "proc_exit", funcName: "$runtime.proc_exit",
 						typeIndex: indexZero},
@@ -165,7 +270,7 @@ func TestParseModule(t *testing.T) {
 			name:  "import func inlined type no param",
 			input: `(module (import "" "" (func (result i32))))`,
 			expected: &module{
-				typeFuncs:   []*typeFunc{resultI32},
+				types:       []*typeFunc{resultI32},
 				importFuncs: []*importFunc{{typeIndex: indexZero}},
 			},
 		},
@@ -175,7 +280,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "path_open" (func $runtime.path_open (param i32) (param i32) (param i32) (param i32) (param i32) (param i64) (param i64) (param i32) (param i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32I32I64I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32I32I64I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "path_open", funcName: "$runtime.path_open",
 						typeIndex: indexZero},
@@ -188,7 +293,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "path_open" (func $runtime.path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32I32I32I32I64I32I32ResultI32},
+				types: []*typeFunc{paramI32I32I32I32I32I64I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "path_open", funcName: "$runtime.path_open",
 						typeIndex: indexZero},
@@ -202,7 +307,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{paramI32I32ResultI32, paramI32I32I32I32ResultI32},
+				types: []*typeFunc{paramI32I32ResultI32, paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
 						typeIndex: indexZero},
@@ -221,7 +326,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (type $i32i32i32i32_i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{
+				types: []*typeFunc{
 					typeFuncEmpty,
 					{name: "$i32i32_i32", params: []wasm.ValueType{i32, i32}, result: i32},
 					{name: "$i32i32i32i32_i32", params: []wasm.ValueType{i32, i32, i32, i32}, result: i32},
@@ -244,7 +349,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (type 2)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32, paramI32I32I32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32, paramI32I32I32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
 						typeIndex: &index{numeric: 1, line: 5, col: 86}},
@@ -261,7 +366,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "arg_sizes_get" (func $runtime.arg_sizes_get (param i32 i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get",
 						typeIndex: indexOne},
@@ -279,7 +384,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "arg_sizes_get" (func $runtime.arg_sizes_get (type 1)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get",
 						typeIndex: &index{numeric: 1, line: 4, col: 76}},
@@ -297,7 +402,7 @@ func TestParseModule(t *testing.T) {
 	(type (func (param i32 i32) (result i32)))
 )`,
 			expected: &module{
-				typeFuncs: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
+				types: []*typeFunc{typeFuncEmpty, paramI32I32ResultI32},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get",
 						typeIndex: &index{numeric: 1, line: 3, col: 76}},
@@ -306,6 +411,65 @@ func TestParseModule(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:  "import func param names",
+			input: "(module (import \"Math\" \"Mul\" (func $mul (param $x f32) (param $y f32) (result f32))))",
+			expected: &module{
+				types: []*typeFunc{
+					{params: []wasm.ValueType{f32, f32}, result: f32},
+				},
+				importFuncs: []*importFunc{
+					{importIndex: 0, module: "Math", name: "Mul", funcName: "$mul",
+						typeIndex: indexZero,
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'x'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'y'}},
+						},
+					}},
+			},
+		},
+		{
+			name: "import funcs same param types different names",
+			input: `(module
+	(import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32)))
+	(import "Math" "Add" (func $add (param $l f32) (param $r f32) (result f32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{
+					{params: []wasm.ValueType{f32, f32}, result: f32},
+				},
+				importFuncs: []*importFunc{
+					{importIndex: 0, module: "Math", name: "Mul", funcName: "$mul",
+						typeIndex: indexZero,
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'x'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'y'}},
+						},
+					},
+					{importIndex: 1, module: "Math", name: "Add", funcName: "$add",
+						typeIndex: indexZero,
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'l'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'r'}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:  "import func mixed param names", // Checks paramNameIndex when there are less param fields than params
+			input: "(module (import \"\" \"\" (func (param i32 i32) (param $v i32) (param i64) (param $t f32))))",
+			expected: &module{
+				types: []*typeFunc{
+					{params: []wasm.ValueType{i32, i32, i32, i64, f32}},
+				},
+				importFuncs: []*importFunc{
+					{importIndex: 0, typeIndex: indexZero, paramNames: paramNames{
+						&paramNameIndex{uint32(2), []byte{'$', 'v'}},
+						&paramNameIndex{uint32(4), []byte{'$', 't'}},
+					}}},
+			},
+		},
 		{
 			name: "start imported function by name",
 			input: `(module
@@ -313,7 +477,7 @@ func TestParseModule(t *testing.T) {
 	(start $hello)
 )`,
 			expected: &module{
-				typeFuncs:     []*typeFunc{typeFuncEmpty},
+				types:         []*typeFunc{typeFuncEmpty},
 				importFuncs:   []*importFunc{{name: "hello", funcName: "$hello", typeIndex: indexZero}},
 				startFunction: &index{numeric: 0, line: 3, col: 9},
 			},
@@ -325,7 +489,7 @@ func TestParseModule(t *testing.T) {
 	(start 0)
 )`,
 			expected: &module{
-				typeFuncs:     []*typeFunc{typeFuncEmpty},
+				types:         []*typeFunc{typeFuncEmpty},
 				importFuncs:   []*importFunc{{name: "hello", importIndex: 0, typeIndex: indexZero}},
 				startFunction: &index{numeric: 0, line: 3, col: 9},
 			},
@@ -335,20 +499,39 @@ func TestParseModule(t *testing.T) {
 			input: string(example),
 			expected: &module{
 				name: "$example",
-				typeFuncs: []*typeFunc{
+				types: []*typeFunc{
 					{name: "$i32i32_i32", params: []wasm.ValueType{i32, i32}, result: i32},
 					typeFuncEmpty, // Note: inlined types come after explicit ones even if the latter are defined later
 					{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32},
+					{params: []wasm.ValueType{f32, f32}, result: f32},
 				},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get",
 						typeIndex: &index{numeric: 0, line: 3, col: 86}},
 					{importIndex: 1, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write",
-						typeIndex: &index{numeric: 2, line: 0, col: 0}},
-					{importIndex: 2, module: "", name: "hello", funcName: "$hello",
-						typeIndex: &index{numeric: 1, line: 6, col: 40}},
+						typeIndex: &index{numeric: 2, line: 3, col: 81}},
+					{importIndex: 2, module: "Math", name: "Mul", funcName: "$mul",
+						typeIndex: &index{numeric: 3, line: 3, col: 81},
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'x'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'y'}},
+						},
+					},
+					{importIndex: 3, module: "Math", name: "Add", funcName: "$add",
+						typeIndex: &index{numeric: 0, line: 6, col: 40},
+						typeInlined: &inlinedTypeFunc{
+							typeFunc: &typeFunc{name: "$i32i32_i32", params: []wasm.ValueType{i32, i32}, result: i32},
+							line:     6, col: 35,
+						},
+						paramNames: paramNames{
+							&paramNameIndex{uint32(0), []byte{'$', 'l'}},
+							&paramNameIndex{uint32(1), []byte{'$', 'r'}},
+						},
+					},
+					{importIndex: 4, module: "", name: "hello", funcName: "$hello",
+						typeIndex: &index{numeric: 1, line: 8, col: 40}},
 				},
-				startFunction: &index{numeric: 2, line: 7, col: 9},
+				startFunction: &index{numeric: 4, line: 9, col: 9},
 			},
 		},
 	}
@@ -412,25 +595,45 @@ func TestParseModule_Errors(t *testing.T) {
 			expectedErr: "1:30: unexpected id: $Math in module.import[0]",
 		},
 		{
-			name:        "type missing func",
+			name:        "type func missing func",
 			input:       "(module (type))",
 			expectedErr: "1:14: missing func field in module.type[0]",
 		},
 		{
-			name:        "type too much func",
+			name:        "type func too much func",
 			input:       "(module (type (func) (func)))",
 			expectedErr: "1:22: unexpected '(' in module.type[0]",
 		},
 		{
-			name:        "type second name",
+			name:        "type func second name",
 			input:       "(module (type $v_v $v_v func()))",
-			expectedErr: "1:20: unexpected id: $v_v in module.type[0]",
+			expectedErr: "1:20: redundant name in module.type[0]",
+		},
+		{
+			name:        "type func param second name",
+			input:       "(module (type (func (param $x $y i32) ))",
+			expectedErr: "1:31: redundant name in module.type[0].func.param[0]",
 		},
 		{
-			name:        "type name wrong place",
+			name:        "type func param name in abbreviation",
+			input:       "(module (type (func (param $x i32 i64) ))",
+			expectedErr: "1:35: cannot name parameters in abbreviated form in module.type[0].func.param[0]",
+		},
+		{
+			name:        "type func name wrong place",
 			input:       "(module (type (func $v_v )))",
 			expectedErr: "1:21: unexpected id: $v_v in module.type[0].func",
 		},
+		{
+			name:        "type invalid",
+			input:       "(module (type \"0\"))",
+			expectedErr: "1:15: unexpected string: \"0\" in module.type[0]",
+		},
+		{
+			name:        "type func invalid",
+			input:       "(module (type (func \"0\")))",
+			expectedErr: "1:21: unexpected string: \"0\" in module.type[0].func",
+		},
 		{
 			name:        "import missing module",
 			input:       "(module (import))",
@@ -491,6 +694,16 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (import \"foo\" \"bar\" (func $baz $qux)))",
 			expectedErr: "1:40: redundant name: $qux in module.import[0].func",
 		},
+		{
+			name:        "import func param second name",
+			input:       "(module (import \"\" \"\" (func (param $x $y i32) ))",
+			expectedErr: "1:39: redundant name in module.import[0].func.param[0]",
+		},
+		{
+			name:        "import func param name in abbreviation",
+			input:       "(module (import \"\" \"\" (func (param $x i32 i64) ))",
+			expectedErr: "1:43: cannot name parameters in abbreviated form in module.import[0].func.param[0]",
+		},
 		{
 			name:        "import func missing param0 type",
 			input:       "(module (import \"\" \"\" (func (param))))",
@@ -556,6 +769,21 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (import \"\" \"\" (func (result i32) $main)))",
 			expectedErr: "1:42: unexpected id: $main in module.import[0].func",
 		},
+		{
+			name:        "import func type duplicate index",
+			input:       "(module (import \"\" \"\" (func (type 9 2)))",
+			expectedErr: "1:37: redundant index in module.import[0].func.type",
+		},
+		{
+			name:        "import func duplicate type",
+			input:       "(module (import \"\" \"\" (func (type 9) (type 2)))",
+			expectedErr: "1:39: redundant type in module.import[0].func",
+		},
+		{
+			name:        "import func type invalid",
+			input:       "(module (import \"\" \"\" (func (type \"0\")))",
+			expectedErr: "1:35: unexpected string: \"0\" in module.import[0].func.type",
+		},
 		{
 			name:        "import func param after result",
 			input:       "(module (import \"\" \"\" (func (result i32) (param i32))))",
diff --git a/wasm/wat/types_test.go b/wasm/wat/types_test.go
new file mode 100644
index 00000000..9c19baed
--- /dev/null
+++ b/wasm/wat/types_test.go
@@ -0,0 +1,47 @@
+package wat
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestFormatError_Error(t *testing.T) {
+	t.Run("with context", func(t *testing.T) {
+		require.EqualError(t, &FormatError{
+			Line:    1,
+			Col:     2,
+			Context: "start",
+			cause:   errors.New("invalid token"),
+		}, "1:2: invalid token in start")
+	})
+	t.Run("no context", func(t *testing.T) {
+		require.EqualError(t, &FormatError{
+			Line:  1,
+			Col:   2,
+			cause: errors.New("invalid token"),
+		}, "1:2: invalid token")
+	})
+}
+
+func TestFormatError_Unwrap(t *testing.T) {
+	t.Run("cause", func(t *testing.T) {
+		cause := errors.New("invalid token")
+		formatErr := &FormatError{
+			Line:    1,
+			Col:     2,
+			Context: "start",
+			cause:   cause,
+		}
+		require.Equal(t, cause, formatErr.Unwrap())
+	})
+	t.Run("no cause", func(t *testing.T) {
+		formatErr := &FormatError{
+			Line:    1,
+			Col:     2,
+			Context: "start",
+		}
+		require.Nil(t, formatErr.Unwrap())
+	})
+}