1584 lines
59 KiB
Go
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())
|
|
}
|
|
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|