Files
wazero/internal/engine/compiler/compiler_numeric_test.go
Crypt Keeper 329ccca6b1 Switches from gofmt to gofumpt (#848)
This switches to gofumpt and applies changes, as I've noticed working
in dapr (who uses this) that it finds some things that are annoying,
such as inconsistent block formatting in test tables.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-11-09 05:48:24 +01:00

1582 lines
58 KiB
Go

package compiler
import (
"fmt"
"math"
"math/bits"
"testing"
"github.com/tetratelabs/wazero/internal/moremath"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wazeroir"
)
func TestCompiler_compileConsts(t *testing.T) {
for _, op := range []wazeroir.OperationKind{
wazeroir.OperationKindConstI32,
wazeroir.OperationKindConstI64,
wazeroir.OperationKindConstF32,
wazeroir.OperationKindConstF64,
wazeroir.OperationKindV128Const,
} {
op := op
t.Run(op.String(), func(t *testing.T) {
for _, val := range []uint64{
0x0, 0x1, 0x1111000, 1 << 16, 1 << 21, 1 << 27, 1 << 32, 1<<32 + 1, 1 << 53,
math.Float64bits(math.Inf(1)),
math.Float64bits(math.Inf(-1)),
math.Float64bits(math.NaN()),
math.MaxUint32,
math.MaxInt32,
math.MaxUint64,
math.MaxInt64,
uint64(math.Float32bits(float32(math.Inf(1)))),
uint64(math.Float32bits(float32(math.Inf(-1)))),
uint64(math.Float32bits(float32(math.NaN()))),
} {
t.Run(fmt.Sprintf("0x%x", val), func(t *testing.T) {
env := newCompilerEnvironment()
// Compile code.
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
switch op {
case wazeroir.OperationKindConstI32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(val)})
case wazeroir.OperationKindConstI64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: val})
case wazeroir.OperationKindConstF32:
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(val))})
case wazeroir.OperationKindConstF64:
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(val)})
case wazeroir.OperationKindV128Const:
err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: val, Hi: ^val})
}
require.NoError(t, err)
// After compiling const operations, we must see the register allocated value on the top of value.
loc := compiler.runtimeValueLocationStack().peek()
require.True(t, loc.onRegister())
if op == wazeroir.OperationKindV128Const {
require.Equal(t, runtimeValueTypeV128Hi, loc.valueType)
}
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
// Run native code.
env.exec(code)
// Compiler status must be returned.
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
if op == wazeroir.OperationKindV128Const {
require.Equal(t, uint64(2), env.stackPointer()) // a vector value consists of two uint64.
} else {
require.Equal(t, uint64(1), env.stackPointer())
}
switch op {
case wazeroir.OperationKindConstI32, wazeroir.OperationKindConstF32:
require.Equal(t, uint32(val), env.stackTopAsUint32())
case wazeroir.OperationKindConstI64, wazeroir.OperationKindConstF64:
require.Equal(t, val, env.stackTopAsUint64())
case wazeroir.OperationKindV128Const:
lo, hi := env.stackTopAsV128()
require.Equal(t, val, lo)
require.Equal(t, ^val, hi)
}
})
}
})
}
}
func TestCompiler_compile_Add_Sub_Mul(t *testing.T) {
for _, kind := range []wazeroir.OperationKind{
wazeroir.OperationKindAdd,
wazeroir.OperationKindSub,
wazeroir.OperationKindMul,
} {
kind := kind
t.Run(kind.String(), func(t *testing.T) {
for _, unsignedType := range []wazeroir.UnsignedType{
wazeroir.UnsignedTypeI32,
wazeroir.UnsignedTypeI64,
wazeroir.UnsignedTypeF32,
wazeroir.UnsignedTypeF64,
} {
unsignedType := unsignedType
t.Run(unsignedType.String(), func(t *testing.T) {
for _, values := range [][2]uint64{
{0, 0},
{1, 1},
{2, 1},
{100, 1},
{1, 0},
{0, 1},
{math.MaxInt16, math.MaxInt32},
{1 << 14, 1 << 21},
{1 << 14, 1 << 21},
{0xffff_ffff_ffff_ffff, 0},
{0xffff_ffff_ffff_ffff, 1},
{0, 0xffff_ffff_ffff_ffff},
{1, 0xffff_ffff_ffff_ffff},
{0, math.Float64bits(math.Inf(1))},
{0, math.Float64bits(math.Inf(-1))},
{math.Float64bits(math.Inf(1)), 1},
{math.Float64bits(math.Inf(-1)), 1},
{math.Float64bits(1.11231), math.Float64bits(math.Inf(1))},
{math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))},
{math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)},
{math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)},
{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))},
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))},
} {
x1, x2 := values[0], values[1]
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Emit consts operands.
for _, v := range []uint64{x1, x2} {
switch unsignedType {
case wazeroir.UnsignedTypeI32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
case wazeroir.UnsignedTypeI64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
case wazeroir.UnsignedTypeF32:
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
case wazeroir.UnsignedTypeF64:
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
}
require.NoError(t, err)
}
// At this point, two values exist.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
// Emit the operation.
switch kind {
case wazeroir.OperationKindAdd:
err = compiler.compileAdd(&wazeroir.OperationAdd{Type: unsignedType})
case wazeroir.OperationKindSub:
err = compiler.compileSub(&wazeroir.OperationSub{Type: unsignedType})
case wazeroir.OperationKindMul:
err = compiler.compileMul(&wazeroir.OperationMul{Type: unsignedType})
}
require.NoError(t, err)
// We consumed two values, but push the result back.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
resultLocation := compiler.runtimeValueLocationStack().peek()
// Plus the result must be located on a register.
require.True(t, resultLocation.onRegister())
// Also, the result must have an appropriate register type.
if unsignedType == wazeroir.UnsignedTypeF32 || unsignedType == wazeroir.UnsignedTypeF64 {
require.Equal(t, registerTypeVector, resultLocation.getRegisterType())
} else {
require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType())
}
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Compile and execute the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// Check the stack.
require.Equal(t, uint64(1), env.stackPointer())
switch kind {
case wazeroir.OperationKindAdd:
switch unsignedType {
case wazeroir.UnsignedTypeI32:
require.Equal(t, uint32(x1)+uint32(x2), env.stackTopAsUint32())
case wazeroir.UnsignedTypeI64:
require.Equal(t, x1+x2, env.stackTopAsUint64())
case wazeroir.UnsignedTypeF32:
exp := math.Float32frombits(uint32(x1)) + math.Float32frombits(uint32(x2))
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(float64(exp)) {
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
} else {
require.Equal(t, exp, env.stackTopAsFloat32())
}
case wazeroir.UnsignedTypeF64:
exp := math.Float64frombits(x1) + math.Float64frombits(x2)
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(exp) {
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
} else {
require.Equal(t, exp, env.stackTopAsFloat64())
}
}
case wazeroir.OperationKindSub:
switch unsignedType {
case wazeroir.UnsignedTypeI32:
require.Equal(t, uint32(x1)-uint32(x2), env.stackTopAsUint32())
case wazeroir.UnsignedTypeI64:
require.Equal(t, x1-x2, env.stackTopAsUint64())
case wazeroir.UnsignedTypeF32:
exp := math.Float32frombits(uint32(x1)) - math.Float32frombits(uint32(x2))
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(float64(exp)) {
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
} else {
require.Equal(t, exp, env.stackTopAsFloat32())
}
case wazeroir.UnsignedTypeF64:
exp := math.Float64frombits(x1) - math.Float64frombits(x2)
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(exp) {
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
} else {
require.Equal(t, exp, env.stackTopAsFloat64())
}
}
case wazeroir.OperationKindMul:
switch unsignedType {
case wazeroir.UnsignedTypeI32:
require.Equal(t, uint32(x1)*uint32(x2), env.stackTopAsUint32())
case wazeroir.UnsignedTypeI64:
require.Equal(t, x1*x2, env.stackTopAsUint64())
case wazeroir.UnsignedTypeF32:
exp := math.Float32frombits(uint32(x1)) * math.Float32frombits(uint32(x2))
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(float64(exp)) {
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
} else {
require.Equal(t, exp, env.stackTopAsFloat32())
}
case wazeroir.UnsignedTypeF64:
exp := math.Float64frombits(x1) * math.Float64frombits(x2)
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(exp) {
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
} else {
require.Equal(t, exp, env.stackTopAsFloat64())
}
}
}
})
}
})
}
})
}
}
func TestCompiler_compile_And_Or_Xor_Shl_Rotl_Rotr(t *testing.T) {
for _, kind := range []wazeroir.OperationKind{
wazeroir.OperationKindAnd,
wazeroir.OperationKindOr,
wazeroir.OperationKindXor,
wazeroir.OperationKindShl,
wazeroir.OperationKindRotl,
wazeroir.OperationKindRotr,
} {
kind := kind
t.Run(kind.String(), func(t *testing.T) {
for _, unsignedInt := range []wazeroir.UnsignedInt{
wazeroir.UnsignedInt32,
wazeroir.UnsignedInt64,
} {
unsignedInt := unsignedInt
t.Run(unsignedInt.String(), func(t *testing.T) {
for _, values := range [][2]uint64{
{0, 0},
{0, 1},
{1, 0},
{1, 1},
{1 << 31, 1},
{1, 1 << 31},
{1 << 31, 1 << 31},
{1 << 63, 1},
{1, 1 << 63},
{1 << 63, 1 << 63},
} {
x1, x2 := values[0], values[1]
for _, x1OnRegister := range []bool{
true, false,
} {
x1OnRegister := x1OnRegister
t.Run(fmt.Sprintf("x1=0x%x(on_register=%v),x2=0x%x", x1, x1OnRegister, x2), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Emit consts operands.
var x1Location *runtimeValueLocation
switch unsignedInt {
case wazeroir.UnsignedInt32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(x1)})
require.NoError(t, err)
x1Location = compiler.runtimeValueLocationStack().peek()
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2})
require.NoError(t, err)
case wazeroir.UnsignedInt64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x1})
require.NoError(t, err)
x1Location = compiler.runtimeValueLocationStack().peek()
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2})
require.NoError(t, err)
}
if !x1OnRegister {
compiler.compileReleaseRegisterToStack(x1Location)
}
// At this point, two values exist.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
// Emit the operation.
switch kind {
case wazeroir.OperationKindAnd:
err = compiler.compileAnd(&wazeroir.OperationAnd{Type: unsignedInt})
case wazeroir.OperationKindOr:
err = compiler.compileOr(&wazeroir.OperationOr{Type: unsignedInt})
case wazeroir.OperationKindXor:
err = compiler.compileXor(&wazeroir.OperationXor{Type: unsignedInt})
case wazeroir.OperationKindShl:
err = compiler.compileShl(&wazeroir.OperationShl{Type: unsignedInt})
case wazeroir.OperationKindRotl:
err = compiler.compileRotl(&wazeroir.OperationRotl{Type: unsignedInt})
case wazeroir.OperationKindRotr:
err = compiler.compileRotr(&wazeroir.OperationRotr{Type: unsignedInt})
}
require.NoError(t, err)
// We consumed two values, but push the result back.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
resultLocation := compiler.runtimeValueLocationStack().peek()
// Also, the result must have an appropriate register type.
require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType())
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Compile and execute the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// Check the stack.
require.Equal(t, uint64(1), env.stackPointer())
switch kind {
case wazeroir.OperationKindAnd:
switch unsignedInt {
case wazeroir.UnsignedInt32:
require.Equal(t, uint32(x1)&uint32(x2), env.stackTopAsUint32())
case wazeroir.UnsignedInt64:
require.Equal(t, x1&x2, env.stackTopAsUint64())
}
case wazeroir.OperationKindOr:
switch unsignedInt {
case wazeroir.UnsignedInt32:
require.Equal(t, uint32(x1)|uint32(x2), env.stackTopAsUint32())
case wazeroir.UnsignedInt64:
require.Equal(t, x1|x2, env.stackTopAsUint64())
}
case wazeroir.OperationKindXor:
switch unsignedInt {
case wazeroir.UnsignedInt32:
require.Equal(t, uint32(x1)^uint32(x2), env.stackTopAsUint32())
case wazeroir.UnsignedInt64:
require.Equal(t, x1^x2, env.stackTopAsUint64())
}
case wazeroir.OperationKindShl:
switch unsignedInt {
case wazeroir.UnsignedInt32:
require.Equal(t, uint32(x1)<<uint32(x2%32), env.stackTopAsUint32())
case wazeroir.UnsignedInt64:
require.Equal(t, x1<<(x2%64), env.stackTopAsUint64())
}
case wazeroir.OperationKindRotl:
switch unsignedInt {
case wazeroir.UnsignedInt32:
require.Equal(t, bits.RotateLeft32(uint32(x1), int(x2)), env.stackTopAsUint32())
case wazeroir.UnsignedInt64:
require.Equal(t, bits.RotateLeft64(x1, int(x2)), env.stackTopAsUint64())
}
case wazeroir.OperationKindRotr:
switch unsignedInt {
case wazeroir.UnsignedInt32:
require.Equal(t, bits.RotateLeft32(uint32(x1), -int(x2)), env.stackTopAsUint32())
case wazeroir.UnsignedInt64:
require.Equal(t, bits.RotateLeft64(x1, -int(x2)), env.stackTopAsUint64())
}
}
})
}
}
})
}
})
}
}
func TestCompiler_compileShr(t *testing.T) {
kind := wazeroir.OperationKindShr
t.Run(kind.String(), func(t *testing.T) {
for _, signedInt := range []wazeroir.SignedInt{
wazeroir.SignedInt32,
wazeroir.SignedInt64,
wazeroir.SignedUint32,
wazeroir.SignedUint64,
} {
signedInt := signedInt
t.Run(signedInt.String(), func(t *testing.T) {
for _, values := range [][2]uint64{
{0, 0},
{0, 1},
{1, 0},
{1, 1},
{1 << 31, 1},
{1, 1 << 31},
{1 << 31, 1 << 31},
{1 << 63, 1},
{1, 1 << 63},
{1 << 63, 1 << 63},
} {
x1, x2 := values[0], values[1]
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Emit consts operands.
for _, v := range []uint64{x1, x2} {
switch signedInt {
case wazeroir.SignedInt32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))})
case wazeroir.SignedInt64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
case wazeroir.SignedUint32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
case wazeroir.SignedUint64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
}
require.NoError(t, err)
}
// At this point, two values exist.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
// Emit the operation.
err = compiler.compileShr(&wazeroir.OperationShr{Type: signedInt})
require.NoError(t, err)
// We consumed two values, but push the result back.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
resultLocation := compiler.runtimeValueLocationStack().peek()
// Plus the result must be located on a register.
require.True(t, resultLocation.onRegister())
// Also, the result must have an appropriate register type.
require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType())
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Compile and execute the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// Check the stack.
require.Equal(t, uint64(1), env.stackPointer())
switch signedInt {
case wazeroir.SignedInt32:
require.Equal(t, int32(x1)>>(uint32(x2)%32), env.stackTopAsInt32())
case wazeroir.SignedInt64:
require.Equal(t, int64(x1)>>(x2%64), env.stackTopAsInt64())
case wazeroir.SignedUint32:
require.Equal(t, uint32(x1)>>(uint32(x2)%32), env.stackTopAsUint32())
case wazeroir.SignedUint64:
require.Equal(t, x1>>(x2%64), env.stackTopAsUint64())
}
})
}
})
}
})
}
func TestCompiler_compile_Le_Lt_Gt_Ge_Eq_Eqz_Ne(t *testing.T) {
for _, kind := range []wazeroir.OperationKind{
wazeroir.OperationKindEq,
wazeroir.OperationKindEqz,
wazeroir.OperationKindNe,
wazeroir.OperationKindLe,
wazeroir.OperationKindLt,
wazeroir.OperationKindGe,
wazeroir.OperationKindGt,
} {
kind := kind
t.Run(kind.String(), func(t *testing.T) {
for _, signedType := range []wazeroir.SignedType{
wazeroir.SignedTypeUint32,
wazeroir.SignedTypeUint64,
wazeroir.SignedTypeInt32,
wazeroir.SignedTypeInt64,
wazeroir.SignedTypeFloat32,
wazeroir.SignedTypeFloat64,
} {
signedType := signedType
t.Run(signedType.String(), func(t *testing.T) {
for _, values := range [][2]uint64{
{0, 0},
{1, 1},
{2, 1},
{100, 1},
{1, 0},
{0, 1},
{math.MaxInt16, math.MaxInt32},
{1 << 14, 1 << 21},
{1 << 14, 1 << 21},
{0xffff_ffff_ffff_ffff, 0},
{0xffff_ffff_ffff_ffff, 1},
{0, 0xffff_ffff_ffff_ffff},
{1, 0xffff_ffff_ffff_ffff},
{1, math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), 1},
{0xffff_ffff_ffff_ffff, math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), 0xffff_ffff_ffff_ffff},
{math.Float64bits(math.MaxFloat32), 1},
{math.Float64bits(math.SmallestNonzeroFloat32), 1},
{math.Float64bits(math.MaxFloat64), 1},
{math.Float64bits(math.SmallestNonzeroFloat64), 1},
{0, math.Float64bits(math.Inf(1))},
{0, math.Float64bits(math.Inf(-1))},
{math.Float64bits(math.Inf(1)), 0},
{math.Float64bits(math.Inf(-1)), 0},
{math.Float64bits(math.Inf(1)), 1},
{math.Float64bits(math.Inf(-1)), 1},
{math.Float64bits(1.11231), math.Float64bits(math.Inf(1))},
{math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))},
{math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)},
{math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)},
{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))},
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))},
} {
x1, x2 := values[0], values[1]
isEqz := kind == wazeroir.OperationKindEqz
if isEqz && (signedType == wazeroir.SignedTypeFloat32 || signedType == wazeroir.SignedTypeFloat64) {
// Eqz isn't defined for float.
return
}
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Emit consts operands.
for _, v := range []uint64{x1, x2} {
switch signedType {
case wazeroir.SignedTypeUint32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
case wazeroir.SignedTypeInt32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))})
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
case wazeroir.SignedTypeFloat32:
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
case wazeroir.SignedTypeFloat64:
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
}
require.NoError(t, err)
}
if isEqz {
// Eqz only needs one value, so pop the top one (x2).
compiler.runtimeValueLocationStack().pop()
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
} else {
// At this point, two values exist for comparison.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
}
// Emit the operation.
switch kind {
case wazeroir.OperationKindLe:
err = compiler.compileLe(&wazeroir.OperationLe{Type: signedType})
case wazeroir.OperationKindLt:
err = compiler.compileLt(&wazeroir.OperationLt{Type: signedType})
case wazeroir.OperationKindGe:
err = compiler.compileGe(&wazeroir.OperationGe{Type: signedType})
case wazeroir.OperationKindGt:
err = compiler.compileGt(&wazeroir.OperationGt{Type: signedType})
case wazeroir.OperationKindEq:
// Eq uses UnsignedType instead, so we translate the signed one.
switch signedType {
case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI64})
case wazeroir.SignedTypeFloat32:
err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeF32})
case wazeroir.SignedTypeFloat64:
err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeF64})
}
case wazeroir.OperationKindNe:
// Ne uses UnsignedType, so we translate the signed one.
switch signedType {
case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32})
case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI64})
case wazeroir.SignedTypeFloat32:
err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeF32})
case wazeroir.SignedTypeFloat64:
err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeF64})
}
case wazeroir.OperationKindEqz:
// Eqz uses UnsignedInt, so we translate the signed one.
switch signedType {
case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
err = compiler.compileEqz(&wazeroir.OperationEqz{Type: wazeroir.UnsignedInt32})
case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
err = compiler.compileEqz(&wazeroir.OperationEqz{Type: wazeroir.UnsignedInt64})
}
}
require.NoError(t, err)
// We consumed two values, but push the result back.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Compile and execute the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// There should only be one value on the stack
require.Equal(t, uint64(1), env.stackPointer())
actual := env.stackTopAsUint32() == 1
switch kind {
case wazeroir.OperationKindLe:
switch signedType {
case wazeroir.SignedTypeInt32:
require.Equal(t, int32(x1) <= int32(x2), actual)
case wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) <= uint32(x2), actual)
case wazeroir.SignedTypeInt64:
require.Equal(t, int64(x1) <= int64(x2), actual)
case wazeroir.SignedTypeUint64:
require.Equal(t, x1 <= x2, actual)
case wazeroir.SignedTypeFloat32:
require.Equal(t, math.Float32frombits(uint32(x1)) <= math.Float32frombits(uint32(x2)), actual)
case wazeroir.SignedTypeFloat64:
require.Equal(t, math.Float64frombits(x1) <= math.Float64frombits(x2), actual)
}
case wazeroir.OperationKindLt:
switch signedType {
case wazeroir.SignedTypeInt32:
require.Equal(t, int32(x1) < int32(x2), actual)
case wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) < uint32(x2), actual)
case wazeroir.SignedTypeInt64:
require.Equal(t, int64(x1) < int64(x2), actual)
case wazeroir.SignedTypeUint64:
require.Equal(t, x1 < x2, actual)
case wazeroir.SignedTypeFloat32:
require.Equal(t, math.Float32frombits(uint32(x1)) < math.Float32frombits(uint32(x2)), actual)
case wazeroir.SignedTypeFloat64:
require.Equal(t, math.Float64frombits(x1) < math.Float64frombits(x2), actual)
}
case wazeroir.OperationKindGe:
switch signedType {
case wazeroir.SignedTypeInt32:
require.Equal(t, int32(x1) >= int32(x2), actual)
case wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) >= uint32(x2), actual)
case wazeroir.SignedTypeInt64:
require.Equal(t, int64(x1) >= int64(x2), actual)
case wazeroir.SignedTypeUint64:
require.Equal(t, x1 >= x2, actual)
case wazeroir.SignedTypeFloat32:
require.Equal(t, math.Float32frombits(uint32(x1)) >= math.Float32frombits(uint32(x2)), actual)
case wazeroir.SignedTypeFloat64:
require.Equal(t, math.Float64frombits(x1) >= math.Float64frombits(x2), actual)
}
case wazeroir.OperationKindGt:
switch signedType {
case wazeroir.SignedTypeInt32:
require.Equal(t, int32(x1) > int32(x2), actual)
case wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) > uint32(x2), actual)
case wazeroir.SignedTypeInt64:
require.Equal(t, int64(x1) > int64(x2), actual)
case wazeroir.SignedTypeUint64:
require.Equal(t, x1 > x2, actual)
case wazeroir.SignedTypeFloat32:
require.Equal(t, math.Float32frombits(uint32(x1)) > math.Float32frombits(uint32(x2)), actual)
case wazeroir.SignedTypeFloat64:
require.Equal(t, math.Float64frombits(x1) > math.Float64frombits(x2), actual)
}
case wazeroir.OperationKindEq:
switch signedType {
case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) == uint32(x2), actual)
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
require.Equal(t, x1 == x2, actual)
case wazeroir.SignedTypeFloat32:
require.Equal(t, math.Float32frombits(uint32(x1)) == math.Float32frombits(uint32(x2)), actual)
case wazeroir.SignedTypeFloat64:
require.Equal(t, math.Float64frombits(x1) == math.Float64frombits(x2), actual)
}
case wazeroir.OperationKindNe:
switch signedType {
case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) != uint32(x2), actual)
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
require.Equal(t, x1 != x2, actual)
case wazeroir.SignedTypeFloat32:
require.Equal(t, math.Float32frombits(uint32(x1)) != math.Float32frombits(uint32(x2)), actual)
case wazeroir.SignedTypeFloat64:
require.Equal(t, math.Float64frombits(x1) != math.Float64frombits(x2), actual)
}
case wazeroir.OperationKindEqz:
switch signedType {
case wazeroir.SignedTypeInt32, wazeroir.SignedTypeUint32:
require.Equal(t, uint32(x1) == 0, actual)
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
require.Equal(t, x1 == 0, actual)
}
}
})
}
})
}
})
}
}
func TestCompiler_compile_Clz_Ctz_Popcnt(t *testing.T) {
for _, kind := range []wazeroir.OperationKind{
wazeroir.OperationKindClz,
wazeroir.OperationKindCtz,
wazeroir.OperationKindPopcnt,
} {
kind := kind
t.Run(kind.String(), func(t *testing.T) {
for _, tp := range []wazeroir.UnsignedInt{wazeroir.UnsignedInt32, wazeroir.UnsignedInt64} {
tp := tp
is32bit := tp == wazeroir.UnsignedInt32
t.Run(tp.String(), func(t *testing.T) {
for _, v := range []uint64{
0, 1, 1 << 4, 1 << 6, 1 << 31,
0b11111111110000, 0b010101010, 0b1111111111111, math.MaxUint64,
} {
name := fmt.Sprintf("%064b", v)
if is32bit {
name = fmt.Sprintf("%032b", v)
}
t.Run(name, func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
if is32bit {
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
} else {
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
}
require.NoError(t, err)
switch kind {
case wazeroir.OperationKindClz:
err = compiler.compileClz(&wazeroir.OperationClz{Type: tp})
case wazeroir.OperationKindCtz:
err = compiler.compileCtz(&wazeroir.OperationCtz{Type: tp})
case wazeroir.OperationKindPopcnt:
err = compiler.compilePopcnt(&wazeroir.OperationPopcnt{Type: tp})
}
require.NoError(t, err)
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate and run the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
// One value must be pushed as a result.
require.Equal(t, uint64(1), env.stackPointer())
switch kind {
case wazeroir.OperationKindClz:
if is32bit {
require.Equal(t, bits.LeadingZeros32(uint32(v)), int(env.stackTopAsUint32()))
} else {
require.Equal(t, bits.LeadingZeros64(v), int(env.stackTopAsUint32()))
}
case wazeroir.OperationKindCtz:
if is32bit {
require.Equal(t, bits.TrailingZeros32(uint32(v)), int(env.stackTopAsUint32()))
} else {
require.Equal(t, bits.TrailingZeros64(v), int(env.stackTopAsUint32()))
}
case wazeroir.OperationKindPopcnt:
if is32bit {
require.Equal(t, bits.OnesCount32(uint32(v)), int(env.stackTopAsUint32()))
} else {
require.Equal(t, bits.OnesCount64(v), int(env.stackTopAsUint32()))
}
}
})
}
})
}
})
}
}
func TestCompiler_compile_Min_Max_Copysign(t *testing.T) {
tests := []struct {
name string
is32bit bool
setupFunc func(t *testing.T, compiler compilerImpl)
verifyFunc func(t *testing.T, x1, x2 float64, raw uint64)
}{
{
name: "min-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileMin(&wazeroir.OperationMin{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
exp := moremath.WasmCompatMin32(float32(x1), float32(x2))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, math.Float32bits(exp), math.Float32bits(actual))
}
},
},
{
name: "min-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileMin(&wazeroir.OperationMin{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
exp := moremath.WasmCompatMin64(x1, x2)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, math.Float64bits(exp), math.Float64bits(actual))
}
},
},
{
name: "max-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileMax(&wazeroir.OperationMax{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
exp := moremath.WasmCompatMax32(float32(x1), float32(x2))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, math.Float32bits(exp), math.Float32bits(actual))
}
},
},
{
name: "max-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileMax(&wazeroir.OperationMax{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
exp := moremath.WasmCompatMax64(x1, x2)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, math.Float64bits(exp), math.Float64bits(actual))
}
},
},
{
name: "copysign-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileCopysign(&wazeroir.OperationCopysign{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
exp := float32(math.Copysign(float64(float32(x1)), float64(float32(x2))))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "copysign-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileCopysign(&wazeroir.OperationCopysign{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
exp := math.Copysign(x1, x2)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, vs := range [][2]float64{
{math.Copysign(0, 1), math.Copysign(0, 1)},
{math.Copysign(0, -1), math.Copysign(0, 1)},
{math.Copysign(0, 1), math.Copysign(0, -1)},
{math.Copysign(0, -1), math.Copysign(0, -1)},
{100, -1.1},
{100, 0},
{0, 0},
{1, 1},
{-1, 100},
{100, 200},
{100.01234124, 100.01234124},
{100.01234124, -100.01234124},
{200.12315, 100},
{6.8719476736e+10 /* = 1 << 36 */, 100},
{6.8719476736e+10 /* = 1 << 36 */, 1.37438953472e+11 /* = 1 << 37*/},
{math.Inf(1), 100},
{math.Inf(1), -100},
{100, math.Inf(1)},
{-100, math.Inf(1)},
{math.Inf(-1), 100},
{math.Inf(-1), -100},
{100, math.Inf(-1)},
{-100, math.Inf(-1)},
{math.Inf(1), 0},
{math.Inf(-1), 0},
{0, math.Inf(1)},
{0, math.Inf(-1)},
{math.NaN(), 0},
{0, math.NaN()},
{math.NaN(), 12321},
{12313, math.NaN()},
{math.NaN(), math.NaN()},
} {
x1, x2 := vs[0], vs[1]
t.Run(fmt.Sprintf("x1=%f_x2=%f", x1, x2), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Setup the target values.
if tc.is32bit {
err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(x1)})
require.NoError(t, err)
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(x2)})
require.NoError(t, err)
} else {
err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: x1})
require.NoError(t, err)
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: x2})
require.NoError(t, err)
}
// At this point two values are pushed.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
require.Equal(t, 2, len(compiler.runtimeValueLocationStack().usedRegisters))
tc.setupFunc(t, compiler)
// We consumed two values, but push one value after operation.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate and run the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, uint64(1), env.stackPointer()) // Result must be pushed!
tc.verifyFunc(t, x1, x2, env.stackTopAsUint64())
})
}
})
}
}
func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
tests := []struct {
name string
is32bit bool
setupFunc func(t *testing.T, compiler compilerImpl)
verifyFunc func(t *testing.T, v float64, raw uint64)
}{
{
name: "abs-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileAbs(&wazeroir.OperationAbs{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := float32(math.Abs(float64(v)))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "abs-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileAbs(&wazeroir.OperationAbs{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := math.Abs(v)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "neg-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileNeg(&wazeroir.OperationNeg{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := -float32(v)
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "neg-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileNeg(&wazeroir.OperationNeg{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := -v
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "ceil-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileCeil(&wazeroir.OperationCeil{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := float32(math.Ceil(float64(v)))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "ceil-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileCeil(&wazeroir.OperationCeil{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := math.Ceil(v)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "floor-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileFloor(&wazeroir.OperationFloor{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := float32(math.Floor(float64(v)))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "floor-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileFloor(&wazeroir.OperationFloor{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := math.Floor(v)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "trunc-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileTrunc(&wazeroir.OperationTrunc{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := float32(math.Trunc(float64(v)))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "trunc-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileTrunc(&wazeroir.OperationTrunc{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := math.Trunc(v)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "nearest-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileNearest(&wazeroir.OperationNearest{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := moremath.WasmCompatNearestF32(float32(v))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "nearest-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileNearest(&wazeroir.OperationNearest{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := moremath.WasmCompatNearestF64(v)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "sqrt-32-bit",
is32bit: true,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileSqrt(&wazeroir.OperationSqrt{Type: wazeroir.Float32})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := float32(math.Sqrt(float64(v)))
actual := math.Float32frombits(uint32(raw))
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(float64(actual)))
} else {
require.Equal(t, exp, actual)
}
},
},
{
name: "sqrt-64-bit",
is32bit: false,
setupFunc: func(t *testing.T, compiler compilerImpl) {
err := compiler.compileSqrt(&wazeroir.OperationSqrt{Type: wazeroir.Float64})
require.NoError(t, err)
},
verifyFunc: func(t *testing.T, v float64, raw uint64) {
exp := math.Sqrt(v)
actual := math.Float64frombits(raw)
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
require.True(t, math.IsNaN(actual))
} else {
require.Equal(t, exp, actual)
}
},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
for _, v := range []float64{
0, 1 << 63, 1<<63 | 12345, 1 << 31,
1<<31 | 123455, 6.8719476736e+10,
// This verifies that the impl is Wasm compatible in nearest, rather than being equivalent of math.Round.
// See moremath.WasmCompatNearestF32 and moremath.WasmCompatNearestF64
-4.5,
1.37438953472e+11, -1.3,
-1231.123, 1.3, 100.3, -100.3, 1231.123,
math.Inf(1), math.Inf(-1), math.NaN(),
} {
v := v
t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
if tc.is32bit {
err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(v)})
require.NoError(t, err)
} else {
err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: v})
require.NoError(t, err)
}
// At this point two values are pushed.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
tc.setupFunc(t, compiler)
// We consumed one value, but push the result after operation.
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate and run the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, uint64(1), env.stackPointer()) // Result must be pushed!
tc.verifyFunc(t, v, env.stackTopAsUint64())
})
}
})
}
}
func TestCompiler_compile_Div_Rem(t *testing.T) {
for _, kind := range []wazeroir.OperationKind{
wazeroir.OperationKindDiv,
wazeroir.OperationKindRem,
} {
kind := kind
t.Run(kind.String(), func(t *testing.T) {
for _, signedType := range []wazeroir.SignedType{
wazeroir.SignedTypeUint32,
wazeroir.SignedTypeUint64,
wazeroir.SignedTypeInt32,
wazeroir.SignedTypeInt64,
wazeroir.SignedTypeFloat32,
wazeroir.SignedTypeFloat64,
} {
signedType := signedType
t.Run(signedType.String(), func(t *testing.T) {
for _, values := range [][2]uint64{
{0, 0},
{1, 1},
{2, 1},
{100, 1},
{1, 0},
{0, 1},
{math.MaxInt16, math.MaxInt32},
{1234, 5},
{5, 1234},
{4, 2},
{40, 4},
{123456, 4},
{1 << 14, 1 << 21},
{1 << 14, 1 << 21},
{0xffff_ffff_ffff_ffff, 0},
{0xffff_ffff_ffff_ffff, 1},
{0, 0xffff_ffff_ffff_ffff},
{1, 0xffff_ffff_ffff_ffff},
{0x80000000, 0xffffffff}, // This is equivalent to (-2^31 / -1) and results in overflow for 32-bit signed div.
{0x8000000000000000, 0xffffffffffffffff}, // This is equivalent to (-2^63 / -1) and results in overflow for 64-bit signed div.
{0xffffffff /* -1 in signed 32bit */, 0xfffffffe /* -2 in signed 32bit */},
{0xffffffffffffffff /* -1 in signed 64bit */, 0xfffffffffffffffe /* -2 in signed 64bit */},
{1, 0xffff_ffff_ffff_ffff},
{math.Float64bits(1.11231), math.Float64bits(12312312.12312)},
{math.Float64bits(1.11231), math.Float64bits(-12312312.12312)},
{math.Float64bits(-1.11231), math.Float64bits(12312312.12312)},
{math.Float64bits(-1.11231), math.Float64bits(-12312312.12312)},
{math.Float64bits(1.11231), math.Float64bits(12312312.12312)},
{math.Float64bits(-12312312.12312), math.Float64bits(1.11231)},
{math.Float64bits(12312312.12312), math.Float64bits(-1.11231)},
{math.Float64bits(-12312312.12312), math.Float64bits(-1.11231)},
{1, math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), 1},
{0xffff_ffff_ffff_ffff, math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), 0xffff_ffff_ffff_ffff},
{math.Float64bits(math.MaxFloat32), 1},
{math.Float64bits(math.SmallestNonzeroFloat32), 1},
{math.Float64bits(math.MaxFloat64), 1},
{math.Float64bits(math.SmallestNonzeroFloat64), 1},
{0, math.Float64bits(math.Inf(1))},
{0, math.Float64bits(math.Inf(-1))},
{math.Float64bits(math.Inf(1)), 0},
{math.Float64bits(math.Inf(-1)), 0},
{math.Float64bits(math.Inf(1)), 1},
{math.Float64bits(math.Inf(-1)), 1},
{math.Float64bits(1.11231), math.Float64bits(math.Inf(1))},
{math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))},
{math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)},
{math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)},
{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))},
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))},
} {
x1, x2 := values[0], values[1]
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Emit consts operands.
for _, v := range []uint64{x1, x2} {
switch signedType {
case wazeroir.SignedTypeUint32:
// In order to test zero value on non-zero register, we directly assign an register.
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
err = compiler.compileEnsureOnRegister(loc)
require.NoError(t, err)
env.stack()[loc.stackPointer] = uint64(v)
case wazeroir.SignedTypeInt32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))})
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
case wazeroir.SignedTypeFloat32:
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
case wazeroir.SignedTypeFloat64:
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
}
require.NoError(t, err)
}
// At this point, two values exist for comparison.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
switch kind {
case wazeroir.OperationKindDiv:
err = compiler.compileDiv(&wazeroir.OperationDiv{Type: signedType})
case wazeroir.OperationKindRem:
switch signedType {
case wazeroir.SignedTypeInt32:
err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedInt32})
case wazeroir.SignedTypeInt64:
err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedInt64})
case wazeroir.SignedTypeUint32:
err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedUint32})
case wazeroir.SignedTypeUint64:
err = compiler.compileRem(&wazeroir.OperationRem{Type: wazeroir.SignedUint64})
case wazeroir.SignedTypeFloat32:
// Rem undefined for float32.
return
case wazeroir.SignedTypeFloat64:
// Rem undefined for float64.
return
}
}
require.NoError(t, err)
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Compile and execute the code under test.
code, _, err := compiler.compile()
require.NoError(t, err)
env.exec(code)
switch kind {
case wazeroir.OperationKindDiv:
switch signedType {
case wazeroir.SignedTypeUint32:
if uint32(x2) == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, uint32(x1)/uint32(x2), env.stackTopAsUint32())
}
case wazeroir.SignedTypeInt32:
v1, v2 := int32(x1), int32(x2)
if v2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else if v1 == math.MinInt32 && v2 == -1 {
require.Equal(t, nativeCallStatusIntegerOverflow, env.compilerStatus())
} else {
require.Equal(t, v1/v2, env.stackTopAsInt32())
}
case wazeroir.SignedTypeUint64:
if x2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, x1/x2, env.stackTopAsUint64())
}
case wazeroir.SignedTypeInt64:
v1, v2 := int64(x1), int64(x2)
if v2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else if v1 == math.MinInt64 && v2 == -1 {
require.Equal(t, nativeCallStatusIntegerOverflow, env.compilerStatus())
} else {
require.Equal(t, v1/v2, env.stackTopAsInt64())
}
case wazeroir.SignedTypeFloat32:
exp := math.Float32frombits(uint32(x1)) / math.Float32frombits(uint32(x2))
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(float64(exp)) {
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
} else {
require.Equal(t, exp, env.stackTopAsFloat32())
}
case wazeroir.SignedTypeFloat64:
exp := math.Float64frombits(x1) / math.Float64frombits(x2)
// NaN cannot be compared with themselves, so we have to use IsNaN
if math.IsNaN(exp) {
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
} else {
require.Equal(t, exp, env.stackTopAsFloat64())
}
}
case wazeroir.OperationKindRem:
switch signedType {
case wazeroir.SignedTypeInt32:
v1, v2 := int32(x1), int32(x2)
if v2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, v1%v2, env.stackTopAsInt32())
}
case wazeroir.SignedTypeInt64:
v1, v2 := int64(x1), int64(x2)
if v2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, v1%v2, env.stackTopAsInt64())
}
case wazeroir.SignedTypeUint32:
v1, v2 := uint32(x1), uint32(x2)
if v2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, v1%v2, env.stackTopAsUint32())
}
case wazeroir.SignedTypeUint64:
if x2 == 0 {
require.Equal(t, nativeCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, x1%x2, env.stackTopAsUint64())
}
}
}
})
}
})
}
})
}
}