This implements various SIMD instructions related to load, store, and lane manipulations for all engines. Notablely, now our engines pass the following specification tests: * simd_address.wast * simd_const.wast * simd_align.wast * simd_laod16_lane.wast * simd_laod32_lane.wast * simd_laod64_lane.wast * simd_laod8_lane.wast * simd_lane.wast * simd_load_extend.wast * simd_load_splat.wast * simd_load_zero.wast * simd_store.wast * simd_store16_lane.wast * simd_store32_lane.wast * simd_store64_lane.wast * simd_store8_lane.wast part of #484 Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io> Co-authored-by: Adrian Cole <adrian@tetrate.io>
1515 lines
57 KiB
Go
1515 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,
|
|
wazeroir.OperationKindV128Const,
|
|
} {
|
|
op := op
|
|
t.Run(op.String(), func(t *testing.T) {
|
|
for _, val := range []uint64{
|
|
0x0, 0x1, 0x1111000, 1 << 16, 1 << 21, 1 << 27, 1 << 32, 1<<32 + 1, 1 << 53,
|
|
math.Float64bits(math.Inf(1)),
|
|
math.Float64bits(math.Inf(-1)),
|
|
math.Float64bits(math.NaN()),
|
|
math.MaxUint32,
|
|
math.MaxInt32,
|
|
math.MaxUint64,
|
|
math.MaxInt64,
|
|
uint64(math.Float32bits(float32(math.Inf(1)))),
|
|
uint64(math.Float32bits(float32(math.Inf(-1)))),
|
|
uint64(math.Float32bits(float32(math.NaN()))),
|
|
} {
|
|
t.Run(fmt.Sprintf("0x%x", val), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
|
|
// Compile code.
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
switch op {
|
|
case wazeroir.OperationKindConstI32:
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(val)})
|
|
case wazeroir.OperationKindConstI64:
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: val})
|
|
case wazeroir.OperationKindConstF32:
|
|
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(val))})
|
|
case wazeroir.OperationKindConstF64:
|
|
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(val)})
|
|
case wazeroir.OperationKindV128Const:
|
|
err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: val, Hi: ^val})
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// After compiling const operations, we must see the register allocated value on the top of value.
|
|
loc := compiler.runtimeValueLocationStack().peek()
|
|
require.True(t, loc.onRegister())
|
|
|
|
if op == wazeroir.OperationKindV128Const {
|
|
require.Equal(t, runtimeValueTypeV128Hi, loc.valueType)
|
|
}
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Generate the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
// Run native code.
|
|
env.exec(code)
|
|
|
|
// Compiler status must be returned.
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
if op == wazeroir.OperationKindV128Const {
|
|
require.Equal(t, uint64(2), env.stackPointer()) // a vector value consists of two uint64.
|
|
} else {
|
|
require.Equal(t, uint64(1), env.stackPointer())
|
|
}
|
|
|
|
switch op {
|
|
case wazeroir.OperationKindConstI32, wazeroir.OperationKindConstF32:
|
|
require.Equal(t, uint32(val), env.stackTopAsUint32())
|
|
case wazeroir.OperationKindConstI64, wazeroir.OperationKindConstF64:
|
|
require.Equal(t, val, env.stackTopAsUint64())
|
|
case wazeroir.OperationKindV128Const:
|
|
lo, hi := env.stackTopAsV128()
|
|
require.Equal(t, val, lo)
|
|
require.Equal(t, ^val, hi)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compile_Add_Sub_Mul(t *testing.T) {
|
|
for _, kind := range []wazeroir.OperationKind{
|
|
wazeroir.OperationKindAdd,
|
|
wazeroir.OperationKindSub,
|
|
wazeroir.OperationKindMul,
|
|
} {
|
|
kind := kind
|
|
t.Run(kind.String(), func(t *testing.T) {
|
|
for _, unsignedType := range []wazeroir.UnsignedType{
|
|
wazeroir.UnsignedTypeI32,
|
|
wazeroir.UnsignedTypeI64,
|
|
wazeroir.UnsignedTypeF32,
|
|
wazeroir.UnsignedTypeF64,
|
|
} {
|
|
unsignedType := unsignedType
|
|
t.Run(unsignedType.String(), func(t *testing.T) {
|
|
for _, values := range [][2]uint64{
|
|
{0, 0}, {1, 1}, {2, 1}, {100, 1}, {1, 0}, {0, 1}, {math.MaxInt16, math.MaxInt32},
|
|
{1 << 14, 1 << 21}, {1 << 14, 1 << 21},
|
|
{0xffff_ffff_ffff_ffff, 0}, {0xffff_ffff_ffff_ffff, 1},
|
|
{0, 0xffff_ffff_ffff_ffff}, {1, 0xffff_ffff_ffff_ffff},
|
|
{0, math.Float64bits(math.Inf(1))},
|
|
{0, math.Float64bits(math.Inf(-1))},
|
|
{math.Float64bits(math.Inf(1)), 1},
|
|
{math.Float64bits(math.Inf(-1)), 1},
|
|
{math.Float64bits(1.11231), math.Float64bits(math.Inf(1))},
|
|
{math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))},
|
|
{math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)},
|
|
{math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)},
|
|
{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
|
|
{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
|
|
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))},
|
|
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))},
|
|
} {
|
|
x1, x2 := values[0], values[1]
|
|
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Emit consts operands.
|
|
for _, v := range []uint64{x1, x2} {
|
|
switch unsignedType {
|
|
case wazeroir.UnsignedTypeI32:
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
|
|
case wazeroir.UnsignedTypeI64:
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
|
|
case wazeroir.UnsignedTypeF32:
|
|
err = compiler.compileConstF32(&wazeroir.OperationConstF32{Value: math.Float32frombits(uint32(v))})
|
|
case wazeroir.UnsignedTypeF64:
|
|
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(v)})
|
|
}
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// At this point, two values exist.
|
|
require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().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.runtimeValueLocationStack().sp)
|
|
resultLocation := compiler.runtimeValueLocationStack().peek()
|
|
// Plus the result must be located on a register.
|
|
require.True(t, resultLocation.onRegister())
|
|
// Also, the result must have an appropriate register type.
|
|
if unsignedType == wazeroir.UnsignedTypeF32 || unsignedType == wazeroir.UnsignedTypeF64 {
|
|
require.Equal(t, registerTypeVector, resultLocation.getRegisterType())
|
|
} else {
|
|
require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType())
|
|
}
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Compile and execute the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
// Check the stack.
|
|
require.Equal(t, uint64(1), env.stackPointer())
|
|
|
|
switch kind {
|
|
case wazeroir.OperationKindAdd:
|
|
switch unsignedType {
|
|
case wazeroir.UnsignedTypeI32:
|
|
require.Equal(t, uint32(x1)+uint32(x2), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedTypeI64:
|
|
require.Equal(t, x1+x2, env.stackTopAsUint64())
|
|
case wazeroir.UnsignedTypeF32:
|
|
exp := math.Float32frombits(uint32(x1)) + math.Float32frombits(uint32(x2))
|
|
// NaN cannot be compared with themselves, so we have to use IsNaN
|
|
if math.IsNaN(float64(exp)) {
|
|
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
|
|
} else {
|
|
require.Equal(t, exp, env.stackTopAsFloat32())
|
|
}
|
|
case wazeroir.UnsignedTypeF64:
|
|
exp := math.Float64frombits(x1) + math.Float64frombits(x2)
|
|
// NaN cannot be compared with themselves, so we have to use IsNaN
|
|
if math.IsNaN(exp) {
|
|
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
|
|
} else {
|
|
require.Equal(t, exp, env.stackTopAsFloat64())
|
|
}
|
|
}
|
|
case wazeroir.OperationKindSub:
|
|
switch unsignedType {
|
|
case wazeroir.UnsignedTypeI32:
|
|
require.Equal(t, uint32(x1)-uint32(x2), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedTypeI64:
|
|
require.Equal(t, x1-x2, env.stackTopAsUint64())
|
|
case wazeroir.UnsignedTypeF32:
|
|
exp := math.Float32frombits(uint32(x1)) - math.Float32frombits(uint32(x2))
|
|
// NaN cannot be compared with themselves, so we have to use IsNaN
|
|
if math.IsNaN(float64(exp)) {
|
|
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
|
|
} else {
|
|
require.Equal(t, exp, env.stackTopAsFloat32())
|
|
}
|
|
case wazeroir.UnsignedTypeF64:
|
|
exp := math.Float64frombits(x1) - math.Float64frombits(x2)
|
|
// NaN cannot be compared with themselves, so we have to use IsNaN
|
|
if math.IsNaN(exp) {
|
|
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
|
|
} else {
|
|
require.Equal(t, exp, env.stackTopAsFloat64())
|
|
}
|
|
}
|
|
case wazeroir.OperationKindMul:
|
|
switch unsignedType {
|
|
case wazeroir.UnsignedTypeI32:
|
|
require.Equal(t, uint32(x1)*uint32(x2), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedTypeI64:
|
|
require.Equal(t, x1*x2, env.stackTopAsUint64())
|
|
case wazeroir.UnsignedTypeF32:
|
|
exp := math.Float32frombits(uint32(x1)) * math.Float32frombits(uint32(x2))
|
|
// NaN cannot be compared with themselves, so we have to use IsNaN
|
|
if math.IsNaN(float64(exp)) {
|
|
require.True(t, math.IsNaN(float64(env.stackTopAsFloat32())))
|
|
} else {
|
|
require.Equal(t, exp, env.stackTopAsFloat32())
|
|
}
|
|
case wazeroir.UnsignedTypeF64:
|
|
exp := math.Float64frombits(x1) * math.Float64frombits(x2)
|
|
// NaN cannot be compared with themselves, so we have to use IsNaN
|
|
if math.IsNaN(exp) {
|
|
require.True(t, math.IsNaN(env.stackTopAsFloat64()))
|
|
} else {
|
|
require.Equal(t, exp, env.stackTopAsFloat64())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compile_And_Or_Xor_Shl_Rotl_Rotr(t *testing.T) {
|
|
for _, kind := range []wazeroir.OperationKind{
|
|
wazeroir.OperationKindAnd,
|
|
wazeroir.OperationKindOr,
|
|
wazeroir.OperationKindXor,
|
|
wazeroir.OperationKindShl,
|
|
wazeroir.OperationKindRotl,
|
|
wazeroir.OperationKindRotr,
|
|
} {
|
|
kind := kind
|
|
t.Run(kind.String(), func(t *testing.T) {
|
|
for _, unsignedInt := range []wazeroir.UnsignedInt{
|
|
wazeroir.UnsignedInt32,
|
|
wazeroir.UnsignedInt64,
|
|
} {
|
|
unsignedInt := unsignedInt
|
|
t.Run(unsignedInt.String(), func(t *testing.T) {
|
|
for _, values := range [][2]uint64{
|
|
{0, 0}, {0, 1}, {1, 0}, {1, 1},
|
|
{1 << 31, 1}, {1, 1 << 31}, {1 << 31, 1 << 31},
|
|
{1 << 63, 1}, {1, 1 << 63}, {1 << 63, 1 << 63},
|
|
} {
|
|
x1, x2 := values[0], values[1]
|
|
for _, x1OnRegister := range []bool{
|
|
true, false,
|
|
} {
|
|
x1OnRegister := x1OnRegister
|
|
t.Run(fmt.Sprintf("x1=0x%x(on_register=%v),x2=0x%x", x1, x1OnRegister, x2), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Emit consts operands.
|
|
var x1Location *runtimeValueLocation
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(x1)})
|
|
require.NoError(t, err)
|
|
x1Location = compiler.runtimeValueLocationStack().peek()
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2})
|
|
require.NoError(t, err)
|
|
case wazeroir.UnsignedInt64:
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x1})
|
|
require.NoError(t, err)
|
|
x1Location = compiler.runtimeValueLocationStack().peek()
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: x2})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if !x1OnRegister {
|
|
compiler.compileReleaseRegisterToStack(x1Location)
|
|
}
|
|
|
|
// At this point, two values exist.
|
|
require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().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.runtimeValueLocationStack().sp)
|
|
resultLocation := compiler.runtimeValueLocationStack().peek()
|
|
// Also, the result must have an appropriate register type.
|
|
require.Equal(t, registerTypeGeneralPurpose, resultLocation.getRegisterType())
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Compile and execute the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
// Check the stack.
|
|
require.Equal(t, uint64(1), env.stackPointer())
|
|
|
|
switch kind {
|
|
case wazeroir.OperationKindAnd:
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
require.Equal(t, uint32(x1)&uint32(x2), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedInt64:
|
|
require.Equal(t, x1&x2, env.stackTopAsUint64())
|
|
}
|
|
case wazeroir.OperationKindOr:
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
require.Equal(t, uint32(x1)|uint32(x2), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedInt64:
|
|
require.Equal(t, x1|x2, env.stackTopAsUint64())
|
|
}
|
|
case wazeroir.OperationKindXor:
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
require.Equal(t, uint32(x1)^uint32(x2), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedInt64:
|
|
require.Equal(t, x1^x2, env.stackTopAsUint64())
|
|
}
|
|
case wazeroir.OperationKindShl:
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
require.Equal(t, uint32(x1)<<uint32(x2%32), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedInt64:
|
|
require.Equal(t, x1<<(x2%64), env.stackTopAsUint64())
|
|
}
|
|
case wazeroir.OperationKindRotl:
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
require.Equal(t, bits.RotateLeft32(uint32(x1), int(x2)), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedInt64:
|
|
require.Equal(t, bits.RotateLeft64(x1, int(x2)), env.stackTopAsUint64())
|
|
}
|
|
case wazeroir.OperationKindRotr:
|
|
switch unsignedInt {
|
|
case wazeroir.UnsignedInt32:
|
|
require.Equal(t, bits.RotateLeft32(uint32(x1), -int(x2)), env.stackTopAsUint32())
|
|
case wazeroir.UnsignedInt64:
|
|
require.Equal(t, bits.RotateLeft64(x1, -int(x2)), env.stackTopAsUint64())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compileShr(t *testing.T) {
|
|
kind := wazeroir.OperationKindShr
|
|
t.Run(kind.String(), func(t *testing.T) {
|
|
for _, signedInt := range []wazeroir.SignedInt{
|
|
wazeroir.SignedInt32,
|
|
wazeroir.SignedInt64,
|
|
wazeroir.SignedUint32,
|
|
wazeroir.SignedUint64,
|
|
} {
|
|
signedInt := signedInt
|
|
t.Run(signedInt.String(), func(t *testing.T) {
|
|
for _, values := range [][2]uint64{
|
|
{0, 0}, {0, 1}, {1, 0}, {1, 1},
|
|
{1 << 31, 1}, {1, 1 << 31}, {1 << 31, 1 << 31},
|
|
{1 << 63, 1}, {1, 1 << 63}, {1 << 63, 1 << 63},
|
|
} {
|
|
x1, x2 := values[0], values[1]
|
|
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Emit consts operands.
|
|
for _, v := range []uint64{x1, x2} {
|
|
switch signedInt {
|
|
case wazeroir.SignedInt32:
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(int32(v))})
|
|
case wazeroir.SignedInt64:
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
|
|
case wazeroir.SignedUint32:
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(v)})
|
|
case wazeroir.SignedUint64:
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: v})
|
|
}
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// At this point, two values exist.
|
|
require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().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.runtimeValueLocationStack().sp)
|
|
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.
|
|
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.runtimeValueLocationStack().pop()
|
|
require.Equal(t, uint64(1), compiler.runtimeValueLocationStack().sp)
|
|
} else {
|
|
// At this point, two values exist for comparison.
|
|
require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().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.runtimeValueLocationStack().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) {
|
|
tests := []struct {
|
|
name string
|
|
is32bit bool
|
|
setupFunc func(t *testing.T, compiler compilerImpl)
|
|
verifyFunc func(t *testing.T, x1, x2 float64, raw uint64)
|
|
}{
|
|
{
|
|
name: "min-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileMin(&wazeroir.OperationMin{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, x1, x2 float64, raw uint64) {
|
|
exp := 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)
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
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.runtimeValueLocationStack().sp)
|
|
require.Equal(t, 2, len(compiler.runtimeValueLocationStack().usedRegisters))
|
|
|
|
tc.setupFunc(t, compiler)
|
|
|
|
// We consumed two values, but push one value after operation.
|
|
require.Equal(t, uint64(1), compiler.runtimeValueLocationStack().sp)
|
|
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Generate and run the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(1), env.stackPointer()) // Result must be pushed!
|
|
|
|
tc.verifyFunc(t, x1, x2, env.stackTopAsUint64())
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compile_Abs_Neg_Ceil_Floor_Trunc_Nearest_Sqrt(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
is32bit bool
|
|
setupFunc func(t *testing.T, compiler compilerImpl)
|
|
verifyFunc func(t *testing.T, v float64, raw uint64)
|
|
}{
|
|
{
|
|
name: "abs-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileAbs(&wazeroir.OperationAbs{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := float32(math.Abs(float64(v)))
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "abs-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileAbs(&wazeroir.OperationAbs{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := math.Abs(v)
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "neg-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileNeg(&wazeroir.OperationNeg{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := -float32(v)
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "neg-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileNeg(&wazeroir.OperationNeg{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := -v
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "ceil-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileCeil(&wazeroir.OperationCeil{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := float32(math.Ceil(float64(v)))
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "ceil-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileCeil(&wazeroir.OperationCeil{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := math.Ceil(v)
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "floor-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileFloor(&wazeroir.OperationFloor{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := float32(math.Floor(float64(v)))
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "floor-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileFloor(&wazeroir.OperationFloor{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := math.Floor(v)
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "trunc-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileTrunc(&wazeroir.OperationTrunc{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := float32(math.Trunc(float64(v)))
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "trunc-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileTrunc(&wazeroir.OperationTrunc{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := math.Trunc(v)
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "nearest-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileNearest(&wazeroir.OperationNearest{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := moremath.WasmCompatNearestF32(float32(v))
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "nearest-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileNearest(&wazeroir.OperationNearest{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := moremath.WasmCompatNearestF64(v)
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "sqrt-32-bit",
|
|
is32bit: true,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileSqrt(&wazeroir.OperationSqrt{Type: wazeroir.Float32})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := float32(math.Sqrt(float64(v)))
|
|
actual := math.Float32frombits(uint32(raw))
|
|
if math.IsNaN(float64(exp)) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(float64(actual)))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "sqrt-64-bit",
|
|
is32bit: false,
|
|
setupFunc: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileSqrt(&wazeroir.OperationSqrt{Type: wazeroir.Float64})
|
|
require.NoError(t, err)
|
|
},
|
|
verifyFunc: func(t *testing.T, v float64, raw uint64) {
|
|
exp := math.Sqrt(v)
|
|
actual := math.Float64frombits(raw)
|
|
if math.IsNaN(exp) { // NaN cannot be compared with themselves, so we have to use IsNaN
|
|
require.True(t, math.IsNaN(actual))
|
|
} else {
|
|
require.Equal(t, exp, actual)
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
for _, v := range []float64{
|
|
0, 1 << 63, 1<<63 | 12345, 1 << 31,
|
|
1<<31 | 123455, 6.8719476736e+10,
|
|
// This verifies that the impl is Wasm compatible in nearest, rather than being equivalent of math.Round.
|
|
// See moremath.WasmCompatNearestF32 and moremath.WasmCompatNearestF64
|
|
-4.5,
|
|
1.37438953472e+11, -1.3,
|
|
-1231.123, 1.3, 100.3, -100.3, 1231.123,
|
|
math.Inf(1), math.Inf(-1), math.NaN(),
|
|
} {
|
|
v := v
|
|
t.Run(fmt.Sprintf("%f", v), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
if tc.is32bit {
|
|
err := compiler.compileConstF32(&wazeroir.OperationConstF32{Value: float32(v)})
|
|
require.NoError(t, err)
|
|
} else {
|
|
err := compiler.compileConstF64(&wazeroir.OperationConstF64{Value: v})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// At this point two values are pushed.
|
|
require.Equal(t, uint64(1), compiler.runtimeValueLocationStack().sp)
|
|
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
|
|
|
|
tc.setupFunc(t, compiler)
|
|
|
|
// We consumed one value, but push the result after operation.
|
|
require.Equal(t, uint64(1), compiler.runtimeValueLocationStack().sp)
|
|
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Generate and run the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(1), env.stackPointer()) // Result must be pushed!
|
|
|
|
tc.verifyFunc(t, v, env.stackTopAsUint64())
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compile_Div_Rem(t *testing.T) {
|
|
for _, kind := range []wazeroir.OperationKind{
|
|
wazeroir.OperationKindDiv,
|
|
wazeroir.OperationKindRem,
|
|
} {
|
|
kind := kind
|
|
t.Run(kind.String(), func(t *testing.T) {
|
|
for _, signedType := range []wazeroir.SignedType{
|
|
wazeroir.SignedTypeUint32,
|
|
wazeroir.SignedTypeUint64,
|
|
wazeroir.SignedTypeInt32,
|
|
wazeroir.SignedTypeInt64,
|
|
wazeroir.SignedTypeFloat32,
|
|
wazeroir.SignedTypeFloat64,
|
|
} {
|
|
signedType := signedType
|
|
t.Run(signedType.String(), func(t *testing.T) {
|
|
for _, values := range [][2]uint64{
|
|
{0, 0}, {1, 1}, {2, 1}, {100, 1}, {1, 0}, {0, 1}, {math.MaxInt16, math.MaxInt32},
|
|
{1234, 5}, {5, 1234}, {4, 2}, {40, 4}, {123456, 4},
|
|
{1 << 14, 1 << 21}, {1 << 14, 1 << 21},
|
|
{0xffff_ffff_ffff_ffff, 0}, {0xffff_ffff_ffff_ffff, 1},
|
|
{0, 0xffff_ffff_ffff_ffff}, {1, 0xffff_ffff_ffff_ffff},
|
|
{0x80000000, 0xffffffff}, // This is equivalent to (-2^31 / -1) and results in overflow for 32-bit signed div.
|
|
{0x8000000000000000, 0xffffffffffffffff}, // This is equivalent to (-2^63 / -1) and results in overflow for 64-bit signed div.
|
|
{0xffffffff /* -1 in signed 32bit */, 0xfffffffe /* -2 in signed 32bit */},
|
|
{0xffffffffffffffff /* -1 in signed 64bit */, 0xfffffffffffffffe /* -2 in signed 64bit */},
|
|
{1, 0xffff_ffff_ffff_ffff},
|
|
{math.Float64bits(1.11231), math.Float64bits(12312312.12312)},
|
|
{math.Float64bits(1.11231), math.Float64bits(-12312312.12312)},
|
|
{math.Float64bits(-1.11231), math.Float64bits(12312312.12312)},
|
|
{math.Float64bits(-1.11231), math.Float64bits(-12312312.12312)},
|
|
{math.Float64bits(1.11231), math.Float64bits(12312312.12312)},
|
|
{math.Float64bits(-12312312.12312), math.Float64bits(1.11231)},
|
|
{math.Float64bits(12312312.12312), math.Float64bits(-1.11231)},
|
|
{math.Float64bits(-12312312.12312), math.Float64bits(-1.11231)},
|
|
{1, math.Float64bits(math.NaN())}, {math.Float64bits(math.NaN()), 1},
|
|
{0xffff_ffff_ffff_ffff, math.Float64bits(math.NaN())}, {math.Float64bits(math.NaN()), 0xffff_ffff_ffff_ffff},
|
|
{math.Float64bits(math.MaxFloat32), 1},
|
|
{math.Float64bits(math.SmallestNonzeroFloat32), 1},
|
|
{math.Float64bits(math.MaxFloat64), 1},
|
|
{math.Float64bits(math.SmallestNonzeroFloat64), 1},
|
|
{0, math.Float64bits(math.Inf(1))},
|
|
{0, math.Float64bits(math.Inf(-1))},
|
|
{math.Float64bits(math.Inf(1)), 0},
|
|
{math.Float64bits(math.Inf(-1)), 0},
|
|
{math.Float64bits(math.Inf(1)), 1},
|
|
{math.Float64bits(math.Inf(-1)), 1},
|
|
{math.Float64bits(1.11231), math.Float64bits(math.Inf(1))},
|
|
{math.Float64bits(1.11231), math.Float64bits(math.Inf(-1))},
|
|
{math.Float64bits(math.Inf(1)), math.Float64bits(1.11231)},
|
|
{math.Float64bits(math.Inf(-1)), math.Float64bits(1.11231)},
|
|
{math.Float64bits(math.Inf(1)), math.Float64bits(math.NaN())},
|
|
{math.Float64bits(math.Inf(-1)), math.Float64bits(math.NaN())},
|
|
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(1))},
|
|
{math.Float64bits(math.NaN()), math.Float64bits(math.Inf(-1))},
|
|
} {
|
|
x1, x2 := values[0], values[1]
|
|
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", x1, x2), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Emit consts operands.
|
|
for _, v := range []uint64{x1, x2} {
|
|
switch signedType {
|
|
case wazeroir.SignedTypeUint32:
|
|
// In order to test zero value on non-zero register, we directly assign an register.
|
|
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
err = compiler.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.runtimeValueLocationStack().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, 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())
|
|
}
|
|
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|