Repository: tetratelabs/wazero
Pull request #121 (2022-01-07 07:33:43)

jit: support for various float operations part2
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>

mathetake: Ok the only thing left is truncation from float to int!

Patch:
diff --git a/wasm/jit/compiler.go b/wasm/jit/compiler.go
index 8bf62f5e..8b3f7189 100644
--- a/wasm/jit/compiler.go
+++ b/wasm/jit/compiler.go
@@ -45,6 +45,14 @@ type compiler interface {
 	compileTrunc(o *wazeroir.OperationTrunc) error
 	compileNearest(o *wazeroir.OperationNearest) error
 	compileSqrt(o *wazeroir.OperationSqrt) error
+	compileMin(o *wazeroir.OperationMin) error
+	compileMax(o *wazeroir.OperationMax) error
+	compileCopysign(o *wazeroir.OperationCopysign) error
+	compileI32WrapFromI64() error
+	compileITruncFromF(o *wazeroir.OperationITruncFromF) error
+	compileFConvertFromI(o *wazeroir.OperationFConvertFromI) error
+	compileF32DemoteFromF64() error
+	compileF64PromoteFromF32() 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 c6c8fb92..be1eb35d 100644
--- a/wasm/jit/engine.go
+++ b/wasm/jit/engine.go
@@ -577,20 +577,21 @@ func (e *engine) compileWasmFunction(f *wasm.FunctionInstance) (*compiledWasmFun
 		case *wazeroir.OperationSqrt:
 			err = compiler.compileSqrt(o)
 		case *wazeroir.OperationMin:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileMin(o)
 		case *wazeroir.OperationMax:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileMax(o)
 		case *wazeroir.OperationCopysign:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileCopysign(o)
 		case *wazeroir.OperationI32WrapFromI64:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileI32WrapFromI64()
 		case *wazeroir.OperationITruncFromF:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileITruncFromF(o)
 		case *wazeroir.OperationFConvertFromI:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileFConvertFromI(o)
 		case *wazeroir.OperationF32DemoteFromF64:
-			err = fmt.Errorf("unsupported operation")
+			err = compiler.compileF32DemoteFromF64()
 		case *wazeroir.OperationF64PromoteFromF32:
+			err = compiler.compileF64PromoteFromF32()
 		case *wazeroir.OperationI32ReinterpretFromF32,
 			*wazeroir.OperationI64ReinterpretFromF64,
 			*wazeroir.OperationF32ReinterpretFromI32,
diff --git a/wasm/jit/jit_amd64.go b/wasm/jit/jit_amd64.go
index 96fee6ce..f2567fc7 100644
--- a/wasm/jit/jit_amd64.go
+++ b/wasm/jit/jit_amd64.go
@@ -25,18 +25,21 @@ import (
 )
 
 var (
-	float32SignBitMask        int64 = 1 << 31
+	float32SignBitMask        uint32 = 1 << 31
+	float32RestBitMask        uint32 = ^float32SignBitMask
 	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
+	float32RestBitMaskAddress uintptr
+	float64SignBitMask        uint64 = 1 << 63
+	float64RestBitMask        uint64 = ^float64SignBitMask
 	float64SignBitMaskAddress uintptr
+	float64RestBitMaskAddress uintptr
 )
 
 func init() {
 	float32SignBitMaskAddress = uintptr(unsafe.Pointer(&float32SignBitMask))
+	float32RestBitMaskAddress = uintptr(unsafe.Pointer(&float32RestBitMask))
 	float64SignBitMaskAddress = uintptr(unsafe.Pointer(&float64SignBitMask))
+	float64RestBitMaskAddress = uintptr(unsafe.Pointer(&float64RestBitMask))
 }
 
 // jitcall is implemented in jit_amd64.s as a Go Assembler function.
@@ -67,8 +70,8 @@ type amd64Compiler struct {
 	ir  *wazeroir.CompilationResult
 	// Set a jmp kind instruction where you want to set the next coming
 	// instruction as the destination of the jmp instruction.
-	setJmpOrigin *obj.Prog
-	builder      *asm.Builder
+	setJmpOrigins []*obj.Prog
+	builder       *asm.Builder
 	// location stack holds the state of wazeroir virtual stack.
 	// and each item is either placed in register or the actual memory stack.
 	locationStack *valueLocationStack
@@ -124,10 +127,14 @@ func (c *amd64Compiler) pushFunctionParams() {
 
 func (c *amd64Compiler) addInstruction(prog *obj.Prog) {
 	c.builder.AddInstruction(prog)
-	if c.setJmpOrigin != nil {
-		c.setJmpOrigin.To.SetTarget(prog)
-		c.setJmpOrigin = nil
+	for _, origin := range c.setJmpOrigins {
+		origin.To.SetTarget(prog)
 	}
+	c.setJmpOrigins = nil
+}
+
+func (c *amd64Compiler) addSetJmpOrigin(prog *obj.Prog) {
+	c.setJmpOrigins = append(c.setJmpOrigins, prog)
 }
 
 func (c *amd64Compiler) newProg() (prog *obj.Prog) {
@@ -473,7 +480,7 @@ func (c *amd64Compiler) compileBrIf(o *wazeroir.OperationBrIf) error {
 	}
 
 	// Handle then branch.
-	c.setJmpOrigin = jmpWithCond
+	c.addSetJmpOrigin(jmpWithCond)
 	c.locationStack = saved
 	if err := c.emitDropRange(thenTarget.ToDrop); err != nil {
 		return err
@@ -672,7 +679,7 @@ func (c *amd64Compiler) compileSelect() error {
 	}
 
 	// Else, we don't need to adjust value, just need to jump to the next instruction.
-	c.setJmpOrigin = jmpIfNotZero
+	c.addSetJmpOrigin(jmpIfNotZero)
 
 	// In any case, we don't need x2 and c anymore!
 	c.locationStack.releaseRegister(x2)
@@ -1023,7 +1030,7 @@ func (c *amd64Compiler) compileClz(o *wazeroir.OperationClz) error {
 
 		// Finally the end jump instruction of zero case must target towards
 		// the next instruction.
-		c.setJmpOrigin = jmpAtEndOfZero
+		c.addSetJmpOrigin(jmpAtEndOfZero)
 	}
 
 	// We reused the same register of target for the result.
@@ -1102,7 +1109,7 @@ func (c *amd64Compiler) compileCtz(o *wazeroir.OperationCtz) error {
 
 		// Finally the end jump instruction of zero case must target towards
 		// the next instruction.
-		c.setJmpOrigin = jmpAtEndOfZero
+		c.addSetJmpOrigin(jmpAtEndOfZero)
 	}
 
 	// We reused the same register of target for the result.
@@ -1621,6 +1628,224 @@ func (c *amd64Compiler) emitRoundInstruction(is32Bit bool, mode int64) error {
 	return nil
 }
 
+// compileMin adds instructions to pop two values from the stack, and push back the maximum of
+// these two values onto the stack. For example, stack [..., 100.1, 1.9] results in [..., 1.9].
+// For the cases where NaN involves, see the doc of emitMinOrMax below.
+func (c *amd64Compiler) compileMin(o *wazeroir.OperationMin) error {
+	is32Bit := o.Type == wazeroir.Float32
+	if is32Bit {
+		return c.emitMinOrMax(is32Bit, x86.AMINSS)
+	} else {
+		return c.emitMinOrMax(is32Bit, x86.AMINSD)
+	}
+}
+
+// compileMax adds instructions to pop two values from the stack, and push back the maximum of
+// these two values onto the stack. For example, stack [..., 100.1, 1.9] results in [..., 100.1].
+// For the cases where NaN involves, see the doc of emitMinOrMax below.
+func (c *amd64Compiler) compileMax(o *wazeroir.OperationMax) error {
+	is32Bit := o.Type == wazeroir.Float32
+	if is32Bit {
+		return c.emitMinOrMax(is32Bit, x86.AMAXSS)
+	} else {
+		return c.emitMinOrMax(is32Bit, x86.AMAXSD)
+	}
+}
+
+// emitMinOrMax adds instructions to pop two values from the stack, and push back either minimum or
+// minimum of these two values onto the stack according to the minOrMaxInstruction argument.
+// minOrMaxInstruction must be one of MAXSS, MAXSD, MINSS or MINSD.
+// Note: These native min/max instructions are almost compatible with min/max in the Wasm specification,
+// but it is slightly different with respect to the NaN handling.
+// Native min/max instructions return non-NaN value if exactly one of target values
+// is NaN. For example native_{min,max}(5.0, NaN) returns always 5.0, not NaN.
+// However, WebAssembly specifies that min/max must always return NaN if one of values is NaN.
+// Therefore in this function, we have to add conditional jumps to check if one of values is NaN before
+// the native min/max, which is why we cannot simply emit a native min/max instruction here.
+//
+// For the semantics, see wazeroir.Min and wazeroir.Max for detail.
+func (c *amd64Compiler) emitMinOrMax(is32Bit bool, minOrMaxInstruction obj.As) error {
+	x2 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x2); err != nil {
+		return err
+	}
+	x1 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+
+	// Check if this is (either x1 or x2 is NaN) or (x1 equals x2) case
+	checkNaNOrEquals := c.newProg()
+	if is32Bit {
+		checkNaNOrEquals.As = x86.AUCOMISS
+	} else {
+		checkNaNOrEquals.As = x86.AUCOMISD
+	}
+	checkNaNOrEquals.From.Type = obj.TYPE_REG
+	checkNaNOrEquals.From.Reg = x2.register
+	checkNaNOrEquals.To.Type = obj.TYPE_REG
+	checkNaNOrEquals.To.Reg = x1.register
+	c.addInstruction(checkNaNOrEquals)
+
+	// At this point, we have the three cases of conditional flags below
+	// (See https://www.felixcloutier.com/x86/ucomiss#operation for detail.)
+	//
+	// 1) Two values are NaN-free and different: All flags are cleared.
+	// 2) Two values are NaN-free and equal: Only ZF flags is set.
+	// 3) One of Two values is NaN: ZF, PF and CF flags are set.
+
+	// Jump instruction to handle 1) case by checking the ZF flag
+	// as ZF is only set for 2) and 3) cases.
+	nanFreeOrDiffJump := c.newProg()
+	nanFreeOrDiffJump.As = x86.AJNE
+	nanFreeOrDiffJump.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(nanFreeOrDiffJump)
+
+	// Start handling 2) and 3).
+
+	// Jump if two values are equal and NaN-free by checking the parity flag (PF).
+	// Here we use JPC to do the conditional jump when the parity flag is NOT set,
+	// and that is of 2).
+	equalExitJmp := c.newProg()
+	equalExitJmp.As = x86.AJPC
+	equalExitJmp.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(equalExitJmp)
+
+	// Start handling 3).
+
+	// We emit the ADD instruction to produce the NaN in x1.
+	copyNan := c.newProg()
+	if is32Bit {
+		copyNan.As = x86.AADDSS
+	} else {
+		copyNan.As = x86.AADDSD
+	}
+	copyNan.From.Type = obj.TYPE_REG
+	copyNan.From.Reg = x2.register
+	copyNan.To.Type = obj.TYPE_REG
+	copyNan.To.Reg = x1.register
+	c.addInstruction(copyNan)
+
+	// Exit from the NaN case branch.
+	nanExitJmp := c.newProg()
+	nanExitJmp.As = obj.AJMP
+	nanExitJmp.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(nanExitJmp)
+
+	// Start handling 1).
+
+	// Now handle the NaN-free and different values case.
+	nanFreeOrDiff := c.newProg()
+	nanFreeOrDiffJump.To.SetTarget(nanFreeOrDiff)
+	nanFreeOrDiff.As = minOrMaxInstruction
+	nanFreeOrDiff.From.Type = obj.TYPE_REG
+	nanFreeOrDiff.From.Reg = x2.register
+	nanFreeOrDiff.To.Type = obj.TYPE_REG
+	nanFreeOrDiff.To.Reg = x1.register
+	c.addInstruction(nanFreeOrDiff)
+
+	// Set the jump target of 1) and 2) cases to the next instruction after 3) case.
+	c.addSetJmpOrigin(nanExitJmp)
+	c.addSetJmpOrigin(equalExitJmp)
+
+	// Record that we consumed the x2 and placed the minOrMax result in the x1's register.
+	c.locationStack.markRegisterUnused(x2.register)
+	c.locationStack.pushValueOnRegister(x1.register)
+	return nil
+}
+
+// compileCopysign adds instructions to pop two float values from the stack, and copy the signbit of
+// the first-popped value to the last one.
+// For example, stack [..., 1.213, -5.0] results in [..., -1.213].
+func (c *amd64Compiler) compileCopysign(o *wazeroir.OperationCopysign) error {
+	is32Bit := o.Type == wazeroir.Float32
+
+	x2 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x2); err != nil {
+		return err
+	}
+	x1 := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+	tmpReg, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
+	if err != nil {
+		return err
+	}
+
+	// Move the rest bit mask to the temp register.
+	movRestBitMask := c.newProg()
+	movRestBitMask.From.Type = obj.TYPE_MEM
+	if is32Bit {
+		movRestBitMask.As = x86.AMOVL
+		movRestBitMask.From.Offset = int64(float32RestBitMaskAddress)
+	} else {
+		movRestBitMask.As = x86.AMOVQ
+		movRestBitMask.From.Offset = int64(float64RestBitMaskAddress)
+	}
+	movRestBitMask.To.Type = obj.TYPE_REG
+	movRestBitMask.To.Reg = tmpReg
+	c.addInstruction(movRestBitMask)
+
+	// Clear the sign bit of x1 via AND with the mask.
+	clearSignBit := c.newProg()
+	clearSignBit.From.Type = obj.TYPE_REG
+	clearSignBit.From.Reg = tmpReg
+	clearSignBit.To.Type = obj.TYPE_REG
+	clearSignBit.To.Reg = x1.register
+	if is32Bit {
+		clearSignBit.As = x86.AANDPS
+	} else {
+		clearSignBit.As = x86.AANDPD
+	}
+	c.addInstruction(clearSignBit)
+
+	// Move the sign bit mask to the temp register.
+	movSignBitMask := c.newProg()
+	movSignBitMask.From.Type = obj.TYPE_MEM
+	if is32Bit {
+		movSignBitMask.As = x86.AMOVL
+		movSignBitMask.From.Offset = int64(float32SignBitMaskAddress)
+	} else {
+		movSignBitMask.As = x86.AMOVQ
+		movSignBitMask.From.Offset = int64(float64SignBitMaskAddress)
+	}
+	movSignBitMask.To.Type = obj.TYPE_REG
+	movSignBitMask.To.Reg = tmpReg
+	c.addInstruction(movSignBitMask)
+
+	// Clear the non-sign bits of x2 via AND with the mask.
+	clearNonSignBit := c.newProg()
+	clearNonSignBit.From.Type = obj.TYPE_REG
+	clearNonSignBit.From.Reg = tmpReg
+	clearNonSignBit.To.Type = obj.TYPE_REG
+	clearNonSignBit.To.Reg = x2.register
+	if is32Bit {
+		clearNonSignBit.As = x86.AANDPS
+	} else {
+		clearNonSignBit.As = x86.AANDPD
+	}
+	c.addInstruction(clearNonSignBit)
+
+	// Finally, copy the sign bit of x2 to x1.
+	copySignBit := c.newProg()
+	copySignBit.From.Type = obj.TYPE_REG
+	copySignBit.From.Reg = x2.register
+	copySignBit.To.Type = obj.TYPE_REG
+	copySignBit.To.Reg = x1.register
+	if is32Bit {
+		copySignBit.As = x86.AORPS
+	} else {
+		copySignBit.As = x86.AORPD
+	}
+	c.addInstruction(copySignBit)
+
+	// Record that we consumed the x2 and placed the copysign result in the x1's register.
+	c.locationStack.markRegisterUnused(x2.register)
+	c.locationStack.pushValueOnRegister(x1.register)
+	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 {
@@ -1643,6 +1868,286 @@ func (c *amd64Compiler) compileSqrt(o *wazeroir.OperationSqrt) error {
 	return nil
 }
 
+// compileI32WrapFromI64 adds instructions to replace the 64-bit int on top of the stack
+// with the corresponding 32-bit integer. This is equivalent to uint64(uint32(v)) in Go.
+func (c *amd64Compiler) compileI32WrapFromI64() error {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	mov := c.newProg()
+	mov.As = x86.AMOVL
+	mov.From.Type = obj.TYPE_REG
+	mov.From.Reg = target.register
+	mov.To.Type = obj.TYPE_REG
+	mov.To.Reg = target.register
+	c.addInstruction(mov)
+	return nil
+}
+
+func (c *amd64Compiler) compileITruncFromF(o *wazeroir.OperationITruncFromF) error {
+	// TODO
+	return nil
+}
+
+// compileFConvertFromI adds instructions to replace the top value of int type on the stack with
+// the corresponding float value. This is equivalent to float32(uint32(x)), float32(int32(x)), etc in Go.
+func (c *amd64Compiler) compileFConvertFromI(o *wazeroir.OperationFConvertFromI) (err error) {
+	if o.OutputType == wazeroir.Float32 && o.InputType == wazeroir.SignedInt32 {
+		err = c.emitSimpleIntToFloatConversion(x86.ACVTSL2SS) // = CVTSI2SS for 32bit int
+	} else if o.OutputType == wazeroir.Float32 && o.InputType == wazeroir.SignedInt64 {
+		err = c.emitSimpleIntToFloatConversion(x86.ACVTSQ2SS) // = CVTSI2SS for 64bit int
+	} else if o.OutputType == wazeroir.Float64 && o.InputType == wazeroir.SignedInt32 {
+		err = c.emitSimpleIntToFloatConversion(x86.ACVTSL2SD) // = CVTSI2SD for 32bit int
+	} else if o.OutputType == wazeroir.Float64 && o.InputType == wazeroir.SignedInt64 {
+		err = c.emitSimpleIntToFloatConversion(x86.ACVTSQ2SD) // = CVTSI2SD for 64bit int
+	} else if o.OutputType == wazeroir.Float32 && o.InputType == wazeroir.SignedUint32 {
+		// See the following link for why we use 64bit conversion for unsigned 32bit integer sources:
+		// https://stackoverflow.com/questions/41495498/fpu-operations-generated-by-gcc-during-casting-integer-to-float.
+		//
+		// Here's the summary:
+		// >> CVTSI2SS is indeed designed for converting a signed integer to a scalar single-precision float,
+		// >> not an unsigned integer like you have here. So what gives? Well, a 64-bit processor has 64-bit wide
+		// >> registers available, so the unsigned 32-bit input values can be stored as signed 64-bit intermediate values,
+		// >> which allows CVTSI2SS to be used after all.
+		err = c.emitSimpleIntToFloatConversion(x86.ACVTSQ2SS) // = CVTSI2SS for 64bit int.
+	} else if o.OutputType == wazeroir.Float64 && o.InputType == wazeroir.SignedUint32 {
+		// For the same reason above, we use 64bit conversion for unsigned 32bit.
+		err = c.emitSimpleIntToFloatConversion(x86.ACVTSQ2SD) // = CVTSI2SD for 64bit int.
+	} else if o.OutputType == wazeroir.Float32 && o.InputType == wazeroir.SignedUint64 {
+		err = c.emitUnsignedInt64ToFloatConversion(true)
+	} else if o.OutputType == wazeroir.Float64 && o.InputType == wazeroir.SignedUint64 {
+		err = c.emitUnsignedInt64ToFloatConversion(false)
+	}
+	return
+}
+
+// emitUnsignedInt64ToFloatConversion is handling the case of unsigned 64-bit integer
+// in compileFConvertFromI.
+func (c *amd64Compiler) emitUnsignedInt64ToFloatConversion(isFloat32bit bool) error {
+	// The logic here is exactly the same as GCC emits for the following code:
+	//
+	// float convert(int num) {
+	//     float foo;
+	//     uint64_t ptr1 = 100;
+	//     foo = (float)(ptr1);
+	//     return foo;
+	// }
+	//
+	// which is compiled by GCC as
+	//
+	// convert:
+	// 	   push    rbp
+	// 	   mov     rbp, rsp
+	// 	   mov     DWORD PTR [rbp-20], edi
+	// 	   mov     DWORD PTR [rbp-4], 100
+	// 	   mov     eax, DWORD PTR [rbp-4]
+	// 	   test    rax, rax
+	// 	   js      .handle_sign_bit_case
+	// 	   cvtsi2ss        xmm0, rax
+	// 	   jmp     .exit
+	// .handle_sign_bit_case:
+	// 	   mov     rdx, rax
+	// 	   shr     rdx
+	// 	   and     eax, 1
+	// 	   or      rdx, rax
+	// 	   cvtsi2ss        xmm0, rdx
+	// 	   addsd   xmm0, xmm0
+	// .exit: ...
+	//
+	// tl;dr is that we have a branch depending on whether or not sign bit is set.
+
+	origin := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(origin); err != nil {
+		return err
+	}
+
+	dest, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
+	if err != nil {
+		return err
+	}
+
+	// Check if the most significant bit (sign bit) is set.
+	test := c.newProg()
+	test.As = x86.ATESTQ
+	test.From.Type = obj.TYPE_REG
+	test.From.Reg = origin.register
+	test.To.Type = obj.TYPE_REG
+	test.To.Reg = origin.register
+	c.addInstruction(test)
+
+	// Jump if the sign bit is set.
+	jmpIfSignbitSet := c.newProg()
+	jmpIfSignbitSet.To.Type = obj.TYPE_BRANCH
+	jmpIfSignbitSet.As = x86.AJMI
+	c.addInstruction(jmpIfSignbitSet)
+
+	// Otherwise, we could fit the unsigned int into float32.
+	// So, we convert it to float32 and emit jump instruction to exit from this branch.
+	convert := c.newProg()
+	if isFloat32bit {
+		convert.As = x86.ACVTSQ2SS
+	} else {
+		convert.As = x86.ACVTSQ2SD
+	}
+	convert.From.Type = obj.TYPE_REG
+	convert.From.Reg = origin.register
+	convert.To.Type = obj.TYPE_REG
+	convert.To.Reg = dest
+	c.addInstruction(convert)
+
+	exitFromSignbitUnSet := c.newProg()
+	exitFromSignbitUnSet.As = obj.AJMP
+	exitFromSignbitUnSet.To.Type = obj.TYPE_BRANCH
+	c.addInstruction(exitFromSignbitUnSet)
+
+	// Now handling the case where sign-bit is set.
+	// We emit the following sequences:
+	// 	   mov     tmpReg, origin
+	// 	   shr     tmpReg, 1
+	// 	   and     origin, 1
+	// 	   or      tmpReg, origin
+	// 	   cvtsi2ss        xmm0, tmpReg
+	// 	   addsd   xmm0, xmm0
+	tmpReg, err := c.allocateRegister(generalPurposeRegisterTypeInt)
+	if err != nil {
+		return err
+	}
+
+	movToTmp := c.newProg()
+	jmpIfSignbitSet.To.SetTarget(movToTmp)
+	movToTmp.As = x86.AMOVQ
+	movToTmp.From.Type = obj.TYPE_REG
+	movToTmp.From.Reg = origin.register
+	movToTmp.To.Type = obj.TYPE_REG
+	movToTmp.To.Reg = tmpReg
+	c.addInstruction(movToTmp)
+
+	divideBy2 := c.newProg()
+	divideBy2.As = x86.ASHRQ
+	divideBy2.From.Type = obj.TYPE_CONST
+	divideBy2.From.Offset = 1
+	divideBy2.To.Type = obj.TYPE_REG
+	divideBy2.To.Reg = tmpReg
+	c.addInstruction(divideBy2)
+
+	rescueLeastSignificantBit := c.newProg()
+	rescueLeastSignificantBit.As = x86.AANDQ
+	rescueLeastSignificantBit.From.Type = obj.TYPE_CONST
+	rescueLeastSignificantBit.From.Offset = 0x1
+	rescueLeastSignificantBit.To.Type = obj.TYPE_REG
+	rescueLeastSignificantBit.To.Reg = origin.register
+	c.addInstruction(rescueLeastSignificantBit)
+
+	addRescuedBit := c.newProg()
+	addRescuedBit.As = x86.AORQ
+	addRescuedBit.From.Type = obj.TYPE_REG
+	addRescuedBit.From.Reg = origin.register
+	addRescuedBit.To.Type = obj.TYPE_REG
+	addRescuedBit.To.Reg = tmpReg
+	c.addInstruction(addRescuedBit)
+
+	convertDividedBy2Value := c.newProg()
+	if isFloat32bit {
+		convertDividedBy2Value.As = x86.ACVTSQ2SS
+	} else {
+		convertDividedBy2Value.As = x86.ACVTSQ2SD
+	}
+	convertDividedBy2Value.From.Type = obj.TYPE_REG
+	convertDividedBy2Value.From.Reg = tmpReg
+	convertDividedBy2Value.To.Type = obj.TYPE_REG
+	convertDividedBy2Value.To.Reg = dest
+	c.addInstruction(convertDividedBy2Value)
+
+	multiplyBy2 := c.newProg()
+	if isFloat32bit {
+		multiplyBy2.As = x86.AADDSS
+	} else {
+		multiplyBy2.As = x86.AADDSD
+	}
+	multiplyBy2.From.Type = obj.TYPE_REG
+	multiplyBy2.From.Reg = dest
+	multiplyBy2.To.Type = obj.TYPE_REG
+	multiplyBy2.To.Reg = dest
+	c.addInstruction(multiplyBy2)
+
+	// Now, we finished the sign-bit set branch.
+	// We have to make the exit jump target of sign-bit unset branch
+	// towards the next instruction.
+	c.addSetJmpOrigin(exitFromSignbitUnSet)
+
+	// We consumed the origin's register and placed the conversion result
+	// in the dest register.
+	c.locationStack.markRegisterUnused(origin.register)
+	loc := c.locationStack.pushValueOnRegister(dest)
+	loc.setRegisterType(generalPurposeRegisterTypeFloat)
+	return nil
+}
+
+// emitSimpleIntToFloatConversion pops a flaot type from the stack, and applies the
+// given instruction on it, and push the integer result onto the stack.
+func (c *amd64Compiler) emitSimpleIntToFloatConversion(convInstruction obj.As) error {
+	origin := c.locationStack.pop()
+	if err := c.ensureOnGeneralPurposeRegister(origin); err != nil {
+		return err
+	}
+
+	dest, err := c.allocateRegister(generalPurposeRegisterTypeFloat)
+	if err != nil {
+		return err
+	}
+
+	convert := c.newProg()
+	convert.As = convInstruction
+	convert.From.Type = obj.TYPE_REG
+	convert.From.Reg = origin.register
+	convert.To.Type = obj.TYPE_REG
+	convert.To.Reg = dest
+	c.addInstruction(convert)
+
+	c.locationStack.markRegisterUnused(origin.register)
+	loc := c.locationStack.pushValueOnRegister(dest)
+	loc.setRegisterType(generalPurposeRegisterTypeFloat)
+	return nil
+}
+
+// compileF32DemoteFromF64 adds instructions to replace the 64-bit float on top of the stack
+// with the corresponding 32-bit float. This is equivalent to float32(float64(v)) in Go.
+func (c *amd64Compiler) compileF32DemoteFromF64() error {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	convert := c.newProg()
+	convert.As = x86.ACVTSD2SS
+	convert.From.Type = obj.TYPE_REG
+	convert.From.Reg = target.register
+	convert.To.Type = obj.TYPE_REG
+	convert.To.Reg = target.register
+	c.addInstruction(convert)
+	return nil
+}
+
+// compileF64PromoteFromF32 adds instructions to replace the 32-bit float on top of the stack
+// with the corresponding 64-bit float. This is equivalent to float64(float32(v)) in Go.
+func (c *amd64Compiler) compileF64PromoteFromF32() error {
+	target := c.locationStack.peek() // Note this is peek!
+	if err := c.ensureOnGeneralPurposeRegister(target); err != nil {
+		return err
+	}
+
+	convert := c.newProg()
+	convert.As = x86.ACVTSS2SD
+	convert.From.Type = obj.TYPE_REG
+	convert.From.Reg = target.register
+	convert.To.Type = obj.TYPE_REG
+	convert.To.Reg = target.register
+	c.addInstruction(convert)
+	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 967b9da8..91582671 100644
--- a/wasm/wat/parser.go
+++ b/wasm/wat/parser.go
@@ -97,9 +97,9 @@ func (p *moduleParser) parse(tok tokenType, tokenBytes []byte, line, col uint32)
 	return p.tokenParser(tok, tokenBytes, line, col)
 }
 
-func (p *moduleParser) ensureLParen(tok tokenType, _ []byte, _, _ uint32) error {
+func (p *moduleParser) ensureLParen(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	if tok != tokenLParen {
-		return fmt.Errorf("expected '(', but found %s", tok)
+		return fmt.Errorf("expected '(', but found %s: %s", tok, tokenBytes)
 	}
 	p.tokenParser = p.beginField
 	return nil
@@ -142,13 +142,12 @@ func (p *moduleParser) beginField(tok tokenType, fieldName []byte, _, _ uint32)
 		}
 	case fieldModuleImport:
 		// 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
-			}
+		if string(fieldName) == "func" {
+			p.module.importFuncs = append(p.module.importFuncs, &importFunc{
+				module:      string(p.currentValue0),
+				name:        string(p.currentValue1),
+				importIndex: p.currentImportIndex,
+			})
 			p.currentField = fieldModuleImportFunc
 			p.tokenParser = p.parseImportFunc
 		} // TODO: table, memory or global
@@ -178,7 +177,7 @@ func (p *moduleParser) beginField(tok tokenType, fieldName []byte, _, _ uint32)
 //
 // 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 {
+func (p *moduleParser) endField() {
 	switch p.currentField {
 	case fieldModuleImportFuncParam, fieldModuleImportFuncResult:
 		p.currentField = fieldModuleImportFunc
@@ -191,11 +190,10 @@ func (p *moduleParser) endField() error {
 		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
+		p.tokenParser = p.parseUnexpectedTrailingCharacters // only one module is allowed and nothing else
+	default: // currentField is an enum, we expect to have handled all cases above. panic if we didn't
+		panic(fmt.Errorf("BUG: unhandled parsing state: %v", p.currentField))
 	}
-	return nil
 }
 
 func (p *moduleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ uint32) error {
@@ -210,7 +208,7 @@ func (p *moduleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ uint32
 		p.tokenParser = p.beginField // after this look for a field name
 		return nil
 	case tokenRParen: // end of module
-		return p.endField()
+		p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
@@ -230,21 +228,31 @@ func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32
 			return fmt.Errorf("redundant name: %s", name)
 		}
 	case tokenLParen: // start fields, ex. (func
+		// Err if there's a second description. Ex. (import "" "" (func) (func))
+		if uint32(len(p.module.importFuncs)) > p.currentImportIndex {
+			return p.unexpectedToken(tok, tokenBytes)
+		}
+		// Err if there are not enough names when we reach a description. Ex. (import func())
+		if err := p.validateImportModuleAndName(); err != nil {
+			return err
+		}
 		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.
+		// Err if we never reached a description...
 		if uint32(len(p.module.importFuncs)) == p.currentImportIndex {
 			if err := p.validateImportModuleAndName(); err != nil {
-				return err // Ex. (module) or (module "Math")
+				return err // Ex. missing (func) and names: (import) or (import "Math")
 			}
-			return errors.New("missing description field") // Ex. (module "Math" "Pi")
+			return errors.New("missing description field") // Ex. missing (func): (import "Math" "Pi")
 		}
 
-		// multiple imports are allowed, so advance in case there's a next.
+		// Multiple imports are allowed, so advance in case there's a next.
 		p.currentImportIndex++
 
-		return p.endField()
+		// Reset parsing state: this is late to help give correct error messages on multiple descriptions.
+		p.currentValue0, p.currentValue1 = nil, nil
+		p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
@@ -261,19 +269,6 @@ 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
@@ -310,9 +305,8 @@ func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, _, _ ui
 		}
 
 		// reset parsing state
-		p.currentValue0, p.currentValue1 = nil, nil
 		p.currentParamIndex = 0
-		return p.endField()
+		p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
@@ -328,11 +322,6 @@ func (p *moduleParser) parseImportFuncParam(tok tokenType, tokenBytes []byte, _,
 		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 {
@@ -349,7 +338,7 @@ func (p *moduleParser) parseImportFuncParam(tok tokenType, tokenBytes []byte, _,
 		p.currentParams = nil
 		p.currentParamIndex++
 
-		return p.endField()
+		p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
@@ -359,7 +348,11 @@ func (p *moduleParser) parseImportFuncParam(tok tokenType, tokenBytes []byte, _,
 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)
+		if p.currentType == nil { // Ex. no params: (func (result i32))
+			p.currentType = &typeFunc{}
+		}
+
+		if p.currentType.results != nil { // Ex. double result: (func (result i32 i32))
 			return errors.New("redundant type")
 		}
 
@@ -369,16 +362,12 @@ func (p *moduleParser) parseImportFuncResult(tok tokenType, tokenBytes []byte, _
 		}
 
 		// 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()
+		p.endField()
 	default:
 		return p.unexpectedToken(tok, tokenBytes)
 	}
@@ -414,20 +403,20 @@ func (p *moduleParser) parseStart(tok tokenType, tokenBytes []byte, line, col ui
 		if p.module.startFunction == nil {
 			return errors.New("missing funcidx")
 		}
-		return p.endField()
+		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) parseUnexpectedTrailingCharacters(_ tokenType, tokenBytes []byte, _, _ uint32) error {
+	return fmt.Errorf("unexpected trailing characters: %s", tokenBytes)
 }
 
 func (p *moduleParser) unexpectedToken(tok tokenType, tokenBytes []byte) error {
-	if tok == tokenLParen || tok == tokenRParen {
-		return fmt.Errorf("unexpected %s", tok)
+	if tok == tokenLParen { // unbalanced tokenRParen is caught at the lexer layer
+		return errors.New("unexpected '('")
 	}
 	return fmt.Errorf("unexpected %s: %s", tok, tokenBytes)
 }
@@ -452,6 +441,7 @@ func (p *moduleParser) errorContext() string {
 		}
 		// TODO: table, memory or global
 		return fmt.Sprintf("module.import[%d]", p.currentImportIndex)
+	default: // currentField is an enum, we expect to have handled all cases above. panic if we didn't
+		panic(fmt.Errorf("BUG: unhandled parsing state: %v", p.currentField))
 	}
-	return ""
 }
diff --git a/wasm/wazeroir/interpreter.go b/wasm/wazeroir/interpreter.go
index d7239280..c8f671f0 100644
--- a/wasm/wazeroir/interpreter.go
+++ b/wasm/wazeroir/interpreter.go
@@ -1270,11 +1270,11 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 					// Float32
 					v2 := math.Float32frombits(uint32(it.pop()))
 					v1 := math.Float32frombits(uint32(it.pop()))
-					it.push(uint64(math.Float32bits(float32(min(float64(v1), float64(v2))))))
+					it.push(uint64(math.Float32bits(float32(Min(float64(v1), float64(v2))))))
 				} else {
 					v2 := math.Float64frombits(it.pop())
 					v1 := math.Float64frombits(it.pop())
-					it.push(math.Float64bits(min(v1, v2)))
+					it.push(math.Float64bits(Min(v1, v2)))
 				}
 				frame.pc++
 			}
@@ -1285,12 +1285,12 @@ func (it *interpreter) callNativeFunc(f *interpreterFunction) {
 					// Float32
 					v2 := math.Float32frombits(uint32(it.pop()))
 					v1 := math.Float32frombits(uint32(it.pop()))
-					it.push(uint64(math.Float32bits(float32(max(float64(v1), float64(v2))))))
+					it.push(uint64(math.Float32bits(float32(Max(float64(v1), float64(v2))))))
 				} else {
 					// Float64
 					v2 := math.Float64frombits(it.pop())
 					v1 := math.Float64frombits(it.pop())
-					it.push(math.Float64bits(max(v1, v2)))
+					it.push(math.Float64bits(Max(v1, v2)))
 				}
 				frame.pc++
 			}
@@ -1483,7 +1483,7 @@ func funcTypeString(t *wasm.FunctionType) string {
 // math.Min doen't comply with the Wasm spec, so we borrow from the original
 // with a change that either one of NaN results in NaN even if another is -Inf.
 // https://github.com/golang/go/blob/1d20a362d0ca4898d77865e314ef6f73582daef0/src/math/dim.go#L74-L91
-func min(x, y float64) float64 {
+func Min(x, y float64) float64 {
 	switch {
 	case math.IsNaN(x) || math.IsNaN(y):
 		return math.NaN()
@@ -1504,7 +1504,7 @@ func min(x, y float64) float64 {
 // math.Max doen't comply with the Wasm spec, so we borrow from the original
 // with a change that either one of NaN results in NaN even if another is Inf.
 // https://github.com/golang/go/blob/1d20a362d0ca4898d77865e314ef6f73582daef0/src/math/dim.go#L42-L59
-func max(x, y float64) float64 {
+func Max(x, y float64) float64 {
 	switch {
 	case math.IsNaN(x) || math.IsNaN(y):
 		return math.NaN()
diff --git a/wasm/wazeroir/operations.go b/wasm/wazeroir/operations.go
index 48641f86..0f62910d 100644
--- a/wasm/wazeroir/operations.go
+++ b/wasm/wazeroir/operations.go
@@ -31,13 +31,13 @@ const (
 func (s SignedInt) String() (ret string) {
 	switch s {
 	case SignedUint32:
-		ret = "i32"
+		ret = "u32"
 	case SignedUint64:
-		ret = "i64"
+		ret = "u64"
 	case SignedInt32:
-		ret = "u32"
+		ret = "s32"
 	case SignedInt64:
-		ret = "u64"
+		ret = "s64"
 	}
 	return
 }
@@ -99,11 +99,11 @@ const (
 func (s SignedType) String() (ret string) {
 	switch s {
 	case SignedTypeInt32:
-		ret = "i32"
+		ret = "s32"
 	case SignedTypeUint32:
 		ret = "u32"
 	case SignedTypeInt64:
-		ret = "i64"
+		ret = "s64"
 	case SignedTypeUint64:
 		ret = "u64"
 	case SignedTypeFloat32:


Test patch:
diff --git a/wasm/jit/jit_amd64_test.go b/wasm/jit/jit_amd64_test.go
index 41505b5a..ec5ed358 100644
--- a/wasm/jit/jit_amd64_test.go
+++ b/wasm/jit/jit_amd64_test.go
@@ -30,6 +30,14 @@ func newMemoryInst() *wasm.MemoryInstance {
 	return &wasm.MemoryInstance{Buffer: make([]byte, 1024)}
 }
 
+func stackTopAsFloat32(eng *engine) float32 {
+	return math.Float32frombits(uint32(eng.stack[eng.stackPointer-1]))
+}
+
+func stackTopAsFloat64(eng *engine) float64 {
+	return math.Float64frombits(eng.stack[eng.stackPointer-1])
+}
+
 func requireNewCompiler(t *testing.T) *amd64Compiler {
 	b, err := asm.NewBuilder("amd64", 128)
 	require.NoError(t, err)
@@ -872,7 +880,7 @@ func TestAmd64Compiler_compileConstF32(t *testing.T) {
 			// As we push the constant to the stack, the stack pointer must be incremented.
 			require.Equal(t, uint64(1), eng.stackPointer)
 			// Check the value of the top on the stack equals the squared const.
-			require.Equal(t, o.Value*2, math.Float32frombits(uint32(eng.stack[eng.stackPointer-1])))
+			require.Equal(t, o.Value*2, stackTopAsFloat32(eng))
 		})
 	}
 }
@@ -916,7 +924,7 @@ func TestAmd64Compiler_compileConstF64(t *testing.T) {
 			// As we push the constant to the stack, the stack pointer must be incremented.
 			require.Equal(t, uint64(1), eng.stackPointer)
 			// Check the value of the top on the stack equals the squared const.
-			require.Equal(t, o.Value*2, math.Float64frombits(eng.stack[eng.stackPointer-1]))
+			require.Equal(t, o.Value*2, stackTopAsFloat64(eng))
 		})
 	}
 }
@@ -1041,7 +1049,7 @@ func TestAmd64Compiler_compileAdd(t *testing.T) {
 				)
 				// Check the stack.
 				require.Equal(t, uint64(1), eng.stackPointer)
-				require.Equal(t, tc.v1+tc.v2, math.Float32frombits(uint32(eng.stack[eng.stackPointer-1])))
+				require.Equal(t, tc.v1+tc.v2, stackTopAsFloat32(eng))
 			})
 		}
 	})
@@ -1090,7 +1098,7 @@ func TestAmd64Compiler_compileAdd(t *testing.T) {
 				)
 				// Check the stack.
 				require.Equal(t, uint64(1), eng.stackPointer)
-				require.Equal(t, tc.v1+tc.v2, math.Float64frombits(eng.stack[eng.stackPointer-1]))
+				require.Equal(t, tc.v1+tc.v2, stackTopAsFloat64(eng))
 			})
 		}
 	})
@@ -2248,7 +2256,7 @@ func TestAmd64Compiler_compileSub(t *testing.T) {
 				)
 				// Check the stack.
 				require.Equal(t, uint64(1), eng.stackPointer)
-				require.Equal(t, tc.v1-tc.v2, math.Float32frombits(uint32(eng.stack[eng.stackPointer-1])))
+				require.Equal(t, tc.v1-tc.v2, stackTopAsFloat32(eng))
 			})
 		}
 	})
@@ -2297,7 +2305,7 @@ func TestAmd64Compiler_compileSub(t *testing.T) {
 				)
 				// Check the stack.
 				require.Equal(t, uint64(1), eng.stackPointer)
-				require.Equal(t, tc.v1-tc.v2, math.Float64frombits(eng.stack[eng.stackPointer-1]))
+				require.Equal(t, tc.v1-tc.v2, stackTopAsFloat64(eng))
 			})
 		}
 	})
@@ -2581,7 +2589,7 @@ func TestAmd64Compiler_compileMul(t *testing.T) {
 				)
 				// Check the stack.
 				require.Equal(t, uint64(1), eng.stackPointer)
-				require.Equal(t, tc.x1*tc.x2, math.Float32frombits(uint32(eng.stack[eng.stackPointer-1])))
+				require.Equal(t, tc.x1*tc.x2, stackTopAsFloat32(eng))
 			})
 		}
 	})
@@ -2634,7 +2642,7 @@ func TestAmd64Compiler_compileMul(t *testing.T) {
 				)
 				// Check the stack.
 				require.Equal(t, uint64(1), eng.stackPointer)
-				require.Equal(t, tc.x1*tc.x2, math.Float64frombits(eng.stack[eng.stackPointer-1]))
+				require.Equal(t, tc.x1*tc.x2, stackTopAsFloat64(eng))
 			})
 		}
 	})
@@ -2666,7 +2674,7 @@ func TestAmd64Compiler_compilClz(t *testing.T) {
 				require.True(t, compiler.locationStack.peek().onRegister())
 				// On darwin, we have two branches and one must jump to the next
 				// instruction after compileClz.
-				require.True(t, runtime.GOOS != "darwin" || compiler.setJmpOrigin != nil)
+				require.True(t, runtime.GOOS != "darwin" || len(compiler.setJmpOrigins) > 0)
 
 				// To verify the behavior, we release the value
 				// to the stack.
@@ -2717,7 +2725,7 @@ func TestAmd64Compiler_compilClz(t *testing.T) {
 				require.True(t, compiler.locationStack.peek().onRegister())
 				// On darwin, we have two branches and one must jump to the next
 				// instruction after compileClz.
-				require.True(t, runtime.GOOS != "darwin" || compiler.setJmpOrigin != nil)
+				require.True(t, runtime.GOOS != "darwin" || len(compiler.setJmpOrigins) > 0)
 
 				// To verify the behavior, we release the value
 				// to the stack.
@@ -2769,7 +2777,7 @@ func TestAmd64Compiler_compilCtz(t *testing.T) {
 				require.True(t, compiler.locationStack.peek().onRegister())
 				// On darwin, we have two branches and one must jump to the next
 				// instruction after compileCtz.
-				require.True(t, runtime.GOOS != "darwin" || compiler.setJmpOrigin != nil)
+				require.True(t, runtime.GOOS != "darwin" || len(compiler.setJmpOrigins) > 0)
 
 				// To verify the behavior, we release the value
 				// to the stack.
@@ -2820,7 +2828,7 @@ func TestAmd64Compiler_compilCtz(t *testing.T) {
 				require.True(t, compiler.locationStack.peek().onRegister())
 				// On darwin, we have two branches and one must jump to the next
 				// instruction after compileCtz.
-				require.True(t, runtime.GOOS != "darwin" || compiler.setJmpOrigin != nil)
+				require.True(t, runtime.GOOS != "darwin" || len(compiler.setJmpOrigins) > 0)
 
 				// To verify the behavior, we release the value
 				// to the stack.
@@ -3455,6 +3463,11 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 			{x1: float32(math.Inf(-1)), x2: 0},
 			{x1: 0, x2: float32(math.Inf(1))},
 			{x1: 0, x2: float32(math.Inf(-1))},
+			{x1: float32(math.NaN()), x2: 0},
+			{x1: 0, x2: float32(math.NaN())},
+			{x1: float32(math.NaN()), x2: 12321},
+			{x1: 12313, x2: float32(math.NaN())},
+			{x1: float32(math.NaN()), x2: float32(math.NaN())},
 		} {
 			tc := tc
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
@@ -3492,7 +3505,7 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 				// Check the result.
 				require.Equal(t, uint64(1), eng.stackPointer)
 				exp := tc.x1 / tc.x2
-				actual := math.Float32frombits(uint32(eng.stack[eng.stackPointer-1]))
+				actual := stackTopAsFloat32(eng)
 				if math.IsNaN(float64(exp)) {
 					require.True(t, math.IsNaN(float64(actual)))
 				} else {
@@ -3527,6 +3540,11 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 			{x1: math.Inf(-1), x2: 0},
 			{x1: 0, x2: math.Inf(1)},
 			{x1: 0, x2: math.Inf(-1)},
+			{x1: math.NaN(), x2: 0},
+			{x1: 0, x2: math.NaN()},
+			{x1: math.NaN(), x2: 12321},
+			{x1: 12313, x2: math.NaN()},
+			{x1: math.NaN(), x2: math.NaN()},
 		} {
 			tc := tc
 			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
@@ -3564,7 +3582,7 @@ func TestAmd64Compiler_compileDiv(t *testing.T) {
 				// Check the result.
 				require.Equal(t, uint64(1), eng.stackPointer)
 				exp := tc.x1 / tc.x2
-				actual := math.Float64frombits(eng.stack[eng.stackPointer-1])
+				actual := stackTopAsFloat64(eng)
 				if math.IsNaN(exp) {
 					require.True(t, math.IsNaN(actual))
 				} else {
@@ -3877,6 +3895,194 @@ func TestAmd64Compiler_compileRem(t *testing.T) {
 	})
 }
 
+func TestAmd64Compiler_compileF32DemoteFromF64(t *testing.T) {
+	for _, v := range []float64{
+		0, 100, -100, 1, -1,
+		100.01234124, -100.01234124, 200.12315,
+		math.MaxFloat32,
+		math.SmallestNonzeroFloat32,
+		math.MaxFloat64,
+		math.SmallestNonzeroFloat64,
+		6.8719476736e+10,  /* = 1 << 36 */
+		1.37438953472e+11, /* = 1 << 37 */
+		math.Inf(1), math.Inf(-1), math.NaN(),
+	} {
+		t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
+
+			// Setup the demote target.
+			err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: v})
+			require.NoError(t, err)
+
+			err = compiler.compileF32DemoteFromF64()
+			require.NoError(t, err)
+
+			// 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)
+			eng := newEngine()
+			jitcall(uintptr(unsafe.Pointer(&code[0])), uintptr(unsafe.Pointer(eng)), 0)
+
+			// Check the result.
+			require.Equal(t, uint64(1), eng.stackPointer)
+			if math.IsNaN(v) {
+				require.True(t, math.IsNaN(float64(stackTopAsFloat32(eng))))
+			} else {
+				exp := float32(v)
+				actual := stackTopAsFloat32(eng)
+				require.Equal(t, exp, actual)
+			}
+		})
+	}
+}
+
+func TestAmd64Compiler_compileF64PromoteFromF32(t *testing.T) {
+	for _, v := range []float32{
+		0, 100, -100, 1, -1,
+		100.01234124, -100.01234124, 200.12315,
+		math.MaxFloat32,
+		math.SmallestNonzeroFloat32,
+		float32(math.Inf(1)), float32(math.Inf(-1)), float32(math.NaN()),
+	} {
+		t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
+			compiler := requireNewCompiler(t)
+			compiler.initializeReservedRegisters()
+
+			// Setup the promote target.
+			err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: v})
+			require.NoError(t, err)
+
+			err = compiler.compileF64PromoteFromF32()
+			require.NoError(t, err)
+
+			// 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)
+			eng := newEngine()
+			jitcall(uintptr(unsafe.Pointer(&code[0])), uintptr(unsafe.Pointer(eng)), 0)
+
+			// Check the result.
+			require.Equal(t, uint64(1), eng.stackPointer)
+			if math.IsNaN(float64(v)) {
+				require.True(t, math.IsNaN(stackTopAsFloat64(eng)))
+			} else {
+				exp := float64(v)
+				actual := stackTopAsFloat64(eng)
+				require.Equal(t, exp, actual)
+			}
+		})
+	}
+}
+
+func TestAmd64Compiler_compileFConvertFromI(t *testing.T) {
+	for _, tc := range []struct {
+		inputType  wazeroir.SignedInt
+		outputType wazeroir.Float
+	}{
+		{inputType: wazeroir.SignedInt32, outputType: wazeroir.Float32},
+		{inputType: wazeroir.SignedInt32, outputType: wazeroir.Float64},
+		{inputType: wazeroir.SignedInt64, outputType: wazeroir.Float32},
+		{inputType: wazeroir.SignedInt64, outputType: wazeroir.Float64},
+		{inputType: wazeroir.SignedUint32, outputType: wazeroir.Float32},
+		{inputType: wazeroir.SignedUint32, outputType: wazeroir.Float64},
+		{inputType: wazeroir.SignedUint64, outputType: wazeroir.Float32},
+		{inputType: wazeroir.SignedUint64, outputType: wazeroir.Float64},
+	} {
+		tc := tc
+		t.Run(fmt.Sprintf("%s from %s", tc.outputType, tc.inputType), func(t *testing.T) {
+			for _, v := range []uint64{
+				0, 1, 12345, 1 << 31, 1 << 32, 1 << 54, 1 << 63,
+				math.MaxUint32, math.MaxUint64, math.MaxInt32, math.MaxInt64,
+				math.Float64bits(1.23455555),
+				math.Float64bits(-1.23455555),
+				math.Float64bits(math.NaN()),
+				math.Float64bits(math.Inf(1)),
+				math.Float64bits(math.Inf(-1)),
+			} {
+				t.Run(fmt.Sprintf("%d", v), func(t *testing.T) {
+					compiler := requireNewCompiler(t)
+					compiler.initializeReservedRegisters()
+
+					// Setup the conversion target.
+					var err error
+					if tc.inputType == wazeroir.SignedInt32 || tc.inputType == wazeroir.SignedUint32 {
+						err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
+					} else {
+						err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(v)})
+					}
+					require.NoError(t, err)
+
+					err = compiler.compileFConvertFromI(&wazeroir.OperationFConvertFromI{
+						InputType: tc.inputType, OutputType: tc.outputType,
+					})
+					require.NoError(t, err)
+
+					// 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)
+					eng := newEngine()
+					jitcall(uintptr(unsafe.Pointer(&code[0])), uintptr(unsafe.Pointer(eng)), 0)
+
+					// Check the result.
+					require.Equal(t, uint64(1), eng.stackPointer)
+					actualBits := eng.stack[eng.stackPointer-1]
+					if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedInt32 {
+						exp := float32(int32(v))
+						actual := math.Float32frombits(uint32(actualBits))
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedInt64 {
+						exp := float32(int64(v))
+						actual := math.Float32frombits(uint32(actualBits))
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedInt32 {
+						exp := float64(int32(v))
+						actual := math.Float64frombits(actualBits)
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedInt64 {
+						exp := float64(int64(v))
+						actual := math.Float64frombits(actualBits)
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedUint32 {
+						exp := float32(uint32(v))
+						actual := math.Float32frombits(uint32(actualBits))
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedUint32 {
+						exp := float64(uint32(v))
+						actual := math.Float64frombits(actualBits)
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float32 && tc.inputType == wazeroir.SignedUint64 {
+						exp := float32(v)
+						actual := math.Float32frombits(uint32(actualBits))
+						require.Equal(t, exp, actual)
+					} else if tc.outputType == wazeroir.Float64 && tc.inputType == wazeroir.SignedUint64 {
+						exp := float64(v)
+						actual := math.Float64frombits(actualBits)
+						require.Equal(t, exp, actual)
+					} else {
+						t.Fatal()
+					}
+				})
+			}
+		})
+	}
+}
+
 func TestAmd64Compiler_compile_abs_neg_ceil_floor(t *testing.T) {
 	for _, tc := range []struct {
 		name string
@@ -4067,14 +4273,14 @@ func TestAmd64Compiler_compile_abs_neg_ceil_floor(t *testing.T) {
 					// Check the result.
 					require.Equal(t, uint64(1), eng.stackPointer)
 					if is32Bit {
-						actual := math.Float32frombits(uint32(eng.stack[eng.stackPointer-1]))
+						actual := stackTopAsFloat32(eng)
 						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])
+						actual := stackTopAsFloat64(eng)
 						if math.IsNaN(expFloat64) {
 							require.True(t, math.IsNaN(actual))
 						} else {
@@ -4087,6 +4293,149 @@ func TestAmd64Compiler_compile_abs_neg_ceil_floor(t *testing.T) {
 	}
 }
 
+func TestAmd64Compiler_compile_min_max_copysign(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		op   wazeroir.Operation
+	}{
+		{name: "min-32-bit", op: &wazeroir.OperationMin{Type: wazeroir.Float32}},
+		{name: "min-64-bit", op: &wazeroir.OperationMin{Type: wazeroir.Float64}},
+		{name: "max-32-bit", op: &wazeroir.OperationMax{Type: wazeroir.Float32}},
+		{name: "max-64-bit", op: &wazeroir.OperationMax{Type: wazeroir.Float64}},
+		{name: "copysign-32-bit", op: &wazeroir.OperationCopysign{Type: wazeroir.Float32}},
+		{name: "copysign-64-bit", op: &wazeroir.OperationCopysign{Type: wazeroir.Float64}},
+	} {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			for _, vs := range []struct{ x1, x2 float64 }{
+				{x1: 100, x2: -1.1},
+				{x1: 100, x2: 0},
+				{x1: 0, x2: 0},
+				{x1: 1, x2: 1},
+				{x1: -1, x2: 100},
+				{x1: 100, x2: 200},
+				{x1: 100.01234124, x2: 100.01234124},
+				{x1: 100.01234124, x2: -100.01234124},
+				{x1: 200.12315, x2: 100},
+				{x1: 6.8719476736e+10 /* = 1 << 36 */, x2: 100},
+				{x1: 6.8719476736e+10 /* = 1 << 36 */, x2: 1.37438953472e+11 /* = 1 << 37*/},
+				{x1: math.Inf(1), x2: 100},
+				{x1: math.Inf(1), x2: -100},
+				{x1: 100, x2: math.Inf(1)},
+				{x1: -100, x2: math.Inf(1)},
+				{x1: math.Inf(-1), x2: 100},
+				{x1: math.Inf(-1), x2: -100},
+				{x1: 100, x2: math.Inf(-1)},
+				{x1: -100, x2: math.Inf(-1)},
+				{x1: math.Inf(1), x2: 0},
+				{x1: math.Inf(-1), x2: 0},
+				{x1: 0, x2: math.Inf(1)},
+				{x1: 0, x2: math.Inf(-1)},
+				{x1: math.NaN(), x2: 0},
+				{x1: 0, x2: math.NaN()},
+				{x1: math.NaN(), x2: 12321},
+				{x1: 12313, x2: math.NaN()},
+				{x1: math.NaN(), x2: math.NaN()},
+			} {
+				t.Run(fmt.Sprintf("x1=%f_x2=%f", vs.x1, vs.x2), 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.OperationMin:
+						compileOperationFunc = func() {
+							err := compiler.compileMin(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(wazeroir.Min(float64(float32(vs.x1)), float64(float32(vs.x2))))
+						} else {
+							expFloat64 = wazeroir.Min(vs.x1, vs.x2)
+						}
+					case *wazeroir.OperationMax:
+						compileOperationFunc = func() {
+							err := compiler.compileMax(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(wazeroir.Max(float64(float32(vs.x1)), float64(float32(vs.x2))))
+						} else {
+							expFloat64 = wazeroir.Max(vs.x1, vs.x2)
+						}
+					case *wazeroir.OperationCopysign:
+						compileOperationFunc = func() {
+							err := compiler.compileCopysign(o)
+							require.NoError(t, err)
+						}
+						is32Bit = o.Type == wazeroir.Float32
+						if is32Bit {
+							expFloat32 = float32(math.Copysign(float64(float32(vs.x1)), float64(float32(vs.x2))))
+						} else {
+							expFloat64 = math.Copysign(vs.x1, vs.x2)
+						}
+					}
+
+					// Setup the target values.
+					if is32Bit {
+						err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(vs.x1)})
+						require.NoError(t, err)
+						err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(vs.x2)})
+						require.NoError(t, err)
+					} else {
+						err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: vs.x1})
+						require.NoError(t, err)
+						err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: vs.x2})
+						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 := stackTopAsFloat32(eng)
+						if math.IsNaN(float64(expFloat32)) {
+							require.True(t, math.IsNaN(float64(actual)), actual)
+						} else {
+							require.Equal(t, expFloat32, actual)
+						}
+					} else {
+						actual := stackTopAsFloat64(eng)
+						if math.IsNaN(expFloat64) {
+							require.True(t, math.IsNaN(actual), 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/convert_test.go b/wasm/wat/convert_test.go
index e11d64dc..cb19249a 100644
--- a/wasm/wat/convert_test.go
+++ b/wasm/wat/convert_test.go
@@ -13,6 +13,7 @@ import (
 
 func TestTextToBinary(t *testing.T) {
 	zero := uint32(0)
+	i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
 	tests := []struct {
 		name     string
 		input    string
@@ -38,22 +39,28 @@ func TestTextToBinary(t *testing.T) {
 			},
 		},
 		{
-			name:  "import func two",
-			input: "(module (import \"foo\" \"bar\" (func)) (import \"baz\" \"qux\" (func)))",
+			name: "multiple import func with different inlined type",
+			input: `(module
+	(import "wasi_snapshot_preview1" "path_open" (func $runtime.path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))
+	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
+)`,
 			expected: &wasm.Module{
-				TypeSection: []*wasm.FunctionType{{}},
+				TypeSection: []*wasm.FunctionType{
+					{Params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32}, Results: []wasm.ValueType{i32}},
+					{Params: []wasm.ValueType{i32, i32, i32, i32}, Results: []wasm.ValueType{i32}},
+				},
 				ImportSection: []*wasm.ImportSegment{
 					{
-						Module: "foo", Name: "bar",
+						Module: "wasi_snapshot_preview1", Name: "path_open",
 						Desc: &wasm.ImportDesc{
 							Kind:          wasm.ImportKindFunction,
 							FuncTypeIndex: 0,
 						},
 					}, {
-						Module: "baz", Name: "qux",
+						Module: "wasi_snapshot_preview1", Name: "fd_write",
 						Desc: &wasm.ImportDesc{
 							Kind:          wasm.ImportKindFunction,
-							FuncTypeIndex: 0,
+							FuncTypeIndex: 1,
 						},
 					},
 				},
@@ -110,6 +117,11 @@ func TestTextToBinary(t *testing.T) {
 
 func TestTextToBinary_Errors(t *testing.T) {
 	tests := []struct{ name, input, expectedErr string }{
+		{
+			name:        "invalid",
+			input:       "module",
+			expectedErr: "1:1: expected '(', but found keyword: module",
+		},
 		{
 			name:        "start, but no funcs",
 			input:       "(module (start $main))",
diff --git a/wasm/wat/lexer_test.go b/wasm/wat/lexer_test.go
index 44ce2370..abc14565 100644
--- a/wasm/wat/lexer_test.go
+++ b/wasm/wat/lexer_test.go
@@ -507,6 +507,13 @@ func TestLex_Errors(t *testing.T) {
 			expectedCol:  2,
 			expectedErr:  "expected an ASCII character, not 私",
 		},
+		{
+			name:         "truncated string",
+			input:        []byte("\"hello"),
+			expectedLine: 1,
+			expectedCol:  6,
+			expectedErr:  "expected end quote",
+		},
 		{
 			name:         "0x80 in block comment",
 			input:        []byte("(; \200)"),
diff --git a/wasm/wat/parser_test.go b/wasm/wat/parser_test.go
index c61b31e6..6551aca7 100644
--- a/wasm/wat/parser_test.go
+++ b/wasm/wat/parser_test.go
@@ -47,7 +47,33 @@ func TestParseModule(t *testing.T) {
 		{
 			name: "import func inlined type",
 			input: `(module
+	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32) (param i32) (param i32) (param 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 - abbreviated",
+			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"},
+				},
+			},
+		},
+		{
+			// Spec says expand abbreviations first. It doesn't explicitly say you can't mix forms.
+			// See https://www.w3.org/TR/wasm-core-1/#abbreviations%E2%91%A0
+			name: "import func inlined type - mixed abbreviated",
+			input: `(module
+	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32) (param i32 i32) (param i32) (result i32)))
 )`,
 			expected: &module{
 				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}}},
@@ -68,9 +94,32 @@ func TestParseModule(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:  "import func inlined type no param",
+			input: `(module (import "" "" (func (result i32))))`,
+			expected: &module{
+				types:       []*typeFunc{{results: []wasm.ValueType{i32}}},
+				importFuncs: []*importFunc{{}},
+			},
+		},
 		{
 			name: "import func inlined type different param types",
 			input: `(module
+	(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{
+				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: "import func inlined type different param types - abbreviated",
+			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{
@@ -158,8 +207,39 @@ func TestParseModule(t *testing.T) {
 	}
 }
 
+func TestParseValueType(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected wasm.ValueType
+	}{
+		{"i32", wasm.ValueTypeI32},
+		{"i64", wasm.ValueTypeI64},
+		{"f32", wasm.ValueTypeF32},
+		{"f64", wasm.ValueTypeF64},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.input, func(t *testing.T) {
+			m, err := parseValueType([]byte(tc.input))
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, m)
+		})
+	}
+	t.Run("unknown type", func(t *testing.T) {
+		_, err := parseValueType([]byte("f65"))
+		require.EqualError(t, err, "unknown type: f65")
+	})
+}
+
 func TestParseModule_Errors(t *testing.T) {
 	tests := []struct{ name, input, expectedErr string }{
+		{
+			name:        "forgot parens",
+			input:       "module",
+			expectedErr: "1:1: expected '(', but found keyword: module",
+		},
 		{
 			name:        "no module",
 			input:       "()",
@@ -168,13 +248,18 @@ func TestParseModule_Errors(t *testing.T) {
 		{
 			name:        "double module",
 			input:       "(module) (module)",
-			expectedErr: "1:10: unexpected (",
+			expectedErr: "1:10: unexpected trailing characters: (",
 		},
 		{
 			name:        "module invalid name",
 			input:       "(module test)", // must start with $
 			expectedErr: "1:9: unexpected keyword: test in module",
 		},
+		{
+			name:        "module invalid field",
+			input:       "(module (test))",
+			expectedErr: "1:10: unexpected field: test in module",
+		},
 		{
 			name:        "module double name",
 			input:       "(module $foo $bar)",
@@ -195,11 +280,21 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (import))",
 			expectedErr: "1:16: missing module and name in module.import[0]",
 		},
+		{
+			name:        "import with desc, but no module",
+			input:       "(module (import (func))",
+			expectedErr: "1:17: missing module and name in module.import[0]",
+		},
 		{
 			name:        "import missing name",
 			input:       "(module (import \"\"))",
 			expectedErr: "1:19: missing name in module.import[0]",
 		},
+		{
+			name:        "import with desc, no name",
+			input:       "(module (import \"\" (func)))",
+			expectedErr: "1:20: missing name in module.import[0]",
+		},
 		{
 			name:        "import unquoted module",
 			input:       "(module (import foo bar))",
@@ -225,6 +320,11 @@ 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 double name",
+			input:       "(module (import \"foo\" \"bar\" (func $baz $qux)))",
+			expectedErr: "1:40: redundant name: $qux in module.import[0].func",
+		},
 		{
 			name:        "import func missing param0 type",
 			input:       "(module (import \"\" \"\" (func (param))))",
@@ -260,10 +360,30 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (import \"\" \"\" (func (param i32) (result f65))))",
 			expectedErr: "1:49: unknown type: f65 in module.import[0].func.result",
 		},
+		{
+			name:        "import func wrong no param type",
+			input:       "(module (import \"\" \"\" (func (param))))",
+			expectedErr: "1:35: expected a type in module.import[0].func.param[0]",
+		},
+		{
+			name:        "import func no result type",
+			input:       "(module (import \"\" \"\" (func (param i32) (result))))",
+			expectedErr: "1:48: expected a type in module.import[0].func.result",
+		},
+		{
+			name:        "import func wrong param token",
+			input:       "(module (import \"\" \"\" (func (param () ))))",
+			expectedErr: "1:36: unexpected '(' in module.import[0].func.param[0]",
+		},
+		{
+			name:        "import func wrong result token",
+			input:       "(module (import \"\" \"\" (func (result () ))))",
+			expectedErr: "1:37: unexpected '(' in module.import[0].func.result",
+		},
 		{
 			name:        "import func double desc",
 			input:       "(module (import \"foo\" \"bar\" (func $main) (func $mein)))",
-			expectedErr: "1:43: redundant field: func in module.import[0]",
+			expectedErr: "1:42: unexpected '(' in module.import[0]",
 		},
 		{
 			name:        "start missing funcidx",
@@ -280,6 +400,11 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (start $main) (start $main))",
 			expectedErr: "1:24: redundant start in module",
 		},
+		{
+			name:        "wrong start",
+			input:       "(module (start main))",
+			expectedErr: "1:16: unexpected keyword: main in module.start",
+		},
 	}
 
 	for _, tt := range tests {