Repository: tetratelabs/wazero
Pull request #97 (2021-12-30 03:44:05)

jit: add more test cases for add/sub operations.
This completes the support of add/sub operations for all types!


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)


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]))
+			})
+		}
 	})
 }