Repository: tetratelabs/wazero
Pull request #123 (2022-01-10 01:28:40)

wat: enforces signature order
This change does two things:
 * enforces field names are first or not present
   * Ex. (func $main) $main is the name of the func field
 * enforces the order requirements of the type grammar. (param)* result?
   * To reduce complexity, this logic is extracted to type_parser.

## Notes

### Unit test coverage is the same
There is still 100pct coverage except TODO and panics

### benchmark speed decrease and allocation increase

Extracting typeParser resulted in a performance decrease on the trivial benchmark because it initializes, but never needs a type parser. I think this is ok because in real life, %.wat files with anything non-trivial will end up with at least a few types.

decrease in speed
```
BenchmarkTextToBinaryExample/vs_wat.parseModule-16    	 1429618	       832.1 ns/op	     568 B/op	      23 allocs/op
BenchmarkTextToBinaryExample/vs_wat.parseModule-16    	 1293886	       977.1 ns/op	     664 B/op	      27 allocs/op
```

example input
```
(module $simple
	(import "" "hello" (func $hello))
	(start $hello)
)
```

Once we implement functions, we should re-do the benchmark anyway.

### Function pointer fake
The parser used by the lexer changes when fields change. It now also changes when we enter a common grammar (type use). To reduce stack depth and allocations, I'm updating moduleParser.tokenParser via a pointer to the moduleParser as it seems there's no function pointer in go.


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/convert.go b/wasm/wat/convert.go
index d3a2fe42..9e73f7b3 100644
--- a/wasm/wat/convert.go
+++ b/wasm/wat/convert.go
@@ -25,7 +25,11 @@ func TextToBinary(source []byte) (result *wasm.Module, err error) {
 	// difference is that the text format has type names and the binary format does not.
 	result = &wasm.Module{}
 	for _, t := range m.types {
-		result.TypeSection = append(result.TypeSection, &wasm.FunctionType{Params: t.params, Results: t.results})
+		var results []wasm.ValueType
+		if t.result != 0 {
+			results = []wasm.ValueType{t.result}
+		}
+		result.TypeSection = append(result.TypeSection, &wasm.FunctionType{Params: t.params, Results: results})
 	}
 
 	// Now, handle any imported functions. Notably, we retain the same insertion order as defined in the text format in
@@ -35,7 +39,7 @@ func TextToBinary(source []byte) (result *wasm.Module, err error) {
 			Module: f.module, Name: f.name,
 			Desc: &wasm.ImportDesc{
 				Kind:          wasm.ImportKindFunction,
-				FuncTypeIndex: uint32(f.typeIndex),
+				FuncTypeIndex: f.typeIndex,
 			},
 		})
 	}
diff --git a/wasm/wat/parser.go b/wasm/wat/parser.go
index 91582671..947fba6c 100644
--- a/wasm/wat/parser.go
+++ b/wasm/wat/parser.go
@@ -1,11 +1,8 @@
 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
@@ -25,15 +22,18 @@ const (
 	fieldModuleImport
 	// fieldModuleImportFunc is at the position module.import.func and cannot repeat in the same import.
 	fieldModuleImportFunc
-	// 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 {
+	// tokenParser primarily supports dispatching parse to a different tokenParser depending on the position in the file
+	// The initial value is ensureLParen because %.wat files must begin with a '(' token (ignoring whitespace).
+	//
+	// Design Note: This is an alternative to using a stack as the structure defined in "module" is fixed depth, except
+	// function bodies. Any function body may be parsed in a more dynamic way.
+	tokenParser tokenParser
+
 	// source is the entire WebAssembly text format source code being parsed.
 	source []byte
 
@@ -44,10 +44,6 @@ type moduleParser struct {
 	// This is set after reading a field name, ex "module", or after reaching the end of one, ex ')'.
 	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
-
 	// 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" ...)
 	currentValue0 []byte
@@ -59,18 +55,15 @@ type moduleParser struct {
 	// 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
+	typeParser *typeParser
+}
 
-	// currentParamIndex allows us to track the relative position of parameters regardless of position in the source.
-	currentParamIndex uint32
+// parse has the same signature as tokenParser and called by lex on each token.
+//
+// The tokenParser this dispatches to should be updated when reading a new field name, and restored to the prior
+// value or a different parser on endField.
+func (p *moduleParser) parse(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	return p.tokenParser(tok, tokenBytes, line, col)
 }
 
 // parseModule parses the configured source into a module. This function returns when the source is exhausted or an
@@ -81,6 +74,7 @@ type moduleParser struct {
 // * err is a FormatError invoking the parser, dangling block comments or unexpected characters.
 func parseModule(source []byte) (*module, error) {
 	p := moduleParser{source: source, module: &module{}}
+	p.typeParser = &typeParser{m: &p}
 
 	// A valid source must begin with the token '(', but it could be preceded by whitespace or comments. For this
 	// reason, we cannot enforce source[0] == '(', and instead need to start the lexer to check the first token.
@@ -92,11 +86,6 @@ func parseModule(source []byte) (*module, error) {
 	return p.module, nil
 }
 
-// parse calls the delegate moduleParser.tokenParser
-func (p *moduleParser) parse(tok tokenType, tokenBytes []byte, line, col uint32) error {
-	return p.tokenParser(tok, tokenBytes, line, col)
-}
-
 func (p *moduleParser) ensureLParen(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	if tok != tokenLParen {
 		return fmt.Errorf("expected '(', but found %s: %s", tok, tokenBytes)
@@ -125,7 +114,7 @@ func (p *moduleParser) beginField(tok tokenType, fieldName []byte, _, _ uint32)
 	case fieldInitial:
 		if string(fieldName) == "module" {
 			p.currentField = fieldModule
-			p.tokenParser = p.parseModule
+			p.tokenParser = p.parseModuleName
 		}
 	case fieldModule:
 		switch string(fieldName) {
@@ -148,23 +137,10 @@ func (p *moduleParser) beginField(tok tokenType, fieldName []byte, _, _ uint32)
 				name:        string(p.currentValue1),
 				importIndex: p.currentImportIndex,
 			})
+
 			p.currentField = fieldModuleImportFunc
-			p.tokenParser = p.parseImportFunc
+			p.tokenParser = p.parseImportFuncName
 		} // 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")
-		}
 	}
 	if p.tokenParser == nil {
 		return fmt.Errorf("unexpected field: %s", string(fieldName))
@@ -179,12 +155,6 @@ func (p *moduleParser) beginField(tok tokenType, fieldName []byte, _, _ uint32)
 // example, any function body may be parsed in a more dynamic way.
 func (p *moduleParser) endField() {
 	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
 		p.tokenParser = p.parseModule
@@ -196,21 +166,27 @@ func (p *moduleParser) endField() {
 	}
 }
 
+func (p *moduleParser) parseModuleName(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	if tok == tokenID { // Ex. $Math
+		name := string(tokenBytes)
+		p.module.name = name
+		p.tokenParser = p.parseModule
+		return nil
+	}
+	return p.parseModule(tok, tokenBytes, line, col)
+}
+
 func (p *moduleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ uint32) error {
 	switch tok {
 	case tokenID:
-		name := string(tokenBytes)
-		if p.module.name != "" {
-			return fmt.Errorf("redundant name: %s", name)
-		}
-		p.module.name = name
+		return fmt.Errorf("redundant name: %s", string(tokenBytes))
 	case tokenLParen:
 		p.tokenParser = p.beginField // after this look for a field name
 		return nil
 	case tokenRParen: // end of module
 		p.endField()
 	default:
-		return p.unexpectedToken(tok, tokenBytes)
+		return unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
@@ -230,7 +206,7 @@ func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32
 	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)
+			return unexpectedToken(tok, tokenBytes)
 		}
 		// Err if there are not enough names when we reach a description. Ex. (import func())
 		if err := p.validateImportModuleAndName(); err != nil {
@@ -254,7 +230,7 @@ func (p *moduleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ uint32
 		p.currentValue0, p.currentValue1 = nil, nil
 		p.endField()
 	default:
-		return p.unexpectedToken(tok, tokenBytes)
+		return unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
@@ -269,127 +245,44 @@ func (p *moduleParser) validateImportModuleAndName() error {
 	return nil
 }
 
-func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, _, _ uint32) error {
-	switch tok {
-	case tokenID: // Ex. $main
+func (p *moduleParser) parseImportFuncName(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	if tok == tokenID { // Ex. $main
 		name := string(tokenBytes)
 		fn := p.module.importFuncs[len(p.module.importFuncs)-1]
-		if fn.funcName != "" {
-			return fmt.Errorf("redundant name: %s", name)
-		}
 		fn.funcName = name
-	case tokenLParen: // start fields, ex. (param or (result
-		p.tokenParser = p.beginField
+		p.tokenParser = p.parseImportFunc
 		return nil
-	case tokenRParen: // end of this import func
-		if p.currentType == nil {
-			p.currentType = typeFuncEmpty
-		}
-
-		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.currentParamIndex = 0
-		p.endField()
-	default:
-		return p.unexpectedToken(tok, tokenBytes)
 	}
-	return nil
+	return p.parseImportFunc(tok, tokenBytes, line, col)
 }
 
-func (p *moduleParser) parseImportFuncParam(tok tokenType, tokenBytes []byte, _, _ uint32) error {
+func (p *moduleParser) parseImportFunc(tok tokenType, tokenBytes []byte, line, col 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
-		}
-		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++
-
-		p.endField()
-	default:
-		return p.unexpectedToken(tok, tokenBytes)
+	case tokenID: // Ex. (func $main $main)
+		return fmt.Errorf("redundant name: %s", string(tokenBytes))
+	case tokenLParen: // start fields, ex. (param or (result
+		p.typeParser.beginParsingParamsOrResult(p.parseImportFuncAfterType)
+		return nil
 	}
-	return nil
+	return p.parseImportFuncAfterType(tok, tokenBytes, line, col)
 }
 
-func (p *moduleParser) parseImportFuncResult(tok tokenType, tokenBytes []byte, _, _ uint32) error {
-	switch tok {
-	case tokenKeyword: // Ex. 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")
-		}
-
-		vt, err := parseValueType(tokenBytes)
-		if err != nil {
-			return err
-		}
-
-		// add the result to the current type
-		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")
-		}
-		p.endField()
-	default:
-		return p.unexpectedToken(tok, tokenBytes)
+// parseImportFuncAfterType handles tokens after any type signature was read. The only valid token is tokenRParen
+// because no fields are defined except those in the function type.
+func (p *moduleParser) parseImportFuncAfterType(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	if tok == tokenRParen {
+		p.endImportFunc()
+		return nil
 	}
-	return nil
+	return unexpectedToken(tok, tokenBytes)
 }
 
-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
+// endImportFunc adds the module.types index for this function regardless of whether one was defined inline or not
+func (p *moduleParser) endImportFunc() {
+	fn := p.module.importFuncs[len(p.module.importFuncs)-1]
+	fn.typeIndex = p.typeParser.findOrAddType(p.module)
+	p.currentField = fieldModuleImport
+	p.tokenParser = p.parseImport
 }
 
 func (p *moduleParser) parseStart(tok tokenType, tokenBytes []byte, line, col uint32) error {
@@ -405,7 +298,7 @@ func (p *moduleParser) parseStart(tok tokenType, tokenBytes []byte, line, col ui
 		}
 		p.endField()
 	default:
-		return p.unexpectedToken(tok, tokenBytes)
+		return unexpectedToken(tok, tokenBytes)
 	}
 	return nil
 }
@@ -414,7 +307,7 @@ func (p *moduleParser) parseUnexpectedTrailingCharacters(_ tokenType, tokenBytes
 	return fmt.Errorf("unexpected trailing characters: %s", tokenBytes)
 }
 
-func (p *moduleParser) unexpectedToken(tok tokenType, tokenBytes []byte) error {
+func unexpectedToken(tok tokenType, tokenBytes []byte) error {
 	if tok == tokenLParen { // unbalanced tokenRParen is caught at the lexer layer
 		return errors.New("unexpected '('")
 	}
@@ -429,18 +322,10 @@ func (p *moduleParser) errorContext() string {
 		return "module"
 	case fieldModuleStart:
 		return "module.start"
-	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)
-		}
-		// TODO: table, memory or global
+	case fieldModuleImport:
 		return fmt.Sprintf("module.import[%d]", p.currentImportIndex)
+	case fieldModuleImportFunc: // TODO: table, memory or global
+		return fmt.Sprintf("module.import[%d].func%s", p.currentImportIndex, p.typeParser.errorContext())
 	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))
 	}
diff --git a/wasm/wat/type_parser.go b/wasm/wat/type_parser.go
new file mode 100644
index 00000000..e6054ff0
--- /dev/null
+++ b/wasm/wat/type_parser.go
@@ -0,0 +1,204 @@
+package wat
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+
+	"github.com/tetratelabs/wazero/wasm"
+)
+
+// typeParsingState is used to give an appropriate typeParser.errorContext
+type typeParsingState byte
+
+const (
+	parsingParamOrResult typeParsingState = iota
+	parsingParam
+	parsingResult
+	parsingComplete
+)
+
+// typeParser parses an inlined type from a field such as "type" or "func". Unlike normal parsers, this is not used for
+// an entire field (enclosed by parens). Rather, this only handles "param" and "result" inner fields in the correct
+// order.
+//
+// Ex. `(module (import (func $main (param i32))))`
+//   This parses after the name     ^---------^
+//
+// Ex. `(module (type (param i32) (result i64)))`
+//   This parses here ^----------------------^
+//
+// Ex. `(module (import (func)))`
+//   This parses nothing (because there is no type)
+//
+// typeParser is reusable. The caller resets when reaching the appropriate tokenRParen via beginParsingParamsOrResult.
+type typeParser struct {
+	// m is used as a function pointer to moduleParser.tokenParser. This updates based on state changes.
+	m *moduleParser
+
+	// onTypeEnd is invoked when the grammar "(param)* (result)?" completes.
+	//
+	// Note: this is called when neither a "param" nor a "result" field are found, or on any field following a "param"
+	// that is not a "result".
+	onTypeEnd tokenParser
+
+	// state is initially parsingParamOrResult and updated alongside tokenParser
+	state typeParsingState
+
+	// 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 is used to give an appropriate errorContext
+	currentParamIndex uint32
+
+	// foundParam allows us to check if we found a type in a "param" field. We can't use currentParamIndex because when
+	// parameters are abbreviated, ex. (param i32 i32), the currentParamIndex will be less than the type count.
+	foundParam bool
+
+	// currentResult is zero until set, and only set once as WebAssembly 1.0 only supports up to one result.
+	currentResult wasm.ValueType
+}
+
+// beginParsingParamsOrResult sets moduleParser.tokenParser to beginParamOrResult after resetting internal fields.
+// This should only be called when reaching the first tokenLParen after the optional field name (tokenID).
+//
+// Ex. Given the source `(module (import (func $main (param i32))))`
+//         Set this result to the next parser here --^
+//             This result restores control to onTypeEnd here --^
+//
+// The onTypeEnd parameter is invoked once any "param" and "result" fields have been consumed.
+//
+// NOTE: An empty function is valid and will not reach a tokenLParen! Ex. `(module (import (func)))`
+func (p *typeParser) beginParsingParamsOrResult(onTypeEnd tokenParser) {
+	p.onTypeEnd = onTypeEnd
+	p.state = parsingParamOrResult
+	p.m.tokenParser = p.beginParamOrResult
+	p.currentParams = nil
+	p.currentParamIndex = 0
+	p.currentResult = 0
+}
+
+// beginParamOrResult is a tokenParser called after a tokenLParen and accepts either a "param" or a "result" field
+// (tokenKeyword).
+func (p *typeParser) beginParamOrResult(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	if tok == tokenKeyword {
+		switch string(tokenBytes) {
+		case "param":
+			p.state = parsingParam
+			p.foundParam = false
+			p.m.tokenParser = p.parseParam
+		case "result":
+			p.state = parsingResult
+			p.m.tokenParser = p.parseResult
+		case "type": // cannot repeat
+			return errors.New("TODO: (module (import (func (type")
+		}
+		return nil
+	}
+	// If we reach here, it is a syntax error, so punt it to onTypeEnd. Ex. (func ($param i32))
+	return p.onTypeEnd(tok, tokenBytes, line, col)
+}
+
+func (p *typeParser) parseMoreParamsOrResult(tok tokenType, tokenBytes []byte, line, col uint32) error {
+	if tok == tokenLParen {
+		p.state = parsingParamOrResult
+		p.m.tokenParser = p.beginParamOrResult
+		return nil
+	}
+	// If we reached this point, we have one or more parameters, but no result. Ex. (func (param i32)) or (func)
+	return p.onTypeEnd(tok, tokenBytes, line, col)
+}
+
+func (p *typeParser) parseParam(tok tokenType, tokenBytes []byte, _, _ uint32) error {
+	switch tok {
+	case tokenID: // Ex. $len
+		return errors.New("TODO param name ex (param $len i32), but not in abbreviation ex (param $x i32 $y i32)")
+	case tokenKeyword: // Ex. i32
+		vt, err := parseValueType(tokenBytes)
+		if err != nil {
+			return err
+		}
+		p.currentParams = append(p.currentParams, vt)
+		p.foundParam = true
+	case tokenRParen: // end of this field
+		if !p.foundParam {
+			return errors.New("expected a type")
+		}
+
+		// since multiple param fields are valid, ex `(func (param i32) (param i64))`, prepare for any next.
+		p.currentParamIndex++
+		p.state = parsingParamOrResult
+		p.m.tokenParser = p.parseMoreParamsOrResult
+	default:
+		return unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+// parseResult is a tokenParser inside a "result" field (tokenKeyword). When this field completes (tokenRParen), control
+// transitions to parseComplete.
+func (p *typeParser) parseResult(tok tokenType, tokenBytes []byte, _, _ uint32) error {
+	switch tok {
+	case tokenKeyword: // Ex. i32
+		if p.currentResult != 0 {
+			return errors.New("redundant type")
+		}
+		vt, err := parseValueType(tokenBytes)
+		if err != nil {
+			return err
+		}
+		p.currentResult = vt
+	case tokenRParen: // end of this field
+		if p.currentResult == 0 {
+			return errors.New("expected a type")
+		}
+		p.m.tokenParser = p.onTypeEnd
+		p.state = parsingComplete
+	default:
+		return unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func (p *typeParser) errorContext() string {
+	switch p.state {
+	case parsingParam:
+		return fmt.Sprintf(".param[%d]", p.currentParamIndex)
+	case parsingResult:
+		return ".result"
+	}
+	return ""
+}
+
+// findOrAddType returns the index in module.types of a possibly empty type matching any current params or result.
+func (p *typeParser) findOrAddType(m *module) (typeIndex uint32) {
+	// search for an existing signature that matches the current type
+	for i, t := range m.types {
+		if bytes.Equal(p.currentParams, t.params) && p.currentResult == t.result {
+			return uint32(i)
+		}
+	}
+
+	// if we didn't find a match, we need to insert a new type and use it
+	typeIndex = uint32(len(m.types))
+	m.types = append(m.types, &typeFunc{p.currentParams, p.currentResult})
+	return
+}
+
+func parseValueType(tokenBytes []byte) (wasm.ValueType, error) {
+	t := string(tokenBytes)
+	switch t {
+	case "i32":
+		return wasm.ValueTypeI32, nil
+	case "i64":
+		return wasm.ValueTypeI64, nil
+	case "f32":
+		return wasm.ValueTypeF32, nil
+	case "f64":
+		return wasm.ValueTypeF64, nil
+	default:
+		return 0, fmt.Errorf("unknown type: %s", t)
+	}
+}
diff --git a/wasm/wat/types.go b/wasm/wat/types.go
index c61e5d45..436f96d8 100644
--- a/wasm/wat/types.go
+++ b/wasm/wat/types.go
@@ -65,16 +65,13 @@ type typeFunc struct {
 	// See https://www.w3.org/TR/wasm-core-1/#result-types%E2%91%A0
 	params []wasm.ValueType
 
-	// results are the possibly empty sequence of value types returned by a function with this signature.
+	// result is the value type of the signature or zero if there is none.
 	//
-	// Note: In WebAssembly 1.0 (MVP), there can be at most one result.
+	// Note: We use this shortcut instead of a slice because in WebAssembly 1.0 (MVP), there can be at most one result.
 	// See https://www.w3.org/TR/wasm-core-1/#result-types%E2%91%A0
-	results []wasm.ValueType
+	result wasm.ValueType
 }
 
-// typeFuncEmpty represents a nullary function type. Ex. `(type (func))`
-var typeFuncEmpty = &typeFunc{}
-
 // importFunc corresponds to the text format of a WebAssembly function import.
 //
 // Note: nothing is required per specification. Ex `(import "" "" (func))` is valid!
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/names_test.go b/wasm/wat/names_test.go
index afc42970..1914d8f5 100644
--- a/wasm/wat/names_test.go
+++ b/wasm/wat/names_test.go
@@ -35,8 +35,8 @@ func TestEncodeNameSection_OnlyFuncName(t *testing.T) {
 	i32 := wasm.ValueTypeI32
 	m := &module{
 		types: []*typeFunc{
-			{params: []wasm.ValueType{i32, i32}, results: []wasm.ValueType{i32}},
-			{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}},
+			{params: []wasm.ValueType{i32, i32}, result: i32},
+			{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32},
 		},
 		importFuncs: []*importFunc{
 			{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "args_sizes_get", funcName: "$" + func0},
diff --git a/wasm/wat/parser_test.go b/wasm/wat/parser_test.go
index 6551aca7..53259c27 100644
--- a/wasm/wat/parser_test.go
+++ b/wasm/wat/parser_test.go
@@ -9,7 +9,9 @@ import (
 )
 
 func TestParseModule(t *testing.T) {
+	typeFuncEmpty := &typeFunc{}
 	i32, i64 := wasm.ValueTypeI32, wasm.ValueTypeI64
+
 	tests := []struct {
 		name     string
 		input    string
@@ -50,7 +52,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32) (param i32) (param i32) (param i32) (result i32)))
 )`,
 			expected: &module{
-				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}}},
+				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32}},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write"},
 				},
@@ -62,7 +64,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32 i32 i32 i32) (result i32)))
 )`,
 			expected: &module{
-				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}}},
+				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32}},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write"},
 				},
@@ -76,7 +78,7 @@ func TestParseModule(t *testing.T) {
 	(import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (param i32) (param i32 i32) (param i32) (result i32)))
 )`,
 			expected: &module{
-				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}}},
+				types: []*typeFunc{{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32}},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "fd_write", funcName: "$runtime.fd_write"},
 				},
@@ -98,7 +100,7 @@ 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}}},
+				types:       []*typeFunc{{result: i32}},
 				importFuncs: []*importFunc{{}},
 			},
 		},
@@ -109,8 +111,8 @@ func TestParseModule(t *testing.T) {
 )`,
 			expected: &module{
 				types: []*typeFunc{{
-					params:  []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
-					results: []wasm.ValueType{i32},
+					params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
+					result: i32,
 				}},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "path_open", funcName: "$runtime.path_open"},
@@ -124,8 +126,8 @@ func TestParseModule(t *testing.T) {
 )`,
 			expected: &module{
 				types: []*typeFunc{{
-					params:  []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
-					results: []wasm.ValueType{i32},
+					params: []wasm.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
+					result: i32,
 				}},
 				importFuncs: []*importFunc{
 					{importIndex: 0, module: "wasi_snapshot_preview1", name: "path_open", funcName: "$runtime.path_open"},
@@ -140,8 +142,8 @@ func TestParseModule(t *testing.T) {
 )`,
 			expected: &module{
 				types: []*typeFunc{
-					{params: []wasm.ValueType{i32, i32}, results: []wasm.ValueType{i32}},
-					{params: []wasm.ValueType{i32, i32, i32, i32}, results: []wasm.ValueType{i32}},
+					{params: []wasm.ValueType{i32, i32}, result: i32},
+					{params: []wasm.ValueType{i32, i32, i32, i32}, result: i32},
 				},
 				importFuncs: []*importFunc{
 					{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "arg_sizes_get", funcName: "$runtime.arg_sizes_get"},
@@ -157,7 +159,7 @@ func TestParseModule(t *testing.T) {
 )`,
 			expected: &module{
 				types: []*typeFunc{
-					{params: []wasm.ValueType{i32, i32}, results: []wasm.ValueType{i32}},
+					{params: []wasm.ValueType{i32, i32}, result: i32},
 				},
 				importFuncs: []*importFunc{
 					{importIndex: 0, typeIndex: 0, module: "wasi_snapshot_preview1", name: "args_get", funcName: "$runtime.args_get"},
@@ -207,32 +209,6 @@ 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 }{
 		{
@@ -275,6 +251,11 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module $foo ))",
 			expectedErr: "1:15: found ')' before '('",
 		},
+		{
+			name:        "module name after import",
+			input:       "(module (import \"\" \"\" (func) $Math)",
+			expectedErr: "1:30: unexpected id: $Math in module.import[0]",
+		},
 		{
 			name:        "import missing module",
 			input:       "(module (import))",
@@ -320,6 +301,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 invalid token",
+			input:       "(module (import \"foo\" \"bar\" (func ($param))))",
+			expectedErr: "1:36: unexpected id: $param in module.import[0].func",
+		},
 		{
 			name:        "import func double name",
 			input:       "(module (import \"foo\" \"bar\" (func $baz $qux)))",
@@ -348,7 +334,7 @@ func TestParseModule_Errors(t *testing.T) {
 		{
 			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",
+			expectedErr: "1:54: unexpected '(' in module.import[0].func",
 		},
 		{
 			name:        "import func double result type",
@@ -380,6 +366,21 @@ func TestParseModule_Errors(t *testing.T) {
 			input:       "(module (import \"\" \"\" (func (result () ))))",
 			expectedErr: "1:37: unexpected '(' in module.import[0].func.result",
 		},
+		{
+			name:        "import func name after param",
+			input:       "(module (import \"\" \"\" (func (param i32) $main)))",
+			expectedErr: "1:41: unexpected id: $main in module.import[0].func",
+		},
+		{
+			name:        "import func name after result",
+			input:       "(module (import \"\" \"\" (func (result i32) $main)))",
+			expectedErr: "1:42: unexpected id: $main in module.import[0].func",
+		},
+		{
+			name:        "import func param after result",
+			input:       "(module (import \"\" \"\" (func (result i32) (param i32))))",
+			expectedErr: "1:42: unexpected '(' in module.import[0].func",
+		},
 		{
 			name:        "import func double desc",
 			input:       "(module (import \"foo\" \"bar\" (func $main) (func $mein)))",
diff --git a/wasm/wat/type_parser_test.go b/wasm/wat/type_parser_test.go
new file mode 100644
index 00000000..d1efe3fe
--- /dev/null
+++ b/wasm/wat/type_parser_test.go
@@ -0,0 +1,35 @@
+package wat
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/tetratelabs/wazero/wasm"
+)
+
+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")
+	})
+}