Repository: tetratelabs/wazero
Pull request #120 (2022-01-07 02:47:00)

wat: support import functions with inlined types
This begins support of real functions as before we hard-coded all are
nullary (void in void out). As there's a lot of refactoring left to do,
this starts only by supporting inlined type (signatures) for imported
functions. This does not handle inlined parameter names which are a bit
complicated so would make the PR too hard to review.



Patch:
diff --git a/wasm/jit/compiler.go b/wasm/jit/compiler.go
index d10d548f..8bf62f5e 100644
--- a/wasm/jit/compiler.go
+++ b/wasm/jit/compiler.go
@@ -38,6 +38,13 @@ type compiler interface {
 	compileShr(o *wazeroir.OperationShr) error
 	compileRotl(o *wazeroir.OperationRotl) error
 	compileRotr(o *wazeroir.OperationRotr) error
+	compileAbs(o *wazeroir.OperationAbs) error
+	compileNeg(o *wazeroir.OperationNeg) error
+	compileCeil(o *wazeroir.OperationCeil) error
+	compileFloor(o *wazeroir.OperationFloor) error
+	compileTrunc(o *wazeroir.OperationTrunc) error
+	compileNearest(o *wazeroir.OperationNearest) error
+	compileSqrt(o *wazeroir.OperationSqrt) error
 	compileEq(o *wazeroir.OperationEq) error
 	compileNe(o *wazeroir.OperationNe) error
 	compileEqz(o *wazeroir.OperationEqz) error
diff --git a/wasm/jit/engine.go b/wasm/jit/engine.go
index 7920c16d..c6c8fb92 100644
--- a/wasm/jit/engine.go
+++ b/wasm/jit/engine.go
@@ -563,19 +563,19 @@ func (e *engine) compileWasmFunction(f *wasm.FunctionInstance) (*compiledWasmFun
 		case *wazeroir.OperationRotr:
 			err = compiler.compileRotr(o)
 		case *wazeroir.OperationAbs:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileAbs(o)
 		case *wazeroir.OperationNeg:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileNeg(o)
 		case *wazeroir.OperationCeil:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileCeil(o)
 		case *wazeroir.OperationFloor:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileFloor(o)
 		case *wazeroir.OperationTrunc:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileTrunc(o)
 		case *wazeroir.OperationNearest:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileNearest(o)
 		case *wazeroir.OperationSqrt:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileSqrt(o)
 		case *wazeroir.OperationMin:
 			err = fmt.Errorf("unsupported operation")
 		case *wazeroir.OperationMax:
diff --git a/wasm/jit/jit_amd64.go b/wasm/jit/jit_amd64.go
index 92707921..96fee6ce 100644
--- a/wasm/jit/jit_amd64.go
+++ b/wasm/jit/jit_amd64.go
@@ -14,6 +14,7 @@ import (
 	"fmt"
 	"math"
 	"runtime"
+	"unsafe"
 
 	asm "github.com/twitchyliquid64/golang-asm"
 	"github.com/twitchyliquid64/golang-asm/obj"
@@ -23,6 +24,21 @@ import (
 	"github.com/tetratelabs/wazero/wasm/wazeroir"
 )
 
+var (
+	float32SignBitMask        int64 = 1 << 31
+	float32SignBitMaskAddress uintptr
+	// float64SignBitMask equals 1 << 63 but Go compiler errors as the number doesn't fit into
+	// the range of "signed" 64bit int, so we have the literal signed 64-bit integer
+	// whose bit pattern equals 1 << 63 here.
+	float64SignBitMask        int64 = -9223372036854775808
+	float64SignBitMaskAddress uintptr
+)
+
+func init() {
+	float32SignBitMaskAddress = uintptr(unsafe.Pointer(&float32SignBitMask))
+	float64SignBitMaskAddress = uintptr(unsafe.Pointer(&float64SignBitMask))
+}
+
 // jitcall is implemented in jit_amd64.s as a Go Assembler function.
 // This is used by engine.exec and the entrypoint to enter the JITed native code.
 // codeSegment is the pointer to the initial instruction of the compiled native code.
@@ -1440,6 +1456,193 @@ func (c *amd64Compiler) emitSimpleBinaryOp(instruction obj.As) error {
 	return nil
 }
 
+// compileAbs adds instructions to replace the top value of float type on the stack with its absolute value.
+// For example, stack [..., -1.123] results in [..., 1.123].
+// See the following discussions for how we could take the abs of floats on x86 assembly.
+// https://stackoverflow.com/questions/32408665/fastest-way-to-compute-absolute-value-using-sse/32422471#32422471
+// https://stackoverflow.com/questions/44630015/how-would-fabsdouble-be-implemented-on-x86-is-it-an-expensive-operation
+func (c *amd64Compiler) compileAbs(o *wazeroir.OperationAbs) (err error) {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	// First shift left by one to clear the sign bit.
+	shiftLeftByOne := c.newProg()
+	if o.Type == wazeroir.Float32 {
+		shiftLeftByOne.As = x86.APSLLL
+	} else {
+		shiftLeftByOne.As = x86.APSLLQ
+	}
+	shiftLeftByOne.To.Type = obj.TYPE_REG
+	shiftLeftByOne.To.Reg = target.register
+	shiftLeftByOne.From.Type = obj.TYPE_CONST
+	shiftLeftByOne.From.Offset = 1
+	c.addInstruction(shiftLeftByOne)
+
+	// Then shift right by one.
+	shiftRightByOne := c.newProg()
+	if o.Type == wazeroir.Float32 {
+		shiftRightByOne.As = x86.APSRLL
+	} else {
+		shiftRightByOne.As = x86.APSRLQ
+	}
+	shiftRightByOne.To.Type = obj.TYPE_REG
+	shiftRightByOne.To.Reg = target.register
+	shiftRightByOne.From.Type = obj.TYPE_CONST
+	shiftRightByOne.From.Offset = 1
+	c.addInstruction(shiftRightByOne)
+	return nil
+}
+
+// compileNeg adds instructions to replace the top value of float type on the stack with its negated value.
+// For example, stack [..., -1.123] results in [..., 1.123].
+func (c *amd64Compiler) compileNeg(o *wazeroir.OperationNeg) (err error) {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	tmpReg, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
+	if err != nil {
+		return err
+	}
+
+	// First we move the sign-bit mask (placed in memory) to the tmp register,
+	// since we cannot take XOR directly with float reg and const.
+	movToTmp := c.newProg()
+	var maskAddr uintptr
+	if o.Type == wazeroir.Float32 {
+		movToTmp.As = x86.AMOVL
+		maskAddr = float32SignBitMaskAddress
+	} else {
+		movToTmp.As = x86.AMOVQ
+		maskAddr = float64SignBitMaskAddress
+	}
+	movToTmp.From.Type = obj.TYPE_MEM
+	movToTmp.From.Offset = int64(maskAddr)
+	movToTmp.To.Type = obj.TYPE_REG
+	movToTmp.To.Reg = tmpReg
+	c.addInstruction(movToTmp)
+
+	// Negate the value by XOR it with the sign-bit mask.
+	negate := c.newProg()
+	if o.Type == wazeroir.Float32 {
+		negate.As = x86.AXORPS
+	} else {
+		negate.As = x86.AXORPD
+	}
+	negate.From.Type = obj.TYPE_REG
+	negate.From.Reg = tmpReg
+	negate.To.Type = obj.TYPE_REG
+	negate.To.Reg = target.register
+	c.addInstruction(negate)
+	return nil
+}
+
+// compileCeil adds instructions to replace the top value of float type on the stack with its ceiling value.
+// For example, stack [..., 1.123] results in [..., 2.0]. This is equivalent to math.Ceil.
+func (c *amd64Compiler) compileCeil(o *wazeroir.OperationCeil) (err error) {
+	// Internally, ceil can be performed via ROUND instruction with 0x02 mode.
+	// See https://android.googlesource.com/platform/bionic/+/882b8af/libm/x86_64/ceilf.S for example.
+	return c.emitRoundInstruction(o.Type == wazeroir.Float32, 0x02)
+}
+
+// compileFloor adds instructions to replace the top value of float type on the stack with its floor value.
+// For example, stack [..., 1.123] results in [..., 1.0]. This is equivalent to math.Floor.
+func (c *amd64Compiler) compileFloor(o *wazeroir.OperationFloor) (err error) {
+	// Internally, floor can be performed via ROUND instruction with 0x01 mode.
+	// See https://android.googlesource.com/platform/bionic/+/882b8af/libm/x86_64/floorf.S for example.
+	return c.emitRoundInstruction(o.Type == wazeroir.Float32, 0x01)
+}
+
+// compileTrunc adds instructions to replace the top value of float type on the stack with its truncated value.
+// For example, stack [..., 1.9] results in [..., 1.0]. This is equivalent to math.Trunc.
+func (c *amd64Compiler) compileTrunc(o *wazeroir.OperationTrunc) error {
+	// Internally, trunc can be performed via ROUND instruction with 0x03 mode.
+	// See https://android.googlesource.com/platform/bionic/+/882b8af/libm/x86_64/truncf.S for example.
+	return c.emitRoundInstruction(o.Type == wazeroir.Float32, 0x03)
+}
+
+// compileNearest adds instructions to replace the top value of float type on the stack with its nearest integer value.
+// For example, stack [..., 1.9] results in [..., 2.0]. This is NOT equivalent to math.Round and instead has the same
+// the semantics of LLVM's rint instrinsic. See https://llvm.org/docs/LangRef.html#llvm-rint-intrinsic.
+// For example, math.Round(-4.5) produces -5 while ROUND with 0x00 mode produces -4.
+func (c *amd64Compiler) compileNearest(o *wazeroir.OperationNearest) error {
+	// Internally, nearest can be performed via ROUND instruction with 0x00 mode.
+	// If we compile the following Wat by "wasmtime wasm2obj",
+	//
+	// (module
+	//   (func (export "nearest_f32") (param $x f32) (result f32) (f32.nearest (local.get $x)))
+	//   (func (export "nearest_f64") (param $x f64) (result f64) (f64.nearest (local.get $x)))
+	// )
+	//
+	// we see a disassemble of the object via "objdump --disassemble-all" like:
+	//
+	// 0000000000000000 <_wasm_function_0>:
+	// 	0:       55                      push   %rbp
+	// 	1:       48 89 e5                mov    %rsp,%rbp
+	// 	4:       66 0f 3a 0a c0 00       roundss $0x0,%xmm0,%xmm0
+	// 	a:       48 89 ec                mov    %rbp,%rsp
+	// 	d:       5d                      pop    %rbp
+	// 	e:       c3                      retq
+	//
+	// 000000000000000f <_wasm_function_1>:
+	// 	f:        55                      push   %rbp
+	//  10:       48 89 e5                mov    %rsp,%rbp
+	//  13:       66 0f 3a 0b c0 00       roundsd $0x0,%xmm0,%xmm0
+	//  19:       48 89 ec                mov    %rbp,%rsp
+	//  1c:       5d                      pop    %rbp
+	//  1d:       c3                      retq
+	//
+	// Below, we use the same implementation: "rounds{s,d} $0x0,%xmm0,%xmm0" where the mode is set to zero.
+	return c.emitRoundInstruction(o.Type == wazeroir.Float32, 0x00)
+}
+
+func (c *amd64Compiler) emitRoundInstruction(is32Bit bool, mode int64) error {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	round := c.newProg()
+	if is32Bit {
+		round.As = x86.AROUNDSS
+	} else {
+		round.As = x86.AROUNDSD
+	}
+	round.To.Type = obj.TYPE_REG
+	round.To.Reg = target.register
+	round.From.Type = obj.TYPE_CONST
+	round.From.Offset = mode
+	round.RestArgs = append(round.RestArgs,
+		obj.Addr{Reg: target.register, Type: obj.TYPE_REG})
+	c.addInstruction(round)
+	return nil
+}
+
+// compileSqrt adds instructions to replace the top value of float type on the stack with its square root.
+// For example, stack [..., 9.0] results in [..., 3.0]. This is equivalent to "math.Sqrt".
+func (c *amd64Compiler) compileSqrt(o *wazeroir.OperationSqrt) error {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	sqrt := c.newProg()
+	if o.Type == wazeroir.Float32 {
+		sqrt.As = x86.ASQRTSS
+	} else {
+		sqrt.As = x86.ASQRTSD
+	}
+	sqrt.From.Type = obj.TYPE_REG
+	sqrt.From.Reg = target.register
+	sqrt.To.Type = obj.TYPE_REG
+	sqrt.To.Reg = target.register
+	c.addInstruction(sqrt)
+	return nil
+}
+
 func (c *amd64Compiler) compileEq(o *wazeroir.OperationEq) error {
 	return c.emitEqOrNe(o.Type, true)
 }
diff --git a/wasm/wat/parser.go b/wasm/wat/parser.go
index 42d25500..967b9da8 100644
--- a/wasm/wat/parser.go
+++ b/wasm/wat/parser.go
@@ -1,8 +1,11 @@
 package wat
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
+
+	"github.com/tetratelabs/wazero/wasm"
 )
 
 // currentField holds the positional state of parser. Values are also useful as they allow you to do a reference search
@@ -12,33 +15,38 @@ type currentField byte
 const (
 	// fieldInitial is the first position in the source being parsed.
 	fieldInitial currentField = iota
-	// fieldModule is a top-level field named "module"
+	// fieldModule is at the top-level field named "module" and cannot repeat in the same source.
 	fieldModule
-	// fieldModuleImport is a field named "import" enclosed by the top-level field "module"
+	// fieldModuleImport is at the position module.import and can repeat in the same module.
 	//
 	// At the start of the field, moduleParser.currentValue0 tracks importFunc.module while moduleParser.currentValue1
 	// tracks importFunc.name. If a field named "func" is encountered, these names are recorded while
 	// fieldModuleImportFunc takes over parsing.
 	fieldModuleImport
-	// fieldModuleImportFunc is a field named "func" enclosed by a field named "import"
+	// fieldModuleImportFunc is at the position module.import.func and cannot repeat in the same import.
 	fieldModuleImportFunc
-	// fieldModuleStart is a field named "start" enclosed by the top-level field "module"
+	// fieldModuleImportFuncParam is at the position module.import.func.param and can repeat in the same function.
+	fieldModuleImportFuncParam
+	// fieldModuleImportFuncResult is at the position module.import.func.result and cannot in the same function.
+	fieldModuleImportFuncResult
+	// fieldModuleStart is at the position module.start and cannot repeat in the same module.
 	fieldModuleStart
 )
 
 type moduleParser struct {
 	// source is the entire WebAssembly text format source code being parsed.
 	source []byte
+
 	// module holds the fields incrementally parsed from tokens in the source.
 	module *module
 
 	// currentField is the parser and error context.
 	// This is set after reading a field name, ex "module", or after reaching the end of one, ex ')'.
-	currentField
+	currentField currentField
 
 	// tokenParser is called by lex, and changes based on the currentField.
 	// The initial tokenParser is ensureLParen because %.wat files must begin with a '(' token (ignoring whitespace).
-	tokenParser
+	tokenParser tokenParser
 
 	// currentValue0 is a currentField-specific place to stash a string when parsing a field.
 	// Ex. for fieldModuleImport, this would be Math if (import "Math" "PI" ...)
@@ -48,8 +56,21 @@ type moduleParser struct {
 	// Ex. for fieldModuleImport, this would be PI if (import "Math" "PI" ...)
 	currentValue1 []byte
 
-	// currentImportIndex allows us to track the relative position of imports regardless of what they describe
+	// currentImportIndex allows us to track the relative position of imports regardless of position in the source.
 	currentImportIndex uint32
+
+	// currentType allows us to build a type for use when parsing module types or inlined type use. In the case of
+	// inlined types, a new entry in module.types is only added if a matching signature doesn't already exist.
+	// See https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A6
+	currentType *typeFunc
+
+	// currentParams allow us to accumulate typeFunc.params across multiple fields, as well support abbreviated
+	// anonymous parameters. ex. both (param i32) (param i32) and (param i32 i32) formats.
+	// See https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A2
+	currentParams []wasm.ValueType
+
+	// currentParamIndex allows us to track the relative position of parameters regardless of position in the source.
+	currentParamIndex uint32
 }
 
 // parseModule parses the configured source into a module. This function returns when the source is exhausted or an
@@ -80,11 +101,18 @@ func (p *moduleParser) ensureLParen(tok tokenType, _ []byte, _, _ uint32) error
 	if tok != tokenLParen {
 		return fmt.Errorf("expected '(', but found %s", tok)
 	}
-	p.tokenParser = p.startField
+	p.tokenParser = p.beginField
 	return nil
 }
 
-func (p *moduleParser) startField(tok tokenType, tokenBytes []byte, _, _ uint32) (err error) {
+// beginField assigns the correct moduleParser.currentField and moduleParser.parseModule based on the source position
+// and fieldName being read.
+//
+// Once the next parser reaches a tokenRParen, moduleParser.endField must be called. This means that there must be
+// parity between the currentField values handled here and those handled in moduleParser.endField
+//
+// TODO: this design will likely be revisited to introduce a type that handles both begin and end of the current field.
+func (p *moduleParser) beginField(tok tokenType, fieldName []byte, _, _ uint32) error {
 	if tok != tokenKeyword {
 		return fmt.Errorf("expected field, but found %s", tok)
 	}
@@ -92,27 +120,82 @@ func (p *moduleParser) startField(tok tokenType, tokenBytes []byte, _, _ uint32)
 	// We expect p.currentField set according to a potentially nested "($fieldName".
 	// Each case must return a tokenParser that consumes the rest of the field up to the ')'.
 	// Note: each branch must handle any nesting concerns. Ex. "(module (import" nests further to "(func".
+	p.tokenParser = nil
 	switch p.currentField {
 	case fieldInitial:
-		p.tokenParser, err = p.initialFieldHandler(tokenBytes)
+		if string(fieldName) == "module" {
+			p.currentField = fieldModule
+			p.tokenParser = p.parseModule
+		}
 	case fieldModule:
-		p.tokenParser, err = p.moduleFieldHandler(tokenBytes)
+		switch string(fieldName) {
+		// TODO: "types"
+		case "import":
+			p.currentField = fieldModuleImport
+			p.tokenParser = p.parseImport
+		case "start":
+			if p.module.startFunction != nil {
+				return errors.New("redundant start")
+			}
+			p.currentField = fieldModuleStart
+			p.tokenParser = p.parseStart
+		}
 	case fieldModuleImport:
-		p.tokenParser, err = p.importFieldHandler(tokenBytes)
-	default:
-		return fmt.Errorf("unexpected current field %d", p.currentField)
+		// Add the next import func object and ready for parsing it.
+		if string(fieldName) == "func" { // cannot repeat
+			if uint32(len(p.module.importFuncs)) > p.currentImportIndex {
+				return fmt.Errorf("redundant field: %s", string(fieldName))
+			}
+			if err := p.addNextImportFunc(); err != nil {
+				return err
+			}
+			p.currentField = fieldModuleImportFunc
+			p.tokenParser = p.parseImportFunc
+		} // TODO: table, memory or global
+	case fieldModuleImportFunc:
+		switch string(fieldName) {
+		case "param": // can repeat
+			p.currentField = fieldModuleImportFuncParam
+			p.tokenParser = p.parseImportFuncParam
+		case "result": // cannot repeat
+			if p.currentType != nil && len(p.currentType.results) == 1 {
+				return errors.New("redundant result field") // Wasm 1.0 is single or no results
+			}
+			p.currentField = fieldModuleImportFuncResult
+			p.tokenParser = p.parseImportFuncResult
+		case "type": // cannot repeat
+			return errors.New("TODO: (module (import (func (type")
+		}
 	}
-	return
+	if p.tokenParser == nil {
+		return fmt.Errorf("unexpected field: %s", string(fieldName))
+	}
+	return nil
 }
 
-// initialFieldHandler returns a tokenParser for the top-level fields in the WebAssembly source.
-func (p *moduleParser) initialFieldHandler(fieldName []byte) (tokenParser, error) {
-	if string(fieldName) == "module" {
+// endField should be called after encountering tokenRParen. It places the current parser at the parent position based
+// on fixed knowledge of the text format structure.
+//
+// Design Note: This is an alternative to using a stack as the structure parsed by moduleParser is fixed depth. For
+// example, any function body may be parsed in a more dynamic way.
+func (p *moduleParser) endField() error {
+	switch p.currentField {
+	case fieldModuleImportFuncParam, fieldModuleImportFuncResult:
+		p.currentField = fieldModuleImportFunc
+		p.tokenParser = p.parseImportFunc
+	case fieldModuleImportFunc:
+		p.currentField = fieldModuleImport
+		p.tokenParser = p.parseImport
+	case fieldModuleStart, fieldModuleImport:
 		p.currentField = fieldModule
-		return p.parseModule, nil
-	} else {
-		return nil, fmt.Errorf("unexpected field: %s", string(fieldName))
+		p.tokenParser = p.parseModule
+	case fieldModule:
+		p.currentField = fieldInitial
+		p.tokenParser = p.parseUnexpectedToken // only one module is allowed and nothing else
+	default:
+		return errors.New("parent field is unknown") // bug
 	}
+	return nil
 }
 
 func (p *moduleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ uint32) error {
@@ -124,32 +207,16 @@ func (p *moduleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ uint32
 		}
 		p.module.name = name
 	case tokenLParen:
-		p.tokenParser = p.startField // after this look for a field name
+		p.tokenParser = p.beginField // after this look for a field name
 		return nil
 	case tokenRParen: // end of module
-		p.currentField = fieldInitial
+		return p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
 
-func (p *moduleParser) moduleFieldHandler(fieldName []byte) (tokenParser, error) {
-	switch string(fieldName) {
-	// TODO: "types"
-	case "import":
-		p.currentField = fieldModuleImport
-		return p.parseImport, nil
-	case "start":
-		if p.module.startFunction != nil {
-			return nil, errors.New("redundant start")
-		}
-		p.currentField = fieldModuleStart
-		return p.parseStart, nil
-	}
-	return nil, fmt.Errorf("unexpected field: %s", string(fieldName))
-}
-
 func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	switch tok {
 	case tokenString:
@@ -163,7 +230,7 @@ func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32
 			return fmt.Errorf("redundant name: %s", name)
 		}
 	case tokenLParen: // start fields, ex. (func
-		p.tokenParser = p.startField
+		p.tokenParser = p.beginField
 		return nil
 	case tokenRParen: // end of this import
 		// If we've not yet added the current import, determine the best error message.
@@ -173,37 +240,18 @@ func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32
 			}
 			return errors.New("missing description field") // Ex. (module "Math" "Pi")
 		}
-		p.currentField = fieldModule
+
+		// multiple imports are allowed, so advance in case there's a next.
 		p.currentImportIndex++
-		p.tokenParser = p.parseModule
+
+		return p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
 
-func (p *moduleParser) importFieldHandler(fieldName []byte) (tokenParser, error) {
-	if uint32(len(p.module.importFuncs)) > p.currentImportIndex {
-		return nil, fmt.Errorf("redundant field: %s", string(fieldName))
-	}
-	switch string(fieldName) {
-	case "func":
-		if err := p.validateImportModuleAndName(); err != nil {
-			return nil, err
-		}
-		p.currentField = fieldModuleImportFunc
-		desc := &importFunc{
-			module:      string(p.currentValue0),
-			name:        string(p.currentValue1),
-			importIndex: p.currentImportIndex,
-		}
-		p.currentValue0, p.currentValue1 = nil, nil
-		p.module.importFuncs = append(p.module.importFuncs, desc)
-		return p.parseImportFunc, nil
-	} // TODO: table, memory or global
-	return nil, fmt.Errorf("unexpected field: %s", string(fieldName))
-}
-
+// validateImportModuleAndName ensures we read both the module and name in the text format, even if they were empty.
 func (p *moduleParser) validateImportModuleAndName() error {
 	if p.currentValue0 == nil && p.currentValue1 == nil {
 		return errors.New("missing module and name")
@@ -213,6 +261,19 @@ func (p *moduleParser) validateImportModuleAndName() error {
 	return nil
 }
 
+func (p *moduleParser) addNextImportFunc() error {
+	if err := p.validateImportModuleAndName(); err != nil {
+		return err
+	}
+
+	p.module.importFuncs = append(p.module.importFuncs, &importFunc{
+		module:      string(p.currentValue0),
+		name:        string(p.currentValue1),
+		importIndex: p.currentImportIndex,
+	})
+	return nil
+}
+
 func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	switch tok {
 	case tokenID: // Ex. $main
@@ -222,24 +283,126 @@ func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, _, _ ui
 			return fmt.Errorf("redundant name: %s", name)
 		}
 		fn.funcName = name
-	case tokenLParen:
-		return errors.New("TODO: handle (type..) and inlined type (param..) (result..)")
-		// * typeuse https://www.w3.org/TR/wasm-core-1/#text-typeuse
-		// * inlined type https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A6
+	case tokenLParen: // start fields, ex. (param or (result
+		p.tokenParser = p.beginField
+		return nil
 	case tokenRParen: // end of this import func
-		// TODO: once we handle import types, this won't always be empty
-		if len(p.module.types) == 0 {
-			p.module.types = append(p.module.types, typeFuncEmpty)
+		if p.currentType == nil {
+			p.currentType = typeFuncEmpty
 		}
-		p.module.importFuncs[len(p.module.importFuncs)-1].typeIndex = 0
-		p.currentField = fieldModuleImport
-		p.tokenParser = p.parseImport
+
+		fn := p.module.importFuncs[len(p.module.importFuncs)-1]
+
+		// search for an existing signature that matches the current type
+		for i, t := range p.module.types {
+			if bytes.Equal(p.currentType.params, t.params) && bytes.Equal(p.currentType.results, t.results) {
+				fn.typeIndex = uint32(i)
+				p.currentType = nil
+				break
+			}
+		}
+
+		// if we didn't find a match, we need to insert a new type and use it
+		if p.currentType != nil { // new type
+			fn.typeIndex = uint32(len(p.module.types))
+			p.module.types = append(p.module.types, p.currentType)
+			p.currentType = nil
+		}
+
+		// reset parsing state
+		p.currentValue0, p.currentValue1 = nil, nil
+		p.currentParamIndex = 0
+		return p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
 
+func (p *moduleParser) parseImportFuncParam(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)")
+	case tokenKeyword: // Ex. i32
+		vt, err := parseValueType(tokenBytes)
+		if err != nil {
+			return err
+		}
+
+		// Here, we have to check for the abbreviated form, ex. (param i32 i32), noting this does not allow names!
+		if p.currentParamIndex > 0 && p.currentParams != nil { // ex. (param i32) (param i32 i32)
+			return errors.New("multiple type abbreviation is unexpected with multiple params")
+		}
+		p.currentParams = append(p.currentParams, vt)
+	case tokenRParen: // end of this field
+		if p.currentParams == nil {
+			return errors.New("expected a type")
+		}
+
+		// add the parameters to the current type
+		if p.currentType == nil {
+			p.currentType = &typeFunc{}
+		}
+		p.currentType.params = append(p.currentType.params, p.currentParams...)
+
+		// since multiple param fields are valid, ex `(func (param i32) (param i64))`, prepare for any next.
+		p.currentParams = nil
+		p.currentParamIndex++
+
+		return p.endField()
+	default:
+		return p.unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func (p *moduleParser) parseImportFuncResult(tok tokenType, tokenBytes []byte, _, _ uint32) error {
+	switch tok {
+	case tokenKeyword: // Ex. i32
+		if p.currentType.results != nil { // Ex. (result i32 i32)
+			return errors.New("redundant type")
+		}
+
+		vt, err := parseValueType(tokenBytes)
+		if err != nil {
+			return err
+		}
+
+		// add the result to the current type
+		if p.currentType == nil {
+			p.currentType = &typeFunc{}
+		}
+
+		p.currentType.results = append(p.currentType.results, vt)
+	case tokenRParen: // end of this field
+		if p.currentType == nil || p.currentType.results == nil {
+			return errors.New("expected a type")
+		}
+		return p.endField()
+	default:
+		return p.unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func parseValueType(tokenBytes []byte) (wasm.ValueType, error) {
+	t := string(tokenBytes)
+	var vt wasm.ValueType
+	switch t {
+	case "i32":
+		vt = wasm.ValueTypeI32
+	case "i64":
+		vt = wasm.ValueTypeI64
+	case "f32":
+		vt = wasm.ValueTypeF32
+	case "f64":
+		vt = wasm.ValueTypeF64
+	default:
+		return 0, fmt.Errorf("unknown type: %s", t)
+	}
+	return vt, nil
+}
+
 func (p *moduleParser) parseStart(tok tokenType, tokenBytes []byte, line, col uint32) error {
 	switch tok {
 	case tokenUN, tokenID: // Ex. $main or 2
@@ -251,14 +414,17 @@ func (p *moduleParser) parseStart(tok tokenType, tokenBytes []byte, line, col ui
 		if p.module.startFunction == nil {
 			return errors.New("missing funcidx")
 		}
-		p.currentField = fieldModule
-		p.tokenParser = p.parseModule
+		return p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
 
+func (p *moduleParser) parseUnexpectedToken(tok tokenType, tokenBytes []byte, _, _ uint32) error {
+	return p.unexpectedToken(tok, tokenBytes)
+}
+
 func (p *moduleParser) unexpectedToken(tok tokenType, tokenBytes []byte) error {
 	if tok == tokenLParen || tok == tokenRParen {
 		return fmt.Errorf("unexpected %s", tok)
@@ -274,7 +440,13 @@ func (p *moduleParser) errorContext() string {
 		return "module"
 	case fieldModuleStart:
 		return "module.start"
-	case fieldModuleImport, fieldModuleImportFunc:
+	case fieldModuleImport, fieldModuleImportFunc, fieldModuleImportFuncParam, fieldModuleImportFuncResult:
+		if p.currentField == fieldModuleImportFuncParam {
+			return fmt.Sprintf("module.import[%d].func.param[%d]", p.currentImportIndex, p.currentParamIndex)
+		}
+		if p.currentField == fieldModuleImportFuncResult {
+			return fmt.Sprintf("module.import[%d].func.result", p.currentImportIndex)
+		}
 		if p.currentField == fieldModuleImportFunc {
 			return fmt.Sprintf("module.import[%d].func", p.currentImportIndex)
 		}
diff --git a/wasm/wazeroir/interpreter.go b/wasm/wazeroir/interpreter.go
index 561689f5..d7239280 100644
--- a/wasm/wazeroir/interpreter.go
+++ b/wasm/wazeroir/interpreter.go
@@ -1211,38 +1211,40 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 			}
 		case OperationKindNearest:
 			{
-				// Borrowed from https://github.com/wasmerio/wasmer/blob/703bb4ee2ffb17b2929a194fc045a7e351b696e2/lib/vm/src/libcalls.rs#L77
+				// TODO: look at https://github.com/bytecodealliance/wasmtime/pull/2171 and reconsider this algorithm
 				if op.b1 == 0 {
 					// Float32
 					f := math.Float32frombits(uint32(it.pop()))
-					f64 := float64(f)
 					if f != -0 && f != 0 {
-						u := float32(math.Ceil(f64))
-						d := float32(math.Floor(f64))
-						um := math.Abs(float64(f - u))
-						dm := math.Abs(float64(f - d))
-						h := u / 2.0
-						if um < dm || float32(math.Floor(float64(h))) == h {
-							f = u
+						ceil := float32(math.Ceil(float64(f)))
+						floor := float32(math.Floor(float64(f)))
+						distToCeil := math.Abs(float64(f - ceil))
+						distToFloor := math.Abs(float64(f - floor))
+						h := ceil / 2.0
+						if distToCeil < distToFloor {
+							f = ceil
+						} else if distToCeil == distToFloor && float32(math.Floor(float64(h))) == h {
+							f = ceil
 						} else {
-							f = d
+							f = floor
 						}
 					}
 					it.push(uint64(math.Float32bits(f)))
 				} else {
 					// Float64
 					f := math.Float64frombits(it.pop())
-					f64 := float64(f)
 					if f != -0 && f != 0 {
-						u := math.Ceil(f64)
-						d := math.Floor(f64)
-						um := math.Abs(f - u)
-						dm := math.Abs(f - d)
-						h := u / 2.0
-						if um < dm || math.Floor(float64(h)) == h {
-							f = u
+						ceil := math.Ceil(f)
+						floor := math.Floor(f)
+						distToCeil := math.Abs(f - ceil)
+						distToFloor := math.Abs(f - floor)
+						h := ceil / 2.0
+						if distToCeil < distToFloor {
+							f = ceil
+						} else if distToCeil == distToFloor && math.Floor(float64(h)) == h {
+							f = ceil
 						} else {
-							f = d
+							f = floor
 						}
 					}
 					it.push(math.Float64bits(f))


Test patch:
diff --git a/wasm/jit/jit_amd64_test.go b/wasm/jit/jit_amd64_test.go
index 89d86d75..41505b5a 100644
--- a/wasm/jit/jit_amd64_test.go
+++ b/wasm/jit/jit_amd64_test.go
@@ -3877,6 +3877,216 @@ func TestAmd64Compiler_compileRem(t *testing.T) {
 	})
 }
 
+func TestAmd64Compiler_compile_abs_neg_ceil_floor(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		op   wazeroir.Operation
+	}{
+		{name: "abs-32-bit", op: &wazeroir.OperationAbs{Type: wazeroir.Float32}},
+		{name: "abs-64-bit", op: &wazeroir.OperationAbs{Type: wazeroir.Float64}},
+		{name: "neg-32-bit", op: &wazeroir.OperationNeg{Type: wazeroir.Float32}},
+		{name: "neg-64-bit", op: &wazeroir.OperationNeg{Type: wazeroir.Float64}},
+		{name: "ceil-32-bit", op: &wazeroir.OperationCeil{Type: wazeroir.Float32}},
+		{name: "ceil-64-bit", op: &wazeroir.OperationCeil{Type: wazeroir.Float64}},
+		{name: "floor-32-bit", op: &wazeroir.OperationFloor{Type: wazeroir.Float32}},
+		{name: "floor-64-bit", op: &wazeroir.OperationFloor{Type: wazeroir.Float64}},
+		{name: "trunc-32-bit", op: &wazeroir.OperationTrunc{Type: wazeroir.Float32}},
+		{name: "trunc-64-bit", op: &wazeroir.OperationTrunc{Type: wazeroir.Float64}},
+		{name: "sqrt-32-bit", op: &wazeroir.OperationSqrt{Type: wazeroir.Float32}},
+		{name: "sqrt-64-bit", op: &wazeroir.OperationSqrt{Type: wazeroir.Float64}},
+		{name: "nearest-32-bit", op: &wazeroir.OperationNearest{Type: wazeroir.Float32}},
+		{name: "nearest-64-bit", op: &wazeroir.OperationNearest{Type: wazeroir.Float64}},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			for i, v := range []uint64{
+				0,
+				1 << 63,
+				1<<63 | 12345,
+				1 << 31,
+				1<<31 | 123455,
+				6.8719476736e+10,
+				math.Float64bits(-4.5), // This produces the different result between math.Round and ROUND with 0x00 mode.
+				1.37438953472e+11,
+				math.Float64bits(-1.3),
+				uint64(math.Float32bits(-1231.123)),
+				math.Float64bits(1.3),
+				math.Float64bits(100.3),
+				math.Float64bits(-100.3),
+				uint64(math.Float32bits(1231.123)),
+				math.Float64bits(math.Inf(1)),
+				math.Float64bits(math.Inf(-1)),
+				math.Float64bits(math.NaN()),
+			} {
+				t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+					compiler := requireNewCompiler(t)
+					compiler.initializeReservedRegisters()
+					eng := newEngine()
+
+					var is32Bit bool
+					var expFloat32 float32
+					var expFloat64 float64
+					var compileOperationFunc func()
+					switch o := tc.op.(type) {
+					case *wazeroir.OperationAbs:
+						compileOperationFunc = func() {
+							err := compiler.compileAbs(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(math.Abs(float64(math.Float32frombits(uint32(v)))))
+						} else {
+							expFloat64 = math.Abs(math.Float64frombits(v))
+						}
+					case *wazeroir.OperationNeg:
+						compileOperationFunc = func() {
+							err := compiler.compileNeg(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = -math.Float32frombits(uint32(v))
+						} else {
+							expFloat64 = -math.Float64frombits(v)
+						}
+					case *wazeroir.OperationCeil:
+						compileOperationFunc = func() {
+							err := compiler.compileCeil(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(math.Ceil(float64(math.Float32frombits(uint32(v)))))
+						} else {
+							expFloat64 = math.Ceil(math.Float64frombits(v))
+						}
+					case *wazeroir.OperationFloor:
+						compileOperationFunc = func() {
+							err := compiler.compileFloor(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(math.Floor(float64(math.Float32frombits(uint32(v)))))
+						} else {
+							expFloat64 = math.Floor(math.Float64frombits(v))
+						}
+					case *wazeroir.OperationTrunc:
+						compileOperationFunc = func() {
+							err := compiler.compileTrunc(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(math.Trunc(float64(math.Float32frombits(uint32(v)))))
+						} else {
+							expFloat64 = math.Trunc(math.Float64frombits(v))
+						}
+					case *wazeroir.OperationSqrt:
+						compileOperationFunc = func() {
+							err := compiler.compileSqrt(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(math.Sqrt(float64(math.Float32frombits(uint32(v)))))
+						} else {
+							expFloat64 = math.Sqrt(math.Float64frombits(v))
+						}
+					case *wazeroir.OperationNearest:
+						compileOperationFunc = func() {
+							err := compiler.compileNearest(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						// The same algorithm as in wazeroir/interpreter.go.
+						if is32Bit {
+							expFloat32 = math.Float32frombits(uint32(v))
+							f64 := float64(expFloat32)
+							if expFloat32 != -0 && expFloat32 != 0 {
+								ceil := float32(math.Ceil(f64))
+								floor := float32(math.Floor(f64))
+								distToCeil := math.Abs(float64(expFloat32 - ceil))
+								distToFloor := math.Abs(float64(expFloat32 - floor))
+								h := ceil / 2.0
+								if distToCeil < distToFloor {
+									expFloat32 = ceil
+								} else if distToCeil == distToFloor && float32(math.Floor(float64(h))) == h {
+									expFloat32 = ceil
+								} else {
+									expFloat32 = floor
+								}
+							}
+						} else {
+							expFloat64 = math.Float64frombits(v)
+							if expFloat64 != -0 && expFloat64 != 0 {
+								ceil := math.Ceil(expFloat64)
+								floor := math.Floor(expFloat64)
+								distToCeil := math.Abs(expFloat64 - ceil)
+								distToFloor := math.Abs(expFloat64 - floor)
+								h := ceil / 2.0
+								if distToCeil < distToFloor {
+									expFloat64 = ceil
+								} else if distToCeil == distToFloor && math.Floor(h) == h {
+									expFloat64 = ceil
+								} else {
+									expFloat64 = floor
+								}
+							}
+						}
+					}
+
+					// Setup the target values.
+					var err error
+					if is32Bit {
+						err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
+					} else {
+						err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
+					}
+					require.NoError(t, err)
+
+					// Compile the operation.
+					compileOperationFunc()
+
+					// To verify the behavior, we release the value
+					// to the stack.
+					compiler.releaseAllRegistersToStack()
+					compiler.returnFunction()
+
+					// Generate and run the code under test.
+					code, _, err := compiler.generate()
+					require.NoError(t, err)
+					mem := newMemoryInst()
+					jitcall(
+						uintptr(unsafe.Pointer(&code[0])),
+						uintptr(unsafe.Pointer(eng)),
+						uintptr(unsafe.Pointer(&mem.Buffer[0])),
+					)
+
+					// Check the result.
+					require.Equal(t, uint64(1), eng.stackPointer)
+					if is32Bit {
+						actual := math.Float32frombits(uint32(eng.stack[eng.stackPointer-1]))
+						if math.IsNaN(float64(expFloat32)) {
+							require.True(t, math.IsNaN(float64(actual)))
+						} else {
+							require.Equal(t, actual, expFloat32)
+						}
+					} else {
+						actual := math.Float64frombits(eng.stack[eng.stackPointer-1])
+						if math.IsNaN(expFloat64) {
+							require.True(t, math.IsNaN(actual))
+						} else {
+							require.Equal(t, expFloat64, actual)
+						}
+					}
+				})
+			}
+		})
+	}
+}
+
 func TestAmd64Compiler_compileCall(t *testing.T) {
 	t.Run("host function", func(t *testing.T) {
 		const functionIndex = 5
diff --git a/wasm/wat/names_test.go b/wasm/wat/names_test.go
index fbc13756..afc42970 100644
--- a/wasm/wat/names_test.go
+++ b/wasm/wat/names_test.go
@@ -31,16 +31,16 @@ func TestEncodeNameSection(t *testing.T) {
 // TestEncodeNameSection_OnlyFuncName shows that we don't rely on the module name being present. For example, this isn't
 // encoded in TinyGo.
 func TestEncodeNameSection_OnlyFuncName(t *testing.T) {
-	func0, func1 := "runtime.fd_write", "runtime.args_sizes_get" // note: no leading '$'
+	func0, func1 := "runtime.args_sizes_get", "runtime.fd_write" // note: no leading '$'
 	i32 := wasm.ValueTypeI32
 	m := &module{
 		types: []*typeFunc{
-			{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}},
 			{params: []wasm.ValueType{i32, i32}, results: []wasm.ValueType{i32}},
+			{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}},
 		},
 		importFuncs: []*importFunc{
-			{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$" + func0},
-			{importIndex: 1, typeIndex: 1, module: "wasi_snapshot_preview1", name: "args_sizes_get", funcName: "$" + func1},
+			{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "args_sizes_get", funcName: "$" + func0},
+			{importIndex: 1, typeIndex: 1, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$" + func1},
 		},
 	}
 
diff --git a/wasm/wat/parser_test.go b/wasm/wat/parser_test.go
index a215087b..c61b31e6 100644
--- a/wasm/wat/parser_test.go
+++ b/wasm/wat/parser_test.go
@@ -4,9 +4,12 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/require"
+
+	"github.com/tetratelabs/wazero/wasm"
 )
 
 func TestParseModule(t *testing.T) {
+	i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
 	tests := []struct {
 		name     string
 		input    string
@@ -41,6 +44,78 @@ func TestParseModule(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "import func inlined type",
+			input: `(module
+	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}}},
+				importFuncs: []*importFunc{
+					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write"},
+				},
+			},
+		},
+		{
+			name: "import func inlined type no result",
+			input: `(module
+	(import "wasi_snapshot_preview1" "proc_exit" (func $runtime.proc_exit (param i32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{{params: []wasm.ValueType{i32}}},
+				importFuncs: []*importFunc{
+					{importIndex: 0, module: "wasi_snapshot_preview1", name: "proc_exit", funcName: "$runtime.proc_exit"},
+				},
+			},
+		},
+		{
+			name: "import func inlined type different param types",
+			input: `(module
+	(import "wasi_snapshot_preview1" "path_open" (func $runtime.path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{{
+					params:  []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
+					results: []wasm.ValueType{i32},
+				}},
+				importFuncs: []*importFunc{
+					{importIndex: 0, module: "wasi_snapshot_preview1", name: "path_open", funcName: "$runtime.path_open"},
+				},
+			},
+		},
+		{
+			name: "multiple import func different inlined type",
+			input: `(module
+	(import "wasi_snapshot_preview1" "arg_sizes_get" (func $runtime.arg_sizes_get (param i32 i32) (result i32)))
+	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{
+					{params: []wasm.ValueType{i32, i32}, results: []wasm.ValueType{i32}},
+					{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}},
+				},
+				importFuncs: []*importFunc{
+					{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get"},
+					{importIndex: 1, typeIndex: 1, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write"},
+				},
+			},
+		},
+		{
+			name: "multiple import func same inlined type",
+			input: `(module
+	(import "wasi_snapshot_preview1" "args_get" (func $runtime.args_get (param i32 i32) (result i32)))
+	(import "wasi_snapshot_preview1" "arg_sizes_get" (func $runtime.arg_sizes_get (param i32 i32) (result i32)))
+)`,
+			expected: &module{
+				types: []*typeFunc{
+					{params: []wasm.ValueType{i32, i32}, results: []wasm.ValueType{i32}},
+				},
+				importFuncs: []*importFunc{
+					{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get"},
+					{importIndex: 1, typeIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get"},
+				},
+			},
+		},
 		{
 			name:     "start function", // TODO: this is pointing to a funcidx not in the source!
 			input:    "(module (start $main))",
@@ -90,6 +165,11 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "()",
 			expectedErr: "1:2: expected field, but found )",
 		},
+		{
+			name:        "double module",
+			input:       "(module) (module)",
+			expectedErr: "1:10: unexpected (",
+		},
 		{
 			name:        "module invalid name",
 			input:       "(module test)", // must start with $
@@ -145,6 +225,41 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (import \"foo\" \"bar\" (func baz)))",
 			expectedErr: "1:35: unexpected keyword: baz in module.import[0].func",
 		},
+		{
+			name:        "import func missing param0 type",
+			input:       "(module (import \"\" \"\" (func (param))))",
+			expectedErr: "1:35: expected a type in module.import[0].func.param[0]",
+		},
+		{
+			name:        "import func missing param1 type",
+			input:       "(module (import \"\" \"\" (func (param i32) (param))))",
+			expectedErr: "1:47: expected a type in module.import[0].func.param[1]",
+		},
+		{
+			name:        "import func wrong param0 type",
+			input:       "(module (import \"\" \"\" (func (param f65))))",
+			expectedErr: "1:36: unknown type: f65 in module.import[0].func.param[0]",
+		},
+		{
+			name:        "import func wrong param1 type",
+			input:       "(module (import \"\" \"\" (func (param i32) (param f65))))",
+			expectedErr: "1:48: unknown type: f65 in module.import[0].func.param[1]",
+		},
+		{
+			name:        "import func double result",
+			input:       "(module (import \"\" \"\" (func (param i32) (result i32) (result i32))))",
+			expectedErr: "1:55: redundant result field in module.import[0].func",
+		},
+		{
+			name:        "import func double result type",
+			input:       "(module (import \"\" \"\" (func (param i32) (result i32 i32))))",
+			expectedErr: "1:53: redundant type in module.import[0].func.result",
+		},
+		{
+			name:        "import func wrong result type",
+			input:       "(module (import \"\" \"\" (func (param i32) (result f65))))",
+			expectedErr: "1:49: unknown type: f65 in module.import[0].func.result",
+		},
 		{
 			name:        "import func double desc",
 			input:       "(module (import \"foo\" \"bar\" (func $main) (func $mein)))",