Files
wazero/internal/engine/compiler/compiler_numeric_test.go
Crypt Keeper c815060196 Renames JIT to Compiler and notes it is AOT (#564)
This notably changes NewRuntimeJIT to NewRuntimeCompiler as well renames
packages from jit to compiler.

This clarifies the implementation is AOT, not JIT, at least when
clarified to where it occurs (Runtime.CompileModule). In doing so, we
reduce any concern that compilation will happen during function
execution. We also free ourselves to create a JIT option without
confusion in the future via CompileConfig or otherwise.

Fixes #560

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-05-17 08:50:56 +09:00

1496 lines
57 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,
} {
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)})
}
require.NoError(t, err)
// After compiling const operations, we must see the register allocated value on the top of value.
loc := compiler.valueLocationStack().peek()
require.True(t, loc.onRegister())
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, compilerCallStatusCodeReturned, env.compilerStatus())
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())
}
})
}
})
}
}
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.
require.Equal(t, uint64(2), compiler.valueLocationStack().sp)
// 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.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
resultLocation := compiler.valueLocationStack().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, generalPurposeRegisterTypeFloat, resultLocation.regType)
} else {
require.Equal(t, generalPurposeRegisterTypeInt, resultLocation.regType)
}
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 *valueLocation
switch unsignedInt {
case wazeroir.UnsignedInt32:
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(x1)})
require.NoError(t, err)
x1Location = compiler.valueLocationStack().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.valueLocationStack().peek()
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2})
require.NoError(t, err)
}
if !x1OnRegister {
compiler.compileReleaseRegisterToStack(x1Location)
}
// At this point, two values exist.
require.Equal(t, uint64(2), compiler.valueLocationStack().sp)
// 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.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
resultLocation := compiler.valueLocationStack().peek()
// Also, the result must have an appropriate register type.
require.Equal(t, generalPurposeRegisterTypeInt, resultLocation.regType)
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.
require.Equal(t, uint64(2), compiler.valueLocationStack().sp)
// Emit the operation.
err = compiler.compileShr(&wazeroir.OperationShr{Type: signedInt})
require.NoError(t, err)
// We consumed two values, but push the result back.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
resultLocation := compiler.valueLocationStack().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, generalPurposeRegisterTypeInt, resultLocation.regType)
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.
t.Skip()
}
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.valueLocationStack().pop()
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
} else {
// At this point, two values exist for comparison.
require.Equal(t, uint64(2), compiler.valueLocationStack().sp)
}
// 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.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
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) {
for _, tc := range []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 := float32(moremath.WasmCompatMin(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: "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.WasmCompatMin(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)
}
},
},
{
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 := float32(moremath.WasmCompatMax(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: "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.WasmCompatMax(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)
}
},
},
{
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)
}
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
for _, vs := range [][2]float64{
{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.
require.Equal(t, uint64(2), compiler.valueLocationStack().sp)
require.Equal(t, 2, len(compiler.valueLocationStack().usedRegisters))
tc.setupFunc(t, compiler)
// We consumed two values, but push one value after operation.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
require.Equal(t, 1, len(compiler.valueLocationStack().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, compilerCallStatusCodeReturned, 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) {
for _, tc := range []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)
}
},
},
} {
tc := tc
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.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
require.Equal(t, 1, len(compiler.valueLocationStack().usedRegisters))
tc.setupFunc(t, compiler)
// We consumed one value, but push the result after operation.
require.Equal(t, uint64(1), compiler.valueLocationStack().sp)
require.Equal(t, 1, len(compiler.valueLocationStack().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, compilerCallStatusCodeReturned, 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.valueLocationStack().pushValueLocationOnStack()
err = compiler.compileEnsureOnGeneralPurposeRegister(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.
require.Equal(t, uint64(2), compiler.valueLocationStack().sp)
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.
t.Skip()
case wazeroir.SignedTypeFloat64:
// Rem undefined for float64.
t.Skip()
}
}
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, compilerCallStatusIntegerDivisionByZero, 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, compilerCallStatusIntegerDivisionByZero, env.compilerStatus())
} else if v1 == math.MinInt32 && v2 == -1 {
require.Equal(t, compilerCallStatusIntegerOverflow, env.compilerStatus())
} else {
require.Equal(t, v1/v2, env.stackTopAsInt32())
}
case wazeroir.SignedTypeUint64:
if x2 == 0 {
require.Equal(t, compilerCallStatusIntegerDivisionByZero, 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, compilerCallStatusIntegerDivisionByZero, env.compilerStatus())
} else if v1 == math.MinInt64 && v2 == -1 {
require.Equal(t, compilerCallStatusIntegerOverflow, 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, compilerCallStatusIntegerDivisionByZero, 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, compilerCallStatusIntegerDivisionByZero, 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, compilerCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, v1%v2, env.stackTopAsUint32())
}
case wazeroir.SignedTypeUint64:
if x2 == 0 {
require.Equal(t, compilerCallStatusIntegerDivisionByZero, env.compilerStatus())
} else {
require.Equal(t, x1%x2, env.stackTopAsUint64())
}
}
}
})
}
})
}
})
}
}