Files
wazero/internal/engine/compiler/compiler_numeric_test.go
Takeshi Yoneda cc28399052 wazeroir: reuses allocated slices for a module (#1342)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2023-04-05 20:26:44 +09:00

1584 lines
59 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/wasm"
"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, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
switch op {
case wazeroir.OperationKindConstI32:
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(val))))
case wazeroir.OperationKindConstI64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(val)))
case wazeroir.OperationKindConstF32:
err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(val)))))
case wazeroir.OperationKindConstF64:
err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(math.Float64frombits(val))))
case wazeroir.OperationKindV128Const:
err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(val, ^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, &wasm.FunctionType{}, 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(operationPtr(wazeroir.NewOperationConstI32(uint32(v))))
case wazeroir.UnsignedTypeI64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
case wazeroir.UnsignedTypeF32:
err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v)))))
case wazeroir.UnsignedTypeF64:
err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(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(operationPtr(wazeroir.NewOperationAdd(unsignedType)))
case wazeroir.OperationKindSub:
err = compiler.compileSub(operationPtr(wazeroir.NewOperationSub(unsignedType)))
case wazeroir.OperationKindMul:
err = compiler.compileMul(operationPtr(wazeroir.NewOperationMul(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, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Emit consts operands.
var x1Location *runtimeValueLocation
switch unsignedInt {
case wazeroir.UnsignedInt32:
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(x1))))
require.NoError(t, err)
x1Location = compiler.runtimeValueLocationStack().peek()
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(x2)))
require.NoError(t, err)
case wazeroir.UnsignedInt64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(x1)))
require.NoError(t, err)
x1Location = compiler.runtimeValueLocationStack().peek()
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(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(operationPtr(wazeroir.NewOperationAnd(unsignedInt)))
case wazeroir.OperationKindOr:
err = compiler.compileOr(operationPtr(wazeroir.NewOperationOr(unsignedInt)))
case wazeroir.OperationKindXor:
err = compiler.compileXor(operationPtr(wazeroir.NewOperationXor(unsignedInt)))
case wazeroir.OperationKindShl:
err = compiler.compileShl(operationPtr(wazeroir.NewOperationShl(unsignedInt)))
case wazeroir.OperationKindRotl:
err = compiler.compileRotl(operationPtr(wazeroir.NewOperationRotl(unsignedInt)))
case wazeroir.OperationKindRotr:
err = compiler.compileRotr(operationPtr(wazeroir.NewOperationRotr(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, &wasm.FunctionType{}, 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(operationPtr(wazeroir.NewOperationConstI32(uint32(int32(v)))))
case wazeroir.SignedInt64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
case wazeroir.SignedUint32:
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(v))))
case wazeroir.SignedUint64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
}
require.NoError(t, err)
}
// At this point, two values exist.
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
// Emit the operation.
err = compiler.compileShr(operationPtr(wazeroir.NewOperationShr(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, &wasm.FunctionType{}, 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(operationPtr(wazeroir.NewOperationConstI32(uint32(v))))
case wazeroir.SignedTypeInt32:
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(int32(v)))))
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
case wazeroir.SignedTypeFloat32:
err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v)))))
case wazeroir.SignedTypeFloat64:
err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(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(operationPtr(wazeroir.NewOperationLe(signedType)))
case wazeroir.OperationKindLt:
err = compiler.compileLt(operationPtr(wazeroir.NewOperationLt(signedType)))
case wazeroir.OperationKindGe:
err = compiler.compileGe(operationPtr(wazeroir.NewOperationGe(signedType)))
case wazeroir.OperationKindGt:
err = compiler.compileGt(operationPtr(wazeroir.NewOperationGt(signedType)))
case wazeroir.OperationKindEq:
// Eq uses UnsignedType instead, so we translate the signed one.
switch signedType {
case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
err = compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI32)))
case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
err = compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeI64)))
case wazeroir.SignedTypeFloat32:
err = compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeF32)))
case wazeroir.SignedTypeFloat64:
err = compiler.compileEq(operationPtr(wazeroir.NewOperationEq(wazeroir.UnsignedTypeF64)))
}
case wazeroir.OperationKindNe:
// Ne uses UnsignedType, so we translate the signed one.
switch signedType {
case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
err = compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI32)))
case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
err = compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeI64)))
case wazeroir.SignedTypeFloat32:
err = compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeF32)))
case wazeroir.SignedTypeFloat64:
err = compiler.compileNe(operationPtr(wazeroir.NewOperationNe(wazeroir.UnsignedTypeF64)))
}
case wazeroir.OperationKindEqz:
// Eqz uses UnsignedInt, so we translate the signed one.
switch signedType {
case wazeroir.SignedTypeUint32, wazeroir.SignedTypeInt32:
err = compiler.compileEqz(operationPtr(wazeroir.NewOperationEqz(wazeroir.UnsignedInt32)))
case wazeroir.SignedTypeUint64, wazeroir.SignedTypeInt64:
err = compiler.compileEqz(operationPtr(wazeroir.NewOperationEqz(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, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
if is32bit {
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(v))))
} else {
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
}
require.NoError(t, err)
switch kind {
case wazeroir.OperationKindClz:
err = compiler.compileClz(operationPtr(wazeroir.NewOperationClz(tp)))
case wazeroir.OperationKindCtz:
err = compiler.compileCtz(operationPtr(wazeroir.NewOperationCtz(tp)))
case wazeroir.OperationKindPopcnt:
err = compiler.compilePopcnt(operationPtr(wazeroir.NewOperationPopcnt(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(operationPtr(wazeroir.NewOperationMin(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(operationPtr(wazeroir.NewOperationMin(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(operationPtr(wazeroir.NewOperationMax(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(operationPtr(wazeroir.NewOperationMax(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(operationPtr(wazeroir.NewOperationCopysign(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(operationPtr(wazeroir.NewOperationCopysign(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, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
// Setup the target values.
if tc.is32bit {
err := compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(float32(x1))))
require.NoError(t, err)
err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(float32(x2))))
require.NoError(t, err)
} else {
err := compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(x1)))
require.NoError(t, err)
err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(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.list()))
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.list()))
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(operationPtr(wazeroir.NewOperationAbs(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(operationPtr(wazeroir.NewOperationAbs(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(operationPtr(wazeroir.NewOperationNeg(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(operationPtr(wazeroir.NewOperationNeg(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(operationPtr(wazeroir.NewOperationCeil(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(operationPtr(wazeroir.NewOperationCeil(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(operationPtr(wazeroir.NewOperationFloor(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(operationPtr(wazeroir.NewOperationFloor(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(operationPtr(wazeroir.NewOperationTrunc(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(operationPtr(wazeroir.NewOperationTrunc(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(operationPtr(wazeroir.NewOperationNearest(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(operationPtr(wazeroir.NewOperationNearest(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(operationPtr(wazeroir.NewOperationSqrt(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(operationPtr(wazeroir.NewOperationSqrt(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, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
if tc.is32bit {
err := compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(float32(v))))
require.NoError(t, err)
} else {
err := compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(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.list()))
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.list()))
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, &wasm.FunctionType{}, 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()
loc.valueType = runtimeValueTypeI32
err = compiler.compileEnsureOnRegister(loc)
require.NoError(t, err)
env.stack()[loc.stackPointer] = uint64(v)
case wazeroir.SignedTypeInt32:
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(uint32(int32(v)))))
case wazeroir.SignedTypeInt64, wazeroir.SignedTypeUint64:
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(v)))
case wazeroir.SignedTypeFloat32:
err = compiler.compileConstF32(operationPtr(wazeroir.NewOperationConstF32(math.Float32frombits(uint32(v)))))
case wazeroir.SignedTypeFloat64:
err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(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(operationPtr(wazeroir.NewOperationDiv(signedType)))
case wazeroir.OperationKindRem:
switch signedType {
case wazeroir.SignedTypeInt32:
err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedInt32)))
case wazeroir.SignedTypeInt64:
err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedInt64)))
case wazeroir.SignedTypeUint32:
err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedUint32)))
case wazeroir.SignedTypeUint64:
err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(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())
}
}
}
})
}
})
}
})
}
}