[wasm simd] Fix F32x4 Min and Max

- Fix F32x4 tests to save results in globals, so they can be checked
  in C++ code. Perform correct checks in case of NaNs.
- Fix ia32, x64 implementations of F32x4Min, F32x4Max to correctly
  deal with NaNs.
- Enable tests for all float values on all platforms, except skip
  denormalized results on ARM, and skip extreme values for reciprocal,
  reciprocal square root approximation opcodes.
- Disable Min, Max test for interpreter (see v8:8425) since it doesn't
  handle NaNs correctly.
- Fix vmin, vmax implementations in ARM simulator.

Bug: v8:8639
Change-Id: I87e188e3cb078f09fdacfd9955f426c20a11bf64
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1495897
Commit-Queue: Bill Budge <bbudge@chromium.org>
Reviewed-by: Deepti Gandluri <gdeepti@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60021}
This commit is contained in:
Bill Budge 2019-03-04 13:13:08 -08:00 committed by Commit Bot
parent a3ac513b5e
commit 821bc64951
5 changed files with 240 additions and 190 deletions

View File

@ -4180,6 +4180,11 @@ void CompareGreater(Simulator* simulator, int Vd, int Vm, int Vn, bool ge) {
simulator->set_neon_register<T, SIZE>(Vd, src1);
}
float MinMax(float a, float b, bool is_min) {
if (std::isnan(a) || std::isnan(b)) return NAN;
return is_min ? fmin(a, b) : fmax(a, b);
}
template <typename T>
T MinMax(T a, T b, bool is_min) {
return is_min ? std::min(a, b) : std::max(a, b);

View File

@ -1953,24 +1953,47 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
}
case kSSEF32x4Min: {
DCHECK_EQ(i.OutputSimd128Register(), i.InputSimd128Register(0));
__ minps(i.OutputSimd128Register(), i.InputOperand(1));
// minps doesn't propagate NaN lanes in the first source. Compare this
// with itself to generate 1's in those lanes (quiet NaNs) and or them
// with the result of minps to simulate NaN propagation.
__ movaps(kScratchDoubleReg, i.InputSimd128Register(0));
__ cmpps(kScratchDoubleReg, kScratchDoubleReg, 0x4);
__ minps(i.OutputSimd128Register(), i.InputSimd128Register(1));
__ orps(i.OutputSimd128Register(), kScratchDoubleReg);
break;
}
case kAVXF32x4Min: {
CpuFeatureScope avx_scope(tasm(), AVX);
// See comment above for minps and NaN propagation.
__ vcmpneqps(kScratchDoubleReg, i.InputSimd128Register(0),
i.InputSimd128Register(0)); // Is NaN?
__ vminps(i.OutputSimd128Register(), i.InputSimd128Register(0),
i.InputOperand(1));
__ vorps(i.OutputSimd128Register(), i.OutputSimd128Register(),
kScratchDoubleReg); // re-NaN-imate.
break;
}
case kSSEF32x4Max: {
DCHECK_EQ(i.OutputSimd128Register(), i.InputSimd128Register(0));
__ maxps(i.OutputSimd128Register(), i.InputOperand(1));
// maxps doesn't propagate NaN lanes in the first source. Compare this
// with itself to generate 1's in those lanes (quiet NaNs) and or them
// with the result of maxps to simulate NaN propagation.
__ movaps(kScratchDoubleReg, i.InputSimd128Register(0));
__ cmpps(kScratchDoubleReg, kScratchDoubleReg, 0x4);
__ maxps(i.OutputSimd128Register(), i.InputSimd128Register(1));
__ orps(i.OutputSimd128Register(), kScratchDoubleReg);
break;
}
case kAVXF32x4Max: {
CpuFeatureScope avx_scope(tasm(), AVX);
// See comment above for maxps and NaN propagation.
__ vcmpneqps(kScratchDoubleReg, i.InputSimd128Register(0),
i.InputSimd128Register(0));
__ vmaxps(i.OutputSimd128Register(), i.InputSimd128Register(0),
i.InputOperand(1));
__ vorps(i.OutputSimd128Register(), i.OutputSimd128Register(),
kScratchDoubleReg);
break;
}
case kSSEF32x4Eq: {

View File

@ -2302,12 +2302,24 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
}
case kX64F32x4Min: {
DCHECK_EQ(i.OutputSimd128Register(), i.InputSimd128Register(0));
// minps doesn't propagate NaN lanes in the first source. Compare this
// with itself to generate 1's in those lanes (quiet NaNs) and or them
// with the result of minps to simulate NaN propagation.
__ movaps(kScratchDoubleReg, i.InputSimd128Register(0));
__ cmpps(kScratchDoubleReg, kScratchDoubleReg, 0x4);
__ minps(i.OutputSimd128Register(), i.InputSimd128Register(1));
__ orps(i.OutputSimd128Register(), kScratchDoubleReg);
break;
}
case kX64F32x4Max: {
DCHECK_EQ(i.OutputSimd128Register(), i.InputSimd128Register(0));
// maxps doesn't propagate NaN lanes in the first source. Compare this
// with itself to generate 1's in those lanes (quiet NaNs) and or them
// with the result of maxps to simulate NaN propagation.
__ movaps(kScratchDoubleReg, i.InputSimd128Register(0));
__ cmpps(kScratchDoubleReg, kScratchDoubleReg, 0x4);
__ maxps(i.OutputSimd128Register(), i.InputSimd128Register(1));
__ orps(i.OutputSimd128Register(), kScratchDoubleReg);
break;
}
case kX64F32x4Eq: {

View File

@ -895,6 +895,9 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase {
void maxps(XMMRegister dst, XMMRegister src) { maxps(dst, Operand(src)); }
void cmpps(XMMRegister dst, Operand src, uint8_t cmp);
void cmpps(XMMRegister dst, XMMRegister src, uint8_t cmp) {
cmpps(dst, Operand(src), cmp);
}
#define SSE_CMP_P(instr, imm8) \
void instr##ps(XMMRegister dst, XMMRegister src) { \
cmpps(dst, Operand(src), imm8); \
@ -1497,6 +1500,7 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase {
#define PACKED_OP_LIST(V) \
V(and, 0x54) \
V(or, 0x56) \
V(xor, 0x57) \
V(add, 0x58) \
V(mul, 0x59) \

View File

@ -309,37 +309,6 @@ T Sqrt(T a) {
WASM_SIMD_CHECK16(TYPE, value, LANE_TYPE, lv, lv, lv, lv, lv, lv, lv, lv, \
lv, lv, lv, lv, lv, lv, lv, lv)
#define WASM_SIMD_CHECK_F32_LANE(value, lane_value, lane_index) \
WASM_IF(WASM_F32_NE(WASM_GET_LOCAL(lane_value), \
WASM_SIMD_F32x4_EXTRACT_LANE(lane_index, \
WASM_GET_LOCAL(value))), \
WASM_RETURN1(WASM_ZERO))
#define WASM_SIMD_CHECK_F32x4(value, lv0, lv1, lv2, lv3) \
WASM_SIMD_CHECK_F32_LANE(value, lv0, 0) \
, WASM_SIMD_CHECK_F32_LANE(value, lv1, 1), \
WASM_SIMD_CHECK_F32_LANE(value, lv2, 2), \
WASM_SIMD_CHECK_F32_LANE(value, lv3, 3)
#define WASM_SIMD_CHECK_SPLAT_F32x4(value, lv) \
WASM_SIMD_CHECK_F32x4(value, lv, lv, lv, lv)
#define WASM_SIMD_CHECK_F32_LANE_ESTIMATE(value, low, high, lane_index) \
WASM_IF(WASM_F32_GT(WASM_GET_LOCAL(low), \
WASM_SIMD_F32x4_EXTRACT_LANE(lane_index, \
WASM_GET_LOCAL(value))), \
WASM_RETURN1(WASM_ZERO)) \
, WASM_IF(WASM_F32_LT(WASM_GET_LOCAL(high), \
WASM_SIMD_F32x4_EXTRACT_LANE(lane_index, \
WASM_GET_LOCAL(value))), \
WASM_RETURN1(WASM_ZERO))
#define WASM_SIMD_CHECK_SPLAT_F32x4_ESTIMATE(value, low, high) \
WASM_SIMD_CHECK_F32_LANE_ESTIMATE(value, low, high, 0) \
, WASM_SIMD_CHECK_F32_LANE_ESTIMATE(value, low, high, 1), \
WASM_SIMD_CHECK_F32_LANE_ESTIMATE(value, low, high, 2), \
WASM_SIMD_CHECK_F32_LANE_ESTIMATE(value, low, high, 3)
#define TO_BYTE(val) static_cast<byte>(val)
#define WASM_SIMD_OP(op) kSimdPrefix, TO_BYTE(op)
#define WASM_SIMD_SPLAT(Type, x) x, WASM_SIMD_OP(kExpr##Type##Splat)
@ -385,60 +354,6 @@ T Sqrt(T a) {
#define WASM_SIMD_STORE_MEM(index, val) \
index, val, WASM_SIMD_OP(kExprS128StoreMem), ZERO_ALIGNMENT, ZERO_OFFSET
// Skip FP tests involving extremely large or extremely small values, which
// may fail due to non-IEEE-754 SIMD arithmetic on some platforms.
bool SkipFPValue(float x) {
float abs_x = std::fabs(x);
const float kSmallFloatThreshold = 1.0e-32f;
const float kLargeFloatThreshold = 1.0e32f;
return abs_x != 0.0f && // 0 or -0 are fine.
(abs_x < kSmallFloatThreshold || abs_x > kLargeFloatThreshold);
}
// Skip tests where the expected value is a NaN, since our wasm test code
// doesn't handle NaNs. Also skip extreme values.
bool SkipFPExpectedValue(float x) { return std::isnan(x) || SkipFPValue(x); }
WASM_SIMD_TEST(F32x4Splat) {
WasmRunner<int32_t, float> r(execution_tier, lower_simd);
byte lane_val = 0;
byte simd = r.AllocateLocal(kWasmS128);
BUILD(r,
WASM_SET_LOCAL(simd, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(lane_val))),
WASM_SIMD_CHECK_SPLAT_F32x4(simd, lane_val), WASM_RETURN1(WASM_ONE));
FOR_FLOAT32_INPUTS(i) {
if (SkipFPExpectedValue(i)) continue;
CHECK_EQ(1, r.Call(i));
}
}
WASM_SIMD_TEST(F32x4ReplaceLane) {
WasmRunner<int32_t, float, float> r(execution_tier, lower_simd);
byte old_val = 0;
byte new_val = 1;
byte simd = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(simd, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(old_val))),
WASM_SET_LOCAL(simd,
WASM_SIMD_F32x4_REPLACE_LANE(0, WASM_GET_LOCAL(simd),
WASM_GET_LOCAL(new_val))),
WASM_SIMD_CHECK_F32x4(simd, new_val, old_val, old_val, old_val),
WASM_SET_LOCAL(simd,
WASM_SIMD_F32x4_REPLACE_LANE(1, WASM_GET_LOCAL(simd),
WASM_GET_LOCAL(new_val))),
WASM_SIMD_CHECK_F32x4(simd, new_val, new_val, old_val, old_val),
WASM_SET_LOCAL(simd,
WASM_SIMD_F32x4_REPLACE_LANE(2, WASM_GET_LOCAL(simd),
WASM_GET_LOCAL(new_val))),
WASM_SIMD_CHECK_F32x4(simd, new_val, new_val, new_val, old_val),
WASM_SET_LOCAL(simd,
WASM_SIMD_F32x4_REPLACE_LANE(3, WASM_GET_LOCAL(simd),
WASM_GET_LOCAL(new_val))),
WASM_SIMD_CHECK_SPLAT_F32x4(simd, new_val), WASM_RETURN1(WASM_ONE));
CHECK_EQ(1, r.Call(3.14159f, -1.5f));
}
// Runs tests of compiled code, using the interpreter as a reference.
#define WASM_SIMD_COMPILED_TEST(name) \
void RunWasm_##name##_Impl(LowerSimd lower_simd, \
@ -466,50 +381,133 @@ WASM_SIMD_TEST(F32x4ReplaceLane) {
} \
void RunWasm_##name##_Impl(LowerSimd lower_simd, ExecutionTier execution_tier)
// Returns true if the platform can represent the result.
bool PlatformCanRepresent(float x) {
#if V8_TARGET_ARCH_ARM
return std::fpclassify(x) != FP_SUBNORMAL;
#else
return true;
#endif
}
// Returns true for very small and very large numbers. We skip these test
// values for the approximation instructions, which don't work at the extremes.
bool IsExtreme(float x) {
float abs_x = std::fabs(x);
const float kSmallFloatThreshold = 1.0e-32f;
const float kLargeFloatThreshold = 1.0e32f;
return abs_x != 0.0f && // 0 or -0 are fine.
(abs_x < kSmallFloatThreshold || abs_x > kLargeFloatThreshold);
}
WASM_SIMD_TEST(F32x4Splat) {
WasmRunner<int32_t, float> r(execution_tier, lower_simd);
// Set up a global to hold output vector.
float* g = r.builder().AddGlobal<float>(kWasmS128);
byte param1 = 0;
BUILD(r, WASM_SET_GLOBAL(0, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(param1))),
WASM_ONE);
FOR_FLOAT32_INPUTS(x) {
r.Call(x);
float expected = x;
for (int i = 0; i < 4; i++) {
float actual = ReadLittleEndianValue<float>(&g[i]);
if (std::isnan(expected)) {
CHECK(std::isnan(actual));
} else {
CHECK_EQ(actual, expected);
}
}
}
}
WASM_SIMD_TEST(F32x4ReplaceLane) {
WasmRunner<int32_t> r(execution_tier, lower_simd);
// Set up a global to hold input/output vector.
float* g = r.builder().AddGlobal<float>(kWasmS128);
// Build function to replace each lane with its (FP) index.
byte temp1 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_SPLAT(WASM_F32(3.14159f))),
WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_REPLACE_LANE(
0, WASM_GET_LOCAL(temp1), WASM_F32(0.0f))),
WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_REPLACE_LANE(
1, WASM_GET_LOCAL(temp1), WASM_F32(1.0f))),
WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_REPLACE_LANE(
2, WASM_GET_LOCAL(temp1), WASM_F32(2.0f))),
WASM_SET_GLOBAL(0, WASM_SIMD_F32x4_REPLACE_LANE(
3, WASM_GET_LOCAL(temp1), WASM_F32(3.0f))),
WASM_ONE);
r.Call();
for (int i = 0; i < 4; i++) {
CHECK_EQ(static_cast<float>(i), ReadLittleEndianValue<float>(&g[i]));
}
}
// Tests both signed and unsigned conversion.
// v8:8425 tracks this test being enabled in the interpreter.
WASM_SIMD_COMPILED_TEST(F32x4ConvertI32x4) {
WasmRunner<int32_t, int32_t, float, float> r(execution_tier, lower_simd);
byte a = 0;
byte expected_signed = 1;
byte expected_unsigned = 2;
byte simd0 = r.AllocateLocal(kWasmS128);
byte simd1 = r.AllocateLocal(kWasmS128);
byte simd2 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(simd0, WASM_SIMD_I32x4_SPLAT(WASM_GET_LOCAL(a))),
WASM_SET_LOCAL(simd1, WASM_SIMD_UNOP(kExprF32x4SConvertI32x4,
WASM_GET_LOCAL(simd0))),
WASM_SIMD_CHECK_SPLAT_F32x4(simd1, expected_signed),
WASM_SET_LOCAL(simd2, WASM_SIMD_UNOP(kExprF32x4UConvertI32x4,
WASM_GET_LOCAL(simd0))),
WASM_SIMD_CHECK_SPLAT_F32x4(simd2, expected_unsigned),
WASM_RETURN1(WASM_ONE));
WasmRunner<int32_t, int32_t> r(execution_tier, lower_simd);
// Create two output vectors to hold signed and unsigned results.
float* g0 = r.builder().AddGlobal<float>(kWasmS128);
float* g1 = r.builder().AddGlobal<float>(kWasmS128);
// Build fn to splat test value, perform conversions, and write the results.
byte value = 0;
byte temp1 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(temp1, WASM_SIMD_I32x4_SPLAT(WASM_GET_LOCAL(value))),
WASM_SET_GLOBAL(
0, WASM_SIMD_UNOP(kExprF32x4SConvertI32x4, WASM_GET_LOCAL(temp1))),
WASM_SET_GLOBAL(
1, WASM_SIMD_UNOP(kExprF32x4UConvertI32x4, WASM_GET_LOCAL(temp1))),
WASM_ONE);
FOR_INT32_INPUTS(i) {
CHECK_EQ(1, r.Call(i, static_cast<float>(i),
static_cast<float>(static_cast<uint32_t>(i))));
FOR_INT32_INPUTS(x) {
r.Call(x);
for (int i = 0; i < 4; i++) {
float expected_signed = static_cast<float>(x);
float expected_unsigned = static_cast<float>(static_cast<uint32_t>(x));
CHECK_EQ(expected_signed, ReadLittleEndianValue<float>(&g0[i]));
CHECK_EQ(expected_unsigned, ReadLittleEndianValue<float>(&g1[i]));
}
}
}
void RunF32x4UnOpTest(ExecutionTier execution_tier, LowerSimd lower_simd,
WasmOpcode simd_op, FloatUnOp expected_op,
float error = 0.0f) {
WasmRunner<int32_t, float, float, float> r(execution_tier, lower_simd);
byte a = 0;
byte low = 1;
byte high = 2;
byte simd = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(simd, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(a))),
WASM_SET_LOCAL(simd, WASM_SIMD_UNOP(simd_op, WASM_GET_LOCAL(simd))),
WASM_SIMD_CHECK_SPLAT_F32x4_ESTIMATE(simd, low, high),
WASM_RETURN1(WASM_ONE));
WasmOpcode opcode, FloatUnOp expected_op,
bool approximate = false) {
WasmRunner<int32_t, float> r(execution_tier, lower_simd);
// Global to hold output.
float* g = r.builder().AddGlobal<float>(kWasmS128);
// Build fn to splat test value, perform unop, and write the result.
byte value = 0;
byte temp1 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(value))),
WASM_SET_GLOBAL(0, WASM_SIMD_UNOP(opcode, WASM_GET_LOCAL(temp1))),
WASM_ONE);
FOR_FLOAT32_INPUTS(i) {
if (SkipFPValue(i)) continue;
float expected = expected_op(i);
if (SkipFPExpectedValue(expected)) continue;
float abs_error = std::abs(expected) * error;
CHECK_EQ(1, r.Call(i, expected - abs_error, expected + abs_error));
FOR_FLOAT32_INPUTS(x) {
// Extreme values have larger errors so skip them for approximation tests.
if (approximate && IsExtreme(x)) continue;
float expected = expected_op(x);
if (!PlatformCanRepresent(expected)) continue;
r.Call(x);
for (int i = 0; i < 4; i++) {
float actual = ReadLittleEndianValue<float>(&g[i]);
if (std::isnan(expected)) {
CHECK(std::isnan(actual));
} else {
// First check for equality, to handle +/-Inf, since min and max would
// be NaNs in those cases.
if (expected == actual) continue;
// 1% error allows all platforms to pass easily.
constexpr float kApproximationError = 0.01f;
float abs_error = std::abs(expected) * kApproximationError,
min = expected - abs_error, max = expected + abs_error;
CHECK_LE(min, actual);
CHECK_GE(max, actual);
}
}
}
}
@ -520,39 +518,44 @@ WASM_SIMD_TEST(F32x4Neg) {
RunF32x4UnOpTest(execution_tier, lower_simd, kExprF32x4Neg, Negate);
}
static const float kApproxError = 0.01f;
WASM_SIMD_TEST(F32x4RecipApprox) {
RunF32x4UnOpTest(execution_tier, lower_simd, kExprF32x4RecipApprox,
base::Recip, kApproxError);
base::Recip, true /* approximate */);
}
WASM_SIMD_TEST(F32x4RecipSqrtApprox) {
RunF32x4UnOpTest(execution_tier, lower_simd, kExprF32x4RecipSqrtApprox,
base::RecipSqrt, kApproxError);
base::RecipSqrt, true /* approximate */);
}
void RunF32x4BinOpTest(ExecutionTier execution_tier, LowerSimd lower_simd,
WasmOpcode simd_op, FloatBinOp expected_op) {
WasmRunner<int32_t, float, float, float> r(execution_tier, lower_simd);
byte a = 0;
byte b = 1;
byte expected = 2;
byte simd0 = r.AllocateLocal(kWasmS128);
byte simd1 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(simd0, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(a))),
WASM_SET_LOCAL(simd1, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(b))),
WASM_SET_LOCAL(simd1, WASM_SIMD_BINOP(simd_op, WASM_GET_LOCAL(simd0),
WASM_GET_LOCAL(simd1))),
WASM_SIMD_CHECK_SPLAT_F32x4(simd1, expected), WASM_RETURN1(WASM_ONE));
WasmOpcode opcode, FloatBinOp expected_op) {
WasmRunner<int32_t, float, float> r(execution_tier, lower_simd);
// Global to hold output.
float* g = r.builder().AddGlobal<float>(kWasmS128);
// Build fn to splat test values, perform binop, and write the result.
byte value1 = 0, value2 = 1;
byte temp1 = r.AllocateLocal(kWasmS128);
byte temp2 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(value1))),
WASM_SET_LOCAL(temp2, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(value2))),
WASM_SET_GLOBAL(0, WASM_SIMD_BINOP(opcode, WASM_GET_LOCAL(temp1),
WASM_GET_LOCAL(temp2))),
WASM_ONE);
FOR_FLOAT32_INPUTS(i) {
if (SkipFPValue(i)) continue;
FOR_FLOAT32_INPUTS(j) {
if (SkipFPValue(j)) continue;
float expected = expected_op(i, j);
if (SkipFPExpectedValue(expected)) continue;
CHECK_EQ(1, r.Call(i, j, expected));
FOR_FLOAT32_INPUTS(x) {
FOR_FLOAT32_INPUTS(y) {
float expected = expected_op(x, y);
if (!PlatformCanRepresent(expected)) continue;
r.Call(x, y);
for (int i = 0; i < 4; i++) {
float actual = ReadLittleEndianValue<float>(&g[i]);
if (std::isnan(expected)) {
CHECK(std::isnan(actual));
} else {
CHECK_EQ(expected, actual);
}
}
}
}
}
@ -566,34 +569,39 @@ WASM_SIMD_TEST(F32x4Sub) {
WASM_SIMD_TEST(F32x4Mul) {
RunF32x4BinOpTest(execution_tier, lower_simd, kExprF32x4Mul, Mul);
}
WASM_SIMD_TEST(F32x4_Min) {
// v8:8425 tracks this test being enabled in the interpreter.
WASM_SIMD_COMPILED_TEST(F32x4Min) {
RunF32x4BinOpTest(execution_tier, lower_simd, kExprF32x4Min, JSMin);
}
WASM_SIMD_TEST(F32x4_Max) {
// v8:8425 tracks this test being enabled in the interpreter.
WASM_SIMD_COMPILED_TEST(F32x4Max) {
RunF32x4BinOpTest(execution_tier, lower_simd, kExprF32x4Max, JSMax);
}
void RunF32x4CompareOpTest(ExecutionTier execution_tier, LowerSimd lower_simd,
WasmOpcode simd_op, FloatCompareOp expected_op) {
WasmRunner<int32_t, float, float, int32_t> r(execution_tier, lower_simd);
byte a = 0;
byte b = 1;
byte expected = 2;
byte simd0 = r.AllocateLocal(kWasmS128);
byte simd1 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(simd0, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(a))),
WASM_SET_LOCAL(simd1, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(b))),
WASM_SET_LOCAL(simd1, WASM_SIMD_BINOP(simd_op, WASM_GET_LOCAL(simd0),
WASM_GET_LOCAL(simd1))),
WASM_SIMD_CHECK_SPLAT4(I32x4, simd1, I32, expected), WASM_ONE);
WasmOpcode opcode, FloatCompareOp expected_op) {
WasmRunner<int32_t, float, float> r(execution_tier, lower_simd);
// Set up global to hold mask output.
int32_t* g = r.builder().AddGlobal<int32_t>(kWasmS128);
// Build fn to splat test values, perform compare op, and write the result.
byte value1 = 0, value2 = 1;
byte temp1 = r.AllocateLocal(kWasmS128);
byte temp2 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(value1))),
WASM_SET_LOCAL(temp2, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(value2))),
WASM_SET_GLOBAL(0, WASM_SIMD_BINOP(opcode, WASM_GET_LOCAL(temp1),
WASM_GET_LOCAL(temp2))),
WASM_ONE);
FOR_FLOAT32_INPUTS(i) {
if (SkipFPValue(i)) continue;
FOR_FLOAT32_INPUTS(j) {
if (SkipFPValue(j)) continue;
float diff = i - j;
if (SkipFPExpectedValue(diff)) continue;
CHECK_EQ(1, r.Call(i, j, expected_op(i, j)));
FOR_FLOAT32_INPUTS(x) {
FOR_FLOAT32_INPUTS(y) {
float diff = x - y; // Model comparison as subtraction.
if (!PlatformCanRepresent(diff)) continue;
r.Call(x, y);
int32_t expected = expected_op(x, y);
for (int i = 0; i < 4; i++) {
CHECK_EQ(expected, ReadLittleEndianValue<int32_t>(&g[i]));
}
}
}
}
@ -859,26 +867,29 @@ int32_t ConvertToInt(double val, bool unsigned_integer) {
// Tests both signed and unsigned conversion.
WASM_SIMD_TEST(I32x4ConvertF32x4) {
WasmRunner<int32_t, float, int32_t, int32_t> r(execution_tier, lower_simd);
byte a = 0;
byte expected_signed = 1;
byte expected_unsigned = 2;
byte simd0 = r.AllocateLocal(kWasmS128);
byte simd1 = r.AllocateLocal(kWasmS128);
byte simd2 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(simd0, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(a))),
WASM_SET_LOCAL(simd1, WASM_SIMD_UNOP(kExprI32x4SConvertF32x4,
WASM_GET_LOCAL(simd0))),
WASM_SIMD_CHECK_SPLAT4(I32x4, simd1, I32, expected_signed),
WASM_SET_LOCAL(simd2, WASM_SIMD_UNOP(kExprI32x4UConvertF32x4,
WASM_GET_LOCAL(simd0))),
WASM_SIMD_CHECK_SPLAT4(I32x4, simd2, I32, expected_unsigned), WASM_ONE);
WasmRunner<int32_t, float> r(execution_tier, lower_simd);
// Create two output vectors to hold signed and unsigned results.
int32_t* g0 = r.builder().AddGlobal<int32_t>(kWasmS128);
int32_t* g1 = r.builder().AddGlobal<int32_t>(kWasmS128);
// Build fn to splat test value, perform conversions, and write the results.
byte value = 0;
byte temp1 = r.AllocateLocal(kWasmS128);
BUILD(r, WASM_SET_LOCAL(temp1, WASM_SIMD_F32x4_SPLAT(WASM_GET_LOCAL(value))),
WASM_SET_GLOBAL(
0, WASM_SIMD_UNOP(kExprI32x4SConvertF32x4, WASM_GET_LOCAL(temp1))),
WASM_SET_GLOBAL(
1, WASM_SIMD_UNOP(kExprI32x4UConvertF32x4, WASM_GET_LOCAL(temp1))),
WASM_ONE);
FOR_FLOAT32_INPUTS(i) {
if (SkipFPValue(i)) continue;
int32_t signed_value = ConvertToInt(i, false);
int32_t unsigned_value = ConvertToInt(i, true);
CHECK_EQ(1, r.Call(i, signed_value, unsigned_value));
FOR_FLOAT32_INPUTS(x) {
if (!PlatformCanRepresent(x)) continue;
r.Call(x);
int32_t expected_signed = ConvertToInt(x, false);
int32_t expected_unsigned = ConvertToInt(x, true);
for (int i = 0; i < 4; i++) {
CHECK_EQ(expected_signed, ReadLittleEndianValue<int32_t>(&g0[i]));
CHECK_EQ(expected_unsigned, ReadLittleEndianValue<int32_t>(&g1[i]));
}
}
}
@ -2398,11 +2409,6 @@ WASM_SIMD_TEST_TURBOFAN(BitSelect) {
#undef WASM_SIMD_CHECK_SPLAT8
#undef WASM_SIMD_CHECK16
#undef WASM_SIMD_CHECK_SPLAT16
#undef WASM_SIMD_CHECK_F32_LANE
#undef WASM_SIMD_CHECK_F32x4
#undef WASM_SIMD_CHECK_SPLAT_F32x4
#undef WASM_SIMD_CHECK_F32_LANE_ESTIMATE
#undef WASM_SIMD_CHECK_SPLAT_F32x4_ESTIMATE
#undef TO_BYTE
#undef WASM_SIMD_OP
#undef WASM_SIMD_SPLAT