Repository: tetratelabs/wazero
Pull request #92 (2021-12-24 05:20:17)

Adds initial WebAssembly Text Format (%.wat) parser
This builds on the lexing layer to parse the WebAssembly Text Format.

The PR after this will convert the following text format to a `wasm.Module`

```wat
(module
	(import "" "hello" (func $hello))
	(start $hello)
)
```

codefromthecrypt: ok really ready now. I removed all code except what's required at the moment
codefromthecrypt: ok I did another pass.

Patch:
diff --git a/wasm/jit/jit_amd64.go b/wasm/jit/jit_amd64.go
index d8e3bd05..c42a7e59 100644
--- a/wasm/jit/jit_amd64.go
+++ b/wasm/jit/jit_amd64.go
@@ -920,45 +920,25 @@ func (b *amd64Builder) handleAdd(o *wazeroir.OperationAdd) error {
 	// this can be optimized. Same goes for other arithmetic instructions.
 
 	var instruction obj.As
-	var tp generalPurposeRegisterType
 	switch o.Type {
 	case wazeroir.UnsignedTypeI32:
 		instruction = x86.AADDL
-		tp = generalPurposeRegisterTypeInt
-		panic("add tests!")
 	case wazeroir.UnsignedTypeI64:
 		instruction = x86.AADDQ
-		tp = generalPurposeRegisterTypeInt
 	case wazeroir.UnsignedTypeF32:
 		instruction = x86.AADDSS
-		tp = generalPurposeRegisterTypeFloat
-		panic("add tests!")
 	case wazeroir.UnsignedTypeF64:
 		instruction = x86.AADDSD
-		tp = generalPurposeRegisterTypeFloat
-		panic("add tests!")
 	}
 
 	x2 := b.locationStack.pop()
-	if x2.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(tp, x2); err != nil {
-			return err
-		}
-	} else if x2.onConditionalRegister() {
-		if err := b.moveConditionalToGeneralPurposeRegister(x2); err != nil {
-			return err
-		}
+	if err := b.ensureOnGeneralPurposeRegister(x2); err != nil {
+		return err
 	}
 
 	x1 := b.locationStack.peek() // Note this is peek, pop!
-	if x1.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(tp, x1); err != nil {
-			return err
-		}
-	} else if x1.onConditionalRegister() {
-		// This shouldn't happen as the conditional
-		// register must be on top of the stack.
-		panic("a bug in jit compiler")
+	if err := b.ensureOnGeneralPurposeRegister(x1); err != nil {
+		return err
 	}
 
 	// x1 += x2.
@@ -981,45 +961,25 @@ func (b *amd64Builder) handleSub(o *wazeroir.OperationSub) error {
 	// this can be optimized. Same goes for other arithmetic instructions.
 
 	var instruction obj.As
-	var tp generalPurposeRegisterType
 	switch o.Type {
 	case wazeroir.UnsignedTypeI32:
 		instruction = x86.ASUBL
-		tp = generalPurposeRegisterTypeInt
-		panic("add tests!")
 	case wazeroir.UnsignedTypeI64:
 		instruction = x86.ASUBQ
-		tp = generalPurposeRegisterTypeInt
 	case wazeroir.UnsignedTypeF32:
 		instruction = x86.ASUBSS
-		tp = generalPurposeRegisterTypeFloat
-		panic("add tests!")
 	case wazeroir.UnsignedTypeF64:
 		instruction = x86.ASUBSD
-		tp = generalPurposeRegisterTypeFloat
-		panic("add tests!")
 	}
 
 	x2 := b.locationStack.pop()
-	if x2.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(tp, x2); err != nil {
-			return err
-		}
-	} else if x2.onConditionalRegister() {
-		if err := b.moveConditionalToGeneralPurposeRegister(x2); err != nil {
-			return err
-		}
+	if err := b.ensureOnGeneralPurposeRegister(x2); err != nil {
+		return err
 	}
 
 	x1 := b.locationStack.peek() // Note this is peek, pop!
-	if x1.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(tp, x1); err != nil {
-			return err
-		}
-	} else if x1.onConditionalRegister() {
-		// This shouldn't happen as the conditional
-		// register must be on top of the stack.
-		panic("a bug in jit compiler")
+	if err := b.ensureOnGeneralPurposeRegister(x1); err != nil {
+		return err
 	}
 
 	// x1 += x2.
@@ -1038,65 +998,57 @@ func (b *amd64Builder) handleSub(o *wazeroir.OperationSub) error {
 }
 
 func (b *amd64Builder) handleLe(o *wazeroir.OperationLe) error {
+	x2 := b.locationStack.pop()
+	if err := b.ensureOnGeneralPurposeRegister(x2); err != nil {
+		return err
+	}
+
+	x1 := b.locationStack.pop()
+	if err := b.ensureOnGeneralPurposeRegister(x1); err != nil {
+		return err
+	}
+
+	// Emit the compare instruction.
+	prog := b.newProg()
+	prog.From.Type = obj.TYPE_REG
+	prog.To.Type = obj.TYPE_REG
 	var resultConditionState conditionalRegisterState
-	var instruction obj.As
-	var tp generalPurposeRegisterType
 	switch o.Type {
 	case wazeroir.SignedTypeInt32:
 		resultConditionState = conditionalRegisterStateLE
-		instruction = x86.ACMPL
-		tp = generalPurposeRegisterTypeInt
+		prog.As = x86.ACMPL
+		prog.From.Reg = x1.register
+		prog.To.Reg = x2.register
 	case wazeroir.SignedTypeUint32:
 		resultConditionState = conditionalRegisterStateBE
-		instruction = x86.ACMPL
-		tp = generalPurposeRegisterTypeInt
+		prog.As = x86.ACMPL
+		prog.From.Reg = x1.register
+		prog.To.Reg = x2.register
 	case wazeroir.SignedTypeInt64:
 		resultConditionState = conditionalRegisterStateLE
-		instruction = x86.ACMPQ
-		tp = generalPurposeRegisterTypeInt
+		prog.As = x86.ACMPQ
+		prog.From.Reg = x1.register
+		prog.To.Reg = x2.register
 	case wazeroir.SignedTypeUint64:
 		resultConditionState = conditionalRegisterStateBE
-		instruction = x86.ACMPQ
-		tp = generalPurposeRegisterTypeInt
+		prog.As = x86.ACMPQ
+		prog.From.Reg = x1.register
+		prog.To.Reg = x2.register
 	case wazeroir.SignedTypeFloat32:
-		tp = generalPurposeRegisterTypeFloat
-		panic("add test!")
+		resultConditionState = conditionalRegisterStateBE
+		prog.As = x86.ACOMISS
+		prog.From.Reg = x2.register
+		prog.To.Reg = x1.register
 	case wazeroir.SignedTypeFloat64:
-		tp = generalPurposeRegisterTypeFloat
-		panic("add test!")
-	}
-
-	x2 := b.locationStack.pop()
-	if x2.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(tp, x2); err != nil {
-			return err
-		}
-	} else if x2.onConditionalRegister() {
-		if err := b.moveConditionalToGeneralPurposeRegister(x2); err != nil {
-			return err
-		}
-	}
-
-	x1 := b.locationStack.pop()
-	if x1.onStack() {
-		if err := b.moveStackToRegisterWithAllocation(tp, x1); err != nil {
-			return err
-		}
-	} else if x1.onConditionalRegister() {
-		// This shouldn't happen as the conditional
-		// register must be on top of the stack.
-		panic("a bug in jit compiler")
+		resultConditionState = conditionalRegisterStateBE
+		prog.As = x86.ACOMISD
+		prog.From.Reg = x2.register
+		prog.To.Reg = x1.register
 	}
-
-	// Compare: set the flag based on x1-x2.
-	prog := b.newProg()
-	prog.As = instruction
-	prog.From.Type = obj.TYPE_REG
-	prog.From.Reg = x1.register
-	prog.To.Type = obj.TYPE_REG
-	prog.To.Reg = x2.register
 	b.addInstruction(prog)
 
+	// TODO: emit NaN value handings for floats.
+
 	// We no longer need x1,x2 register after cmp operation here,
 	// so we release it.
 	b.locationStack.releaseRegister(x1)
diff --git a/wasm/wat/lexer.go b/wasm/wat/lexer.go
index 8b6aeffa..c13315d5 100644
--- a/wasm/wat/lexer.go
+++ b/wasm/wat/lexer.go
@@ -6,7 +6,7 @@ import (
 	"unicode/utf8"
 )
 
-// parseToken allows a parser to inspect a token without necessarily allocating strings
+// tokenParser is what the lexer calls for each token in the input.
 //
 // * tokenType is the token type
 // * tokenBytes are the UTF-8 bytes representing the token. Do not modify this.
@@ -17,7 +17,7 @@ import (
 //
 // Note: Do not include the line and column number in a parsing error as that will be attached automatically. Line and
 // column are here for storing the source location, such as for use in runtime stack traces.
-type parseToken func(tok tokenType, tokenBytes []byte, line, col int) error
+type tokenParser func(tok tokenType, tokenBytes []byte, line, col int) error
 
 var (
 	constantLParen = []byte{'('}
@@ -31,7 +31,7 @@ var (
 // * line is the source line number determined by unescaped '\n' characters of the error or EOF
 // * col is the UTF-8 column number of the error or EOF
 // * err is an error invoking the parser, dangling block comments or unexpected characters.
-func lex(parser parseToken, source []byte) (line int, col int, err error) {
+func lex(parser tokenParser, source []byte) (line int, col int, err error) {
 	// i is the source index to begin reading, inclusive.
 	i := 0
 	// end is the source index to stop reading, exclusive.
diff --git a/wasm/wat/parser.go b/wasm/wat/parser.go
new file mode 100644
index 00000000..06ec5e59
--- /dev/null
+++ b/wasm/wat/parser.go
@@ -0,0 +1,249 @@
+package wat
+
+import (
+	"errors"
+	"fmt"
+)
+
+type currentField byte
+
+const (
+	// fieldInitial is the first position in the source being parsed.
+	fieldInitial currentField = iota
+	fieldModule
+	fieldModuleImport
+	fieldModuleImportFunc
+	fieldModuleStart
+)
+
+type ModuleParser struct {
+	source []byte
+	module *module
+	// currentStringCount allows us to unquote the _import.module and _import.name fields, differentiating empty
+	// from never set, without making _import.module and _import.name pointers.
+	currentStringCount int
+	// currentField is the parser and error context.
+	// This is set after reading a field name, ex "module", or after reaching the end of one, ex ')'.
+	currentField
+	// 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
+	// currentImportIndex allows us to track the relative position of imports regardless of what they describe
+	currentImportIndex int
+	// currentImportModule is the first string of the current import. Ex. "Math" if (import "Math" "PI" (func))
+	currentImportModule string
+	// currentImportName is the second string of the current import. Ex. "PI" if (import "Math" "PI" (func))
+	currentImportName string
+	// currentImportHasDesc tracks if we found a description (Ex. (func)) of the current import.
+	currentImportHasDesc bool
+}
+
+// parseModule parses the configured source into a module. This function returns when the source is exhausted or an
+// error occurs.
+//
+// Here's a description of the return values:
+// * module is the result of parsing or nil on error
+// * 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{}}
+
+	// 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.
+	p.tokenParser = p.ensureLParen
+	line, col, err := lex(p.parse, p.source)
+	if err != nil {
+		return nil, &formatError{line, col, p.errorContext(), err}
+	}
+	return p.module, nil
+}
+
+// parse calls the delegate ModuleParser.tokenParser
+func (p *ModuleParser) parse(tok tokenType, tokenBytes []byte, line, col int) error {
+	return p.tokenParser(tok, tokenBytes, line, col)
+}
+
+func (p *ModuleParser) ensureLParen(tok tokenType, _ []byte, _, _ int) error {
+	if tok != tokenLParen {
+		return fmt.Errorf("expected '(', but found %s", tok)
+	}
+	p.tokenParser = p.startField
+	return nil
+}
+
+func (p *ModuleParser) startField(tok tokenType, tokenBytes []byte, _, _ int) (err error) {
+	if tok != tokenKeyword {
+		return fmt.Errorf("expected field, but found %s", tok)
+	}
+
+	// We expect p.currentField set according to a potentially nested "($fieldName".
+	// Each case must return a tokenParser that consumes the rest of the field up to the ')'.
+	// Note: each branch must handle any nesting concerns. Ex. "(module (import" nests further to "(func".
+	switch p.currentField {
+	case fieldInitial:
+		p.tokenParser, err = p.initialFieldHandler(tokenBytes)
+	case fieldModule:
+		p.tokenParser, err = p.moduleFieldHandler(tokenBytes)
+	case fieldModuleImport:
+		p.tokenParser, err = p.importFieldHandler(tokenBytes)
+	default:
+		return fmt.Errorf("unexpected current field %d", p.currentField)
+	}
+	return
+}
+
+// initialFieldHandler returns a tokenParser for the top-level fields in the WebAssembly source.
+func (p *ModuleParser) initialFieldHandler(fieldName []byte) (tokenParser, error) {
+	if string(fieldName) == "module" {
+		p.currentField = fieldModule
+		return p.parseModule, nil
+	} else {
+		return nil, fmt.Errorf("unexpected field: %s", string(fieldName))
+	}
+}
+
+func (p *ModuleParser) parseModule(tok tokenType, tokenBytes []byte, _, _ int) error {
+	switch tok {
+	case tokenID:
+		name := string(tokenBytes)
+		if p.module.name != "" {
+			return fmt.Errorf("redundant name: %s", name)
+		}
+		p.module.name = name
+	case tokenLParen:
+		p.tokenParser = p.startField // after this look for a field name
+		return nil
+	case tokenRParen: // end of module
+		p.currentField = fieldInitial
+	default:
+		return p.unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func (p *ModuleParser) moduleFieldHandler(fieldName []byte) (tokenParser, error) {
+	switch string(fieldName) {
+	case "import":
+		p.currentField = fieldModuleImport
+		return p.parseImport, nil
+	case "start":
+		p.currentField = fieldModuleStart
+		return p.parseStart, nil
+	}
+	return nil, fmt.Errorf("unexpected field: %s", string(fieldName))
+}
+
+func (p *ModuleParser) parseImport(tok tokenType, tokenBytes []byte, _, _ int) error {
+	switch tok {
+	case tokenString:
+		// Note: tokenString is minimum length two on account of quotes. Ex. "" or "foo"
+		name := string(tokenBytes[1 : len(tokenBytes)-1]) // unquote
+		if p.currentStringCount == 0 {
+			p.currentImportModule = name
+		} else if p.currentImportName != "" {
+			return fmt.Errorf("redundant name: %s", name)
+		} else {
+			p.currentImportName = name
+		}
+		p.currentStringCount = p.currentStringCount + 1
+	case tokenLParen: // start fields, ex. (func
+		p.tokenParser = p.startField
+		return nil
+	case tokenRParen: // end of this import
+		switch p.currentStringCount { // names precede desc
+		case 0:
+			return errors.New("missing module and name")
+		case 1:
+			return errors.New("missing name")
+		}
+		if !p.currentImportHasDesc {
+			return errors.New("missing description")
+		}
+		p.currentField = fieldModule
+		p.currentImportIndex = p.currentImportIndex + 1
+		p.currentStringCount = 0
+		p.currentImportHasDesc = false
+		p.tokenParser = p.parseModule
+	default:
+		return p.unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func (p *ModuleParser) importFieldHandler(fieldName []byte) (tokenParser, error) {
+	if p.currentImportHasDesc {
+		return nil, fmt.Errorf("redundant field: %s", string(fieldName))
+	}
+	switch string(fieldName) {
+	case "func":
+		p.currentImportHasDesc = true
+		p.currentField = fieldModuleImportFunc
+		desc := &importFunc{module: p.currentImportModule, name: p.currentImportName, importIndex: p.currentImportIndex}
+		p.module.importFuncs = append(p.module.importFuncs, desc)
+		return p.parseImportFunc, nil
+	} // TODO: table, memory or global
+	return nil, fmt.Errorf("unexpected field: %s", string(fieldName))
+}
+
+func (p *ModuleParser) parseImportFunc(tok tokenType, tokenBytes []byte, _, _ int) error {
+	switch tok {
+	case 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
+		// TODO: lParen to handle import types!
+	case tokenRParen: // end of this import func
+		p.currentField = fieldModuleImport
+		p.tokenParser = p.parseImport
+	default:
+		return p.unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func (p *ModuleParser) parseStart(tok tokenType, tokenBytes []byte, _, _ int) error {
+	switch tok {
+	case tokenUN, tokenID: // Ex. $main or 2
+		funcidx := string(tokenBytes)
+		if p.module.startFunction != "" {
+			return fmt.Errorf("redundant funcidx: %s", funcidx)
+		}
+		p.module.startFunction = funcidx
+	case tokenRParen: // end of this start
+		if p.module.startFunction == "" {
+			return errors.New("missing funcidx")
+		}
+		p.currentField = fieldModule
+		p.tokenParser = p.parseModule
+	default:
+		return p.unexpectedToken(tok, tokenBytes)
+	}
+	return nil
+}
+
+func (p *ModuleParser) unexpectedToken(tok tokenType, tokenBytes []byte) error {
+	if tok == tokenLParen || tok == tokenRParen {
+		return fmt.Errorf("unexpected %s", tok)
+	}
+	return fmt.Errorf("unexpected %s: %s", tok, tokenBytes)
+}
+
+func (p *ModuleParser) errorContext() string {
+	switch p.currentField {
+	case fieldInitial:
+		return ""
+	case fieldModule:
+		return "module"
+	case fieldModuleStart:
+		return "module.start"
+	case fieldModuleImport, fieldModuleImportFunc:
+		if p.currentField == fieldModuleImportFunc {
+			return fmt.Sprintf("module.import[%d].func", p.currentImportIndex)
+		}
+		// TODO: table, memory or global
+		return fmt.Sprintf("module.import[%d]", p.currentImportIndex)
+	}
+	return ""
+}
diff --git a/wasm/wat/types.go b/wasm/wat/types.go
new file mode 100644
index 00000000..a0055807
--- /dev/null
+++ b/wasm/wat/types.go
@@ -0,0 +1,83 @@
+package wat
+
+import "fmt"
+
+// module corresponds to the text format of a WebAssembly module, and is an intermediate representation prior to
+// wasm.Module. This is primarily needed to resolve symbolic indexes like "$main" to raw numeric ones.
+//
+// Note: nothing is required per specification. Ex `(module)` is valid!
+//
+// See https://www.w3.org/TR/wasm-core-1/#functions%E2%91%A7
+type module struct {
+	// name is optional and starts with '$'. For example, "$test".
+	// See https://www.w3.org/TR/wasm-core-1/#modules%E2%91%A0%E2%91%A2
+	//
+	// Note: The name may also be stored in the wasm.Module CustomSection under the key "name" subsection 0.
+	// See https://www.w3.org/TR/wasm-core-1/#binary-namesec
+	name string
+
+	// importFuncs are imports describing functions "(import... (func...))" added in insertion order.
+	importFuncs []*importFunc
+
+	// startFunction is the function to call during wasm.Store Instantiate. The value is a importFunc.name, such as
+	// "$main", or its equivalent raw numeric index, such as "2".
+	//
+	// Note: When in raw numeric form, this is relative to imports. See wasm.Module StartSection for more.
+	//
+	// See https://www.w3.org/TR/wasm-core-1/#start-function%E2%91%A4
+	startFunction string
+}
+
+// importFunc corresponds to the text format of a WebAssembly function import.
+//
+// Note: nothing is required per specification. Ex `(import "" "" (func))` is valid!
+//
+// See https://www.w3.org/TR/wasm-core-1/#imports%E2%91%A0
+type importFunc struct {
+	// importIndex is the zero-based index in module.imports. This is needed because imports are not always functions.
+	importIndex int
+
+	// module is the possibly empty module name to import. Ex. "" or "Math"
+	//
+	// Note: This is not necessarily the module.name, so it does not need to begin with '$'!
+	module string
+
+	// name is the possibly empty entity name to import. Ex. "" or "PI"
+	//
+	// Note: This is not necessarily the funcName, so it does not need to begin with '$'!
+	name string
+
+	// funcName starts with '$'. For example, "$main".
+	//
+	// This name is only used for debugging. At runtime, functions are called based on raw numeric index. The function
+	// index space begins with imported functions, followed by any defined in this module.
+	// See https://www.w3.org/TR/wasm-core-1/#functions%E2%91%A7
+	//
+	// Note: The name may also be stored in the wasm.Module CustomSection under the key "name" subsection 1.
+	// See https://www.w3.org/TR/wasm-core-1/#binary-namesec
+	funcName string
+
+	// TODO: typeuse https://www.w3.org/TR/wasm-core-1/#text-typeuse
+}
+
+// formatError allows control over the format of formatError.Error
+type formatError struct {
+	// line is the source line number determined by unescaped '\n' characters of the error or EOF
+	line int
+	// Col is the UTF-8 column number of the error or EOF
+	col int
+	// Context is where symbolically the error occurred. Ex "imports[1].func"
+	context string
+	cause   error
+}
+
+func (e *formatError) Error() string {
+	if e.context == "" { // error starting the file
+		return fmt.Sprintf("%d:%d: %v", e.line, e.col, e.cause)
+	}
+	return fmt.Sprintf("%d:%d: %v in %s", e.line, e.col, e.cause, e.context)
+}
+
+func (e *formatError) Unwrap() error {
+	return e.cause
+}


Test patch:
diff --git a/wasm/jit/jit_amd64_test.go b/wasm/jit/jit_amd64_test.go
index 31cf3c91..0a854096 100644
--- a/wasm/jit/jit_amd64_test.go
+++ b/wasm/jit/jit_amd64_test.go
@@ -918,141 +918,177 @@ func TestAmd64Builder_handleConstF64(t *testing.T) {
 }
 
 func TestAmd64Builder_handleAdd(t *testing.T) {
-	t.Run("int64", func(t *testing.T) {
-		o := &wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64}
-		t.Run("x1:reg,x2:reg", func(t *testing.T) {
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			x1Reg := int16(x86.REG_AX)
-			x2Reg := int16(x86.REG_R10)
-			x1Location := builder.locationStack.pushValueOnRegister(x1Reg)
-			builder.locationStack.pushValueOnRegister(x2Reg)
-			builder.movIntConstToRegister(100, x1Reg)
-			builder.movIntConstToRegister(300, x2Reg)
-			err := builder.handleAdd(o)
-			require.NoError(t, err)
-			require.Contains(t, builder.locationStack.usedRegisters, x1Reg)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Reg)
-
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(x1Location)
-			builder.returnFunction()
-
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			eng := newEngine()
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(1), eng.stackPointer)
-			require.Equal(t, uint64(400), eng.stack[eng.stackPointer-1])
-		})
-		t.Run("x1:stack,x2:reg", func(t *testing.T) {
-			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			_ = builder.locationStack.pushValueOnStack() // dummy value!
-			x1Location := builder.locationStack.pushValueOnStack()
-			x2Location := builder.locationStack.pushValueOnRegister(x86.REG_R10)
-			eng.stack[x1Location.stackPointer] = 5000
-			builder.movIntConstToRegister(300, x2Location.register)
-			err := builder.handleAdd(o)
-			require.NoError(t, err)
+	t.Run("int32", func(t *testing.T) {
+		const x1Value uint32 = 113
+		const x2Value uint32 = 41
+		builder := requireNewBuilder(t)
+		builder.initializeReservedRegisters()
+		err := builder.handleConstI32(&wazeroir.OperationConstI32{Value: x1Value})
+		require.NoError(t, err)
+		x1 := builder.locationStack.peek()
+		err = builder.handleConstI32(&wazeroir.OperationConstI32{Value: x2Value})
+		require.NoError(t, err)
+		x2 := builder.locationStack.peek()
 
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(builder.locationStack.peek())
-			builder.returnFunction()
+		err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI32})
+		require.NoError(t, err)
+		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
 
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(2), eng.stackPointer)
-			require.Equal(t, uint64(5300), eng.stack[eng.stackPointer-1])
-		})
-		t.Run("x1:stack,x2:stack", func(t *testing.T) {
-			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			_ = builder.locationStack.pushValueOnStack() // dummy value!
-			x1Location := builder.locationStack.pushValueOnStack()
-			x2Location := builder.locationStack.pushValueOnStack()
-			eng.stack[x1Location.stackPointer] = 5000
-			eng.stack[x2Location.stackPointer] = 13
-			err := builder.handleAdd(o)
-			require.NoError(t, err)
-			require.True(t, x1Location.onRegister())
-			require.Contains(t, builder.locationStack.usedRegisters, x1Location.register)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Location.register)
+		// To verify the behavior, we push the value
+		// to the stack.
+		builder.releaseRegisterToStack(x1)
+		builder.returnFunction()
 
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(builder.locationStack.peek())
-			builder.returnFunction()
+		// Assemble.
+		code, err := builder.compile()
+		require.NoError(t, err)
+		// Run code.
+		eng := newEngine()
+		mem := newMemoryInst()
+		jitcall(
+			uintptr(unsafe.Pointer(&code[0])),
+			uintptr(unsafe.Pointer(eng)),
+			uintptr(unsafe.Pointer(&mem.Buffer[0])),
+		)
+		// Check the stack.
+		require.Equal(t, uint64(1), eng.stackPointer)
+		require.Equal(t, uint64(x1Value+x2Value), eng.stack[eng.stackPointer-1])
+	})
+	t.Run("int64", func(t *testing.T) {
+		const x1Value uint64 = 1 << 35
+		const x2Value uint64 = 41
+		builder := requireNewBuilder(t)
+		builder.initializeReservedRegisters()
+		err := builder.handleConstI64(&wazeroir.OperationConstI64{Value: x1Value})
+		require.NoError(t, err)
+		x1 := builder.locationStack.peek()
+		err = builder.handleConstI64(&wazeroir.OperationConstI64{Value: x2Value})
+		require.NoError(t, err)
+		x2 := builder.locationStack.peek()
 
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(2), eng.stackPointer)
-			require.Equal(t, uint64(5013), eng.stack[eng.stackPointer-1])
-		})
-		t.Run("x1:reg,x2:stack", func(t *testing.T) {
-			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			_ = builder.locationStack.pushValueOnStack() // dummy value!
-			x1Location := builder.locationStack.pushValueOnRegister(x86.REG_R10)
-			x2Location := builder.locationStack.pushValueOnStack()
-			eng.stack[x2Location.stackPointer] = 5000
-			builder.movIntConstToRegister(132, x1Location.register)
-			err := builder.handleAdd(o)
-			require.NoError(t, err)
-			require.True(t, x1Location.onRegister())
-			require.Contains(t, builder.locationStack.usedRegisters, x1Location.register)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Location.register)
+		err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
+		require.NoError(t, err)
+		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
 
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(builder.locationStack.peek())
-			builder.returnFunction()
+		// To verify the behavior, we push the value
+		// to the stack.
+		builder.releaseRegisterToStack(x1)
+		builder.returnFunction()
 
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(2), eng.stackPointer)
-			require.Equal(t, uint64(5132), eng.stack[eng.stackPointer-1])
-		})
+		// Assemble.
+		code, err := builder.compile()
+		require.NoError(t, err)
+		// Run code.
+		eng := newEngine()
+		mem := newMemoryInst()
+		jitcall(
+			uintptr(unsafe.Pointer(&code[0])),
+			uintptr(unsafe.Pointer(eng)),
+			uintptr(unsafe.Pointer(&mem.Buffer[0])),
+		)
+		// Check the stack.
+		require.Equal(t, uint64(1), eng.stackPointer)
+		require.Equal(t, uint64(x1Value+x2Value), eng.stack[eng.stackPointer-1])
+	})
+	t.Run("float32", func(t *testing.T) {
+		for i, tc := range []struct {
+			v1, v2 float32
+		}{
+			{v1: 1.1, v2: 2.3},
+			{v1: 1.1, v2: -2.3},
+			{v1: float32(math.Inf(1)), v2: -2.1},
+			{v1: float32(math.Inf(1)), v2: 2.1},
+			{v1: float32(math.Inf(-1)), v2: -2.1},
+			{v1: float32(math.Inf(-1)), v2: 2.1},
+		} {
+			tc := tc
+			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+				builder := requireNewBuilder(t)
+				builder.initializeReservedRegisters()
+				err := builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v1})
+				require.NoError(t, err)
+				x1 := builder.locationStack.peek()
+				err = builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v2})
+				require.NoError(t, err)
+				x2 := builder.locationStack.peek()
+
+				err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF32})
+				require.NoError(t, err)
+				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+
+				// To verify the behavior, we push the value
+				// to the stack.
+				builder.releaseRegisterToStack(x1)
+				builder.returnFunction()
+
+				// Assemble.
+				code, err := builder.compile()
+				require.NoError(t, err)
+				// Run code.
+				eng := newEngine()
+				mem := newMemoryInst()
+				jitcall(
+					uintptr(unsafe.Pointer(&code[0])),
+					uintptr(unsafe.Pointer(eng)),
+					uintptr(unsafe.Pointer(&mem.Buffer[0])),
+				)
+				// 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])))
+			})
+		}
+	})
+	t.Run("float64", func(t *testing.T) {
+		for i, tc := range []struct {
+			v1, v2 float64
+		}{
+			{v1: 1.1, v2: 2.3},
+			{v1: 1.1, v2: -2.3},
+			{v1: math.Inf(1), v2: -2.1},
+			{v1: math.Inf(1), v2: 2.1},
+			{v1: math.Inf(-1), v2: -2.1},
+			{v1: math.Inf(-1), v2: 2.1},
+		} {
+			tc := tc
+			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+				builder := requireNewBuilder(t)
+				builder.initializeReservedRegisters()
+				err := builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v1})
+				require.NoError(t, err)
+				x1 := builder.locationStack.peek()
+				err = builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v2})
+				require.NoError(t, err)
+				x2 := builder.locationStack.peek()
+
+				err = builder.handleAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
+				require.NoError(t, err)
+				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+
+				// To verify the behavior, we push the value
+				// to the stack.
+				builder.releaseRegisterToStack(x1)
+				builder.returnFunction()
+
+				// Assemble.
+				code, err := builder.compile()
+				require.NoError(t, err)
+				// Run code.
+				eng := newEngine()
+				mem := newMemoryInst()
+				jitcall(
+					uintptr(unsafe.Pointer(&code[0])),
+					uintptr(unsafe.Pointer(eng)),
+					uintptr(unsafe.Pointer(&mem.Buffer[0])),
+				)
+				// Check the stack.
+				require.Equal(t, uint64(1), eng.stackPointer)
+				require.Equal(t, tc.v1+tc.v2, math.Float64frombits(eng.stack[eng.stackPointer-1]))
+			})
+		}
 	})
 }
 
@@ -1127,6 +1163,10 @@ func TestAmd64Builder_handleLe(t *testing.T) {
 			{x1: -1, x2: 100, signed: false, exp: false},
 			{x1: 100, x2: 200, signed: true, exp: true},
 			{x1: 200, x2: 100, signed: true, exp: false},
+			{x1: 1 << 56, x2: 100, signed: true, exp: false},
+			{x1: 1 << 56, x2: 1 << 61, signed: true, exp: true},
+			{x1: math.MaxInt64, x2: 100, signed: true, exp: false},
+			{x1: math.MinInt64, x2: 100, signed: true, exp: true},
 		} {
 			var o *wazeroir.OperationLe
 			if tc.signed {
@@ -1176,144 +1216,314 @@ func TestAmd64Builder_handleLe(t *testing.T) {
 			}
 		}
 	})
+	t.Run("float32", func(t *testing.T) {
+		for i, tc := range []struct {
+			x1, x2 float32
+			exp    bool
+		}{
+			{x1: 100, x2: -1.1, exp: false},
+			{x1: -1, x2: 100, exp: true},
+			{x1: 100, x2: 200, exp: true},
+			{x1: 100.01234124, x2: 100.01234124, exp: true},
+			{x1: 100.01234124, x2: -100.01234124, exp: false},
+			{x1: 200.12315, x2: 100, exp: false},
+			{x1: float32(math.Inf(1)), x2: 100, exp: false},
+			{x1: float32(math.Inf(-1)), x2: 100, exp: true},
+		} {
+			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+				// Prepare operands.
+				builder := requireNewBuilder(t)
+				builder.initializeReservedRegisters()
+				err := builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.x1})
+				require.NoError(t, err)
+				x1 := builder.locationStack.peek()
+				err = builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.x2})
+				require.NoError(t, err)
+				x2 := builder.locationStack.peek()
+
+				// Emit the Le instructions,
+				err = builder.handleLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat32})
+				require.NoError(t, err)
+
+				// At this point, these registers must be consumed.
+				require.NotContains(t, builder.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				// Plus the result must be pushed.
+				require.Equal(t, uint64(1), builder.locationStack.sp)
+
+				// To verify the behavior, we push the flag value
+				// to the stack.
+				flag := builder.locationStack.peek()
+				require.True(t, flag.onConditionalRegister() && !flag.onRegister())
+				err = builder.moveConditionalToGeneralPurposeRegister(flag)
+				require.NoError(t, err)
+				require.True(t, !flag.onConditionalRegister() && flag.onRegister())
+				builder.releaseRegisterToStack(flag)
+				builder.returnFunction()
+
+				// Assemble.
+				code, err := builder.compile()
+				require.NoError(t, err)
+				// Run code.
+				eng := newEngine()
+				mem := newMemoryInst()
+				jitcall(
+					uintptr(unsafe.Pointer(&code[0])),
+					uintptr(unsafe.Pointer(eng)),
+					uintptr(unsafe.Pointer(&mem.Buffer[0])),
+				)
+				// Check the stack.
+				require.Equal(t, uint64(1), eng.stackPointer)
+				if tc.exp {
+					require.Equal(t, uint64(1), eng.stack[eng.stackPointer-1])
+				} else {
+					require.Equal(t, uint64(0), eng.stack[eng.stackPointer-1])
+				}
+			})
+		}
+	})
+	t.Run("float64", func(t *testing.T) {
+		for i, tc := range []struct {
+			x1, x2 float64
+			exp    bool
+		}{
+			{x1: 100, x2: -1.1, exp: false},
+			{x1: -1, x2: 100, exp: true},
+			{x1: 100, x2: 200, exp: true},
+			{x1: 100.01234124, x2: 100.01234124, exp: true},
+			{x1: 100.01234124, x2: -100.01234124, exp: false},
+			{x1: 200.12315, x2: 100, exp: false},
+			{x1: 6.8719476736e+10 /* = 1 << 36 */, x2: 100, exp: false},
+			{x1: 6.8719476736e+10 /* = 1 << 36 */, x2: 1.37438953472e+11 /* = 1 << 37*/, exp: true},
+			{x1: math.Inf(1), x2: 100, exp: false},
+			{x1: math.Inf(-1), x2: 100, exp: true},
+		} {
+			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+				// Prepare operands.
+				builder := requireNewBuilder(t)
+				builder.initializeReservedRegisters()
+				err := builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.x1})
+				require.NoError(t, err)
+				x1 := builder.locationStack.peek()
+				err = builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.x2})
+				require.NoError(t, err)
+				x2 := builder.locationStack.peek()
+
+				// Emit the Le instructions,
+				err = builder.handleLe(&wazeroir.OperationLe{Type: wazeroir.SignedTypeFloat64})
+				require.NoError(t, err)
+
+				// At this point, these registers must be consumed.
+				require.NotContains(t, builder.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+				// Plus the result must be pushed.
+				require.Equal(t, uint64(1), builder.locationStack.sp)
+
+				// To verify the behavior, we push the flag value
+				// to the stack.
+				flag := builder.locationStack.peek()
+				require.True(t, flag.onConditionalRegister() && !flag.onRegister())
+				err = builder.moveConditionalToGeneralPurposeRegister(flag)
+				require.NoError(t, err)
+				require.True(t, !flag.onConditionalRegister() && flag.onRegister())
+				builder.releaseRegisterToStack(flag)
+				builder.returnFunction()
+
+				// Assemble.
+				code, err := builder.compile()
+				require.NoError(t, err)
+				// Run code.
+				eng := newEngine()
+				mem := newMemoryInst()
+				jitcall(
+					uintptr(unsafe.Pointer(&code[0])),
+					uintptr(unsafe.Pointer(eng)),
+					uintptr(unsafe.Pointer(&mem.Buffer[0])),
+				)
+				// Check the stack.
+				require.Equal(t, uint64(1), eng.stackPointer)
+				if tc.exp {
+					require.Equal(t, uint64(1), eng.stack[eng.stackPointer-1])
+				} else {
+					require.Equal(t, uint64(0), eng.stack[eng.stackPointer-1])
+				}
+			})
+		}
+	})
 }
 
 func TestAmd64Builder_handleSub(t *testing.T) {
-	t.Run("int64", func(t *testing.T) {
-		o := &wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI64}
-		t.Run("x1:reg,x2:reg", func(t *testing.T) {
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			x1Reg := int16(x86.REG_AX)
-			x2Reg := int16(x86.REG_R10)
-			x1Location := builder.locationStack.pushValueOnRegister(x1Reg)
-			builder.locationStack.pushValueOnRegister(x2Reg)
-			builder.movIntConstToRegister(300, x1Reg)
-			builder.movIntConstToRegister(51, x2Reg)
-			err := builder.handleSub(o)
-			require.NoError(t, err)
-			require.Contains(t, builder.locationStack.usedRegisters, x1Reg)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Reg)
-
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(x1Location)
-			builder.returnFunction()
-
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			eng := newEngine()
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(1), eng.stackPointer)
-			require.Equal(t, uint64(249), eng.stack[eng.stackPointer-1])
-		})
-		t.Run("x1:stack,x2:reg", func(t *testing.T) {
-			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			_ = builder.locationStack.pushValueOnStack() // dummy value!
-			x1Location := builder.locationStack.pushValueOnStack()
-			x2Location := builder.locationStack.pushValueOnRegister(x86.REG_R10)
-			eng.stack[x1Location.stackPointer] = 5000
-			builder.movIntConstToRegister(300, x2Location.register)
-			err := builder.handleSub(o)
-			require.NoError(t, err)
+	t.Run("int32", func(t *testing.T) {
+		const x1Value uint32 = 1 << 31
+		const x2Value uint32 = 51
+		builder := requireNewBuilder(t)
+		builder.initializeReservedRegisters()
+		err := builder.handleConstI32(&wazeroir.OperationConstI32{Value: x1Value})
+		require.NoError(t, err)
+		x1 := builder.locationStack.peek()
+		err = builder.handleConstI32(&wazeroir.OperationConstI32{Value: x2Value})
+		require.NoError(t, err)
+		x2 := builder.locationStack.peek()
 
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(builder.locationStack.peek())
-			builder.returnFunction()
+		err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI32})
+		require.NoError(t, err)
+		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
 
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(2), eng.stackPointer)
-			require.Equal(t, uint64(4700), eng.stack[eng.stackPointer-1])
-		})
-		t.Run("x1:stack,x2:stack", func(t *testing.T) {
-			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			_ = builder.locationStack.pushValueOnStack() // dummy value!
-			x1Location := builder.locationStack.pushValueOnStack()
-			x2Location := builder.locationStack.pushValueOnStack()
-			eng.stack[x1Location.stackPointer] = 5000
-			eng.stack[x2Location.stackPointer] = 13
-			err := builder.handleSub(o)
-			require.NoError(t, err)
-			require.True(t, x1Location.onRegister())
-			require.Contains(t, builder.locationStack.usedRegisters, x1Location.register)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Location.register)
+		// To verify the behavior, we push the value
+		// to the stack.
+		builder.releaseRegisterToStack(x1)
+		builder.returnFunction()
 
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(builder.locationStack.peek())
-			builder.returnFunction()
+		// Assemble.
+		code, err := builder.compile()
+		require.NoError(t, err)
+		// Run code.
+		eng := newEngine()
+		mem := newMemoryInst()
+		jitcall(
+			uintptr(unsafe.Pointer(&code[0])),
+			uintptr(unsafe.Pointer(eng)),
+			uintptr(unsafe.Pointer(&mem.Buffer[0])),
+		)
+		// Check the stack.
+		require.Equal(t, uint64(1), eng.stackPointer)
+		require.Equal(t, uint64(x1Value-x2Value), eng.stack[eng.stackPointer-1])
+	})
+	t.Run("int64", func(t *testing.T) {
+		const x1Value uint64 = 1 << 35
+		const x2Value uint64 = 51
+		builder := requireNewBuilder(t)
+		builder.initializeReservedRegisters()
+		err := builder.handleConstI64(&wazeroir.OperationConstI64{Value: x1Value})
+		require.NoError(t, err)
+		x1 := builder.locationStack.peek()
+		err = builder.handleConstI64(&wazeroir.OperationConstI64{Value: x2Value})
+		require.NoError(t, err)
+		x2 := builder.locationStack.peek()
 
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(2), eng.stackPointer)
-			require.Equal(t, uint64(4987), eng.stack[eng.stackPointer-1])
-		})
-		t.Run("x1:reg,x2:stack", func(t *testing.T) {
-			eng := newEngine()
-			builder := requireNewBuilder(t)
-			builder.initializeReservedRegisters()
-			_ = builder.locationStack.pushValueOnStack() // dummy value!
-			x1Location := builder.locationStack.pushValueOnRegister(x86.REG_R10)
-			x2Location := builder.locationStack.pushValueOnStack()
-			eng.stack[x2Location.stackPointer] = 132
-			builder.movIntConstToRegister(5000, x1Location.register)
-			err := builder.handleSub(o)
-			require.NoError(t, err)
-			require.True(t, x1Location.onRegister())
-			require.Contains(t, builder.locationStack.usedRegisters, x1Location.register)
-			require.NotContains(t, builder.locationStack.usedRegisters, x2Location.register)
+		err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeI64})
+		require.NoError(t, err)
+		require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+		require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
 
-			// To verify the behavior, we push the value
-			// to the stack.
-			builder.releaseRegisterToStack(builder.locationStack.peek())
-			builder.returnFunction()
+		// To verify the behavior, we push the value
+		// to the stack.
+		builder.releaseRegisterToStack(x1)
+		builder.returnFunction()
 
-			// Assemble.
-			code, err := builder.compile()
-			require.NoError(t, err)
-			// Run code.
-			mem := newMemoryInst()
-			jitcall(
-				uintptr(unsafe.Pointer(&code[0])),
-				uintptr(unsafe.Pointer(eng)),
-				uintptr(unsafe.Pointer(&mem.Buffer[0])),
-			)
-			// Check the stack.
-			require.Equal(t, uint64(2), eng.stackPointer)
-			require.Equal(t, uint64(4868), eng.stack[eng.stackPointer-1])
-		})
+		// Assemble.
+		code, err := builder.compile()
+		require.NoError(t, err)
+		// Run code.
+		eng := newEngine()
+		mem := newMemoryInst()
+		jitcall(
+			uintptr(unsafe.Pointer(&code[0])),
+			uintptr(unsafe.Pointer(eng)),
+			uintptr(unsafe.Pointer(&mem.Buffer[0])),
+		)
+		// Check the stack.
+		require.Equal(t, uint64(1), eng.stackPointer)
+		require.Equal(t, x1Value-x2Value, eng.stack[eng.stackPointer-1])
+	})
+	t.Run("float32", func(t *testing.T) {
+		for i, tc := range []struct {
+			v1, v2 float32
+		}{
+			{v1: 1.1, v2: 2.3},
+			{v1: 1.1, v2: -2.3},
+			{v1: float32(math.Inf(1)), v2: -2.1},
+			{v1: float32(math.Inf(1)), v2: 2.1},
+			{v1: float32(math.Inf(-1)), v2: -2.1},
+			{v1: float32(math.Inf(-1)), v2: 2.1},
+		} {
+			tc := tc
+			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+				builder := requireNewBuilder(t)
+				builder.initializeReservedRegisters()
+				err := builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v1})
+				require.NoError(t, err)
+				x1 := builder.locationStack.peek()
+				err = builder.handleConstF32(&wazeroir.OperationConstF32{Value: tc.v2})
+				require.NoError(t, err)
+				x2 := builder.locationStack.peek()
+
+				err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeF32})
+				require.NoError(t, err)
+				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+
+				// To verify the behavior, we push the value
+				// to the stack.
+				builder.releaseRegisterToStack(x1)
+				builder.returnFunction()
+
+				// Assemble.
+				code, err := builder.compile()
+				require.NoError(t, err)
+				// Run code.
+				eng := newEngine()
+				mem := newMemoryInst()
+				jitcall(
+					uintptr(unsafe.Pointer(&code[0])),
+					uintptr(unsafe.Pointer(eng)),
+					uintptr(unsafe.Pointer(&mem.Buffer[0])),
+				)
+				// 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])))
+			})
+		}
+	})
+	t.Run("float64", func(t *testing.T) {
+		for i, tc := range []struct {
+			v1, v2 float64
+		}{
+			{v1: 1.1, v2: 2.3},
+			{v1: 1.1, v2: -2.3},
+			{v1: math.Inf(1), v2: -2.1},
+			{v1: math.Inf(1), v2: 2.1},
+			{v1: math.Inf(-1), v2: -2.1},
+			{v1: math.Inf(-1), v2: 2.1},
+		} {
+			tc := tc
+			t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+				builder := requireNewBuilder(t)
+				builder.initializeReservedRegisters()
+				err := builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v1})
+				require.NoError(t, err)
+				x1 := builder.locationStack.peek()
+				err = builder.handleConstF64(&wazeroir.OperationConstF64{Value: tc.v2})
+				require.NoError(t, err)
+				x2 := builder.locationStack.peek()
+
+				err = builder.handleSub(&wazeroir.OperationSub{Type: wazeroir.UnsignedTypeF64})
+				require.NoError(t, err)
+				require.Contains(t, builder.locationStack.usedRegisters, x1.register)
+				require.NotContains(t, builder.locationStack.usedRegisters, x2.register)
+
+				// To verify the behavior, we push the value
+				// to the stack.
+				builder.releaseRegisterToStack(x1)
+				builder.returnFunction()
+
+				// Assemble.
+				code, err := builder.compile()
+				require.NoError(t, err)
+				// Run code.
+				eng := newEngine()
+				mem := newMemoryInst()
+				jitcall(
+					uintptr(unsafe.Pointer(&code[0])),
+					uintptr(unsafe.Pointer(eng)),
+					uintptr(unsafe.Pointer(&mem.Buffer[0])),
+				)
+				// Check the stack.
+				require.Equal(t, uint64(1), eng.stackPointer)
+				require.Equal(t, tc.v1-tc.v2, math.Float64frombits(eng.stack[eng.stackPointer-1]))
+			})
+		}
 	})
 }
 
diff --git a/wasm/wat/lexer_test.go b/wasm/wat/lexer_test.go
index d7efabc7..bec41eb9 100644
--- a/wasm/wat/lexer_test.go
+++ b/wasm/wat/lexer_test.go
@@ -440,7 +440,7 @@ func TestLex(t *testing.T) {
 func TestLex_Errors(t *testing.T) {
 	tests := []struct {
 		name         string
-		parser       parseToken
+		parser       tokenParser
 		input        []byte
 		expectedLine int
 		expectedCol  int
@@ -591,7 +591,7 @@ func TestLex_Errors(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			parser := tc.parser
 			if parser == nil {
-				parser = noopParseToken
+				parser = noopTokenParser
 			}
 			line, col, err := lex(parser, tc.input)
 			require.Equal(t, tc.expectedLine, line)
@@ -611,7 +611,7 @@ func lexTokens(t *testing.T, input string) []*token {
 	return tokens
 }
 
-var noopParseToken parseToken = func(_ tokenType, _ []byte, _, _ int) error {
+var noopTokenParser tokenParser = func(_ tokenType, _ []byte, _, _ int) error {
 	return nil
 }
 
@@ -647,7 +647,7 @@ func BenchmarkLex(b *testing.B) {
 		b.Run(bm.name, func(b *testing.B) {
 			b.ReportAllocs()
 			for i := 0; i < b.N; i++ {
-				line, col, err := lex(noopParseToken, bm.data)
+				line, col, err := lex(noopTokenParser, bm.data)
 				if err != nil {
 					panic(fmt.Errorf("%d:%d: %w", line, col, err))
 				}
diff --git a/wasm/wat/parser_test.go b/wasm/wat/parser_test.go
new file mode 100644
index 00000000..d10e5c63
--- /dev/null
+++ b/wasm/wat/parser_test.go
@@ -0,0 +1,214 @@
+package wat
+
+import (
+	"fmt"
+	"testing"
+	"unicode/utf8"
+
+	"github.com/bytecodealliance/wasmtime-go"
+	"github.com/stretchr/testify/require"
+)
+
+func TestParseModule(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    string
+		expected *module
+	}{
+		{
+			name:     "empty",
+			input:    "(module)",
+			expected: &module{},
+		},
+		{
+			name:     "only name",
+			input:    "(module $tools)",
+			expected: &module{name: "$tools"},
+		},
+		{
+			name:     "import func empty",
+			input:    "(module (import \"foo\" \"bar\" (func)))", // ok empty sig
+			expected: &module{importFuncs: []*importFunc{{module: "foo", name: "bar"}}},
+		},
+		{
+			name:     "start function", // TODO: this is pointing to a funcidx not in the source!
+			input:    "(module (start $main))",
+			expected: &module{startFunction: "$main"},
+		},
+		{
+			name: "start imported function by name",
+			input: `(module
+	(import "" "hello" (func $hello))
+	(start $hello)
+)`,
+			expected: &module{
+				importFuncs:   []*importFunc{{name: "hello", importIndex: 0, funcName: "$hello"}},
+				startFunction: "$hello",
+			},
+		},
+		{
+			name: "start imported function by index",
+			input: `(module
+	(import "" "hello" (func))
+	(start 0)
+)`,
+			expected: &module{
+				importFuncs:   []*importFunc{{name: "hello", importIndex: 0}},
+				startFunction: "0",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			m, err := parseModule([]byte(tc.input))
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, m)
+		})
+	}
+}
+
+func TestParseModule_Errors(t *testing.T) {
+	tests := []struct {
+		name        string
+		input       []byte
+		expectedErr string
+	}{
+		{
+			name:        "no module",
+			input:       []byte("()"),
+			expectedErr: "1:2: expected field, but found )",
+		},
+		{
+			name:        "module invalid name",
+			input:       []byte("(module test)"), // must start with $
+			expectedErr: "1:9: unexpected keyword: test in module",
+		},
+		{
+			name:        "module double name",
+			input:       []byte("(module $foo $bar)"),
+			expectedErr: "1:14: redundant name: $bar in module",
+		},
+		{
+			name:        "module empty field",
+			input:       []byte("(module $foo ())"),
+			expectedErr: "1:15: expected field, but found ) in module",
+		},
+		{
+			name:        "module trailing )",
+			input:       []byte("(module $foo ))"),
+			expectedErr: "1:15: found ')' before '('",
+		},
+		{
+			name:        "import missing module",
+			input:       []byte("(module (import))"),
+			expectedErr: "1:16: missing module and name in module.import[0]",
+		},
+		{
+			name:        "import missing name",
+			input:       []byte("(module (import \"\"))"),
+			expectedErr: "1:19: missing name in module.import[0]",
+		},
+		{
+			name:        "import unquoted module",
+			input:       []byte("(module (import foo bar))"),
+			expectedErr: "1:17: unexpected keyword: foo in module.import[0]",
+		},
+		{
+			name:        "import double name",
+			input:       []byte("(module (import \"foo\" \"bar\" \"baz\")"),
+			expectedErr: "1:29: redundant name: baz in module.import[0]",
+		},
+		{
+			name:        "import missing desc",
+			input:       []byte("(module (import \"foo\" \"bar\"))"),
+			expectedErr: "1:28: missing description in module.import[0]",
+		},
+		{
+			name:        "import empty desc",
+			input:       []byte("(module (import \"foo\" \"bar\"())"),
+			expectedErr: "1:29: expected field, but found ) in module.import[0]",
+		},
+		{
+			name:        "import func invalid name",
+			input:       []byte("(module (import \"foo\" \"bar\" (func baz)))"),
+			expectedErr: "1:35: unexpected keyword: baz in module.import[0].func",
+		},
+		{
+			name:        "import func double desc",
+			input:       []byte("(module (import \"foo\" \"bar\" (func $main) (func $mein)))"),
+			expectedErr: "1:43: redundant field: func in module.import[0]",
+		},
+		{
+			name:        "start missing funcidx",
+			input:       []byte("(module (start))"),
+			expectedErr: "1:15: missing funcidx in module.start",
+		},
+		{
+			name:        "start double funcidx",
+			input:       []byte("(module (start $main $main))"),
+			expectedErr: "1:22: redundant funcidx: $main in module.start",
+		},
+		{
+			name:        "double start",
+			input:       []byte("(module (start $main) (start $main))"),
+			expectedErr: "1:30: redundant funcidx: $main in module.start",
+		},
+	}
+
+	for _, tt := range tests {
+		tc := tt
+
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := parseModule(tc.input)
+			require.EqualError(t, err, tc.expectedErr)
+		})
+	}
+}
+
+func BenchmarkParseExample(b *testing.B) {
+	simpleExample := []byte(`(module
+	(import "" "hello" (func $hello))
+	(start $hello)
+)`)
+
+	b.Run("vs utf8.Valid", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			if !utf8.Valid(simpleExample) {
+				panic("unexpected")
+			}
+		}
+	})
+	// Not a fair comparison as we are only parsing and not writing back %.wasm
+	// If possible, we should find a way to isolate only the lexing C Functions.
+	b.Run("vs wasmtime.Wat2Wasm", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			_, err := wasmtime.Wat2Wasm(string(simpleExample))
+			if err != nil {
+				panic(err)
+			}
+		}
+	})
+	b.Run("vs wat.lex(noop)", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			line, col, err := lex(noopTokenParser, simpleExample)
+			if err != nil {
+				panic(fmt.Errorf("%d:%d: %w", line, col, err))
+			}
+		}
+	})
+	b.Run("parseModule", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			_, err := parseModule(simpleExample)
+			if err != nil {
+				panic(err)
+			}
+		}
+	})
+}