package compiler import ( "fmt" "math" "math/bits" "testing" "github.com/tetratelabs/wazero/internal/asm" "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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) // Run native code. env.exec(code.Bytes()) // 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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Compile and execute the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) // 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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Compile and execute the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) // 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.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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Compile and execute the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) // 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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate and run the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) // 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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate and run the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) 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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Generate and run the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) 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) code := asm.CodeSegment{} defer func() { require.NoError(t, code.Unmap()) }() // Compile and execute the code under test. _, err = compiler.compile(code.NextCodeSection()) require.NoError(t, err) env.exec(code.Bytes()) 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()) } } } }) } }) } }) } }