// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "test/cctest/wasm/wasm-simd-utils.h" #include #include #include "src/base/logging.h" #include "src/base/memory.h" #include "src/common/globals.h" #include "src/wasm/compilation-environment.h" #include "src/wasm/value-type.h" #include "src/wasm/wasm-opcodes-inl.h" #include "src/wasm/wasm-opcodes.h" #include "test/cctest/wasm/wasm-run-utils.h" #include "test/common/c-signature.h" #include "test/common/value-helper.h" #include "test/common/wasm/wasm-macro-gen.h" namespace v8 { namespace internal { namespace wasm { void RunI8x16UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int8UnOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. int8_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform unop, and write the result. uint8_t value = 0; uint8_t temp1 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(opcode, WASM_LOCAL_GET(temp1))), WASM_ONE}); FOR_INT8_INPUTS(x) { r.Call(x); int8_t expected = expected_op(x); for (int i = 0; i < 16; i++) { CHECK_EQ(expected, LANE(g, i)); } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunI8x32UnOpRevecTest(WasmOpcode opcode, Int8UnOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); WasmRunner r(TestExecutionTier::kTurbofan); int8_t* memory = r.builder().AddMemoryElems(64); // Build fn to load a I8x32 vector with test value, perform unop, and write // the result to another array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmS128); byte temp2 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET( temp1, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)))), WASM_LOCAL_SET( temp2, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp1)), WASM_SIMD_STORE_MEM_OFFSET(offset, WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp2)), WASM_ONE}); FOR_INT8_INPUTS(x) { r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[18], x); r.Call(0, 32); int8_t expected = expected_op(x); CHECK_EQ(expected, memory[33]); CHECK_EQ(expected, memory[50]); } } #endif template void RunI8x16BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, OpType expected_op) { WasmRunner r(execution_tier); // Global to hold output. T* g = r.builder().template AddGlobal(kWasmS128); // Build fn to splat test values, perform binop, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); for (T x : compiler::ValueHelper::GetVector()) { for (T y : compiler::ValueHelper::GetVector()) { r.Call(x, y); T expected = expected_op(x, y); for (int i = 0; i < 16; i++) { CHECK_EQ(expected, LANE(g, i)); } } } } // Explicit instantiations of uses. template void RunI8x16BinOpTest(TestExecutionTier, WasmOpcode, Int8BinOp); template void RunI8x16BinOpTest(TestExecutionTier, WasmOpcode, Uint8BinOp); void RunI8x16ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int8ShiftOp expected_op) { // Intentionally shift by 8, should be no-op. for (int shift = 1; shift <= 8; shift++) { WasmRunner r(execution_tier); int32_t* memory = r.builder().AddMemoryElems(1); int8_t* g_imm = r.builder().AddGlobal(kWasmS128); int8_t* g_mem = r.builder().AddGlobal(kWasmS128); uint8_t value = 0; uint8_t simd = r.AllocateLocal(kWasmS128); // Shift using an immediate, and shift using a value loaded from memory. r.Build({WASM_LOCAL_SET(simd, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(simd), WASM_I32V(shift))), WASM_GLOBAL_SET( 1, WASM_SIMD_SHIFT_OP( opcode, WASM_LOCAL_GET(simd), WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO))), WASM_ONE}); r.builder().WriteMemory(&memory[0], shift); FOR_INT8_INPUTS(x) { r.Call(x); int8_t expected = expected_op(x, shift); for (int i = 0; i < 16; i++) { CHECK_EQ(expected, LANE(g_imm, i)); CHECK_EQ(expected, LANE(g_mem, i)); } } } } void RunI8x16MixedRelationalOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int8BinOp expected_op) { WasmRunner r(execution_tier); uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); uint8_t temp3 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I8x16_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value2))), WASM_LOCAL_SET(temp3, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_SIMD_I8x16_EXTRACT_LANE(0, WASM_LOCAL_GET(temp3))}); CHECK_EQ(expected_op(0xff, static_cast(0x7fff)), r.Call(0xff, 0x7fff)); CHECK_EQ(expected_op(0xfe, static_cast(0x7fff)), r.Call(0xfe, 0x7fff)); CHECK_EQ(expected_op(0xff, static_cast(0x7ffe)), r.Call(0xff, 0x7ffe)); } void RunI16x8UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int16UnOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. int16_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform unop, and write the result. uint8_t value = 0; uint8_t temp1 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(opcode, WASM_LOCAL_GET(temp1))), WASM_ONE}); FOR_INT16_INPUTS(x) { r.Call(x); int16_t expected = expected_op(x); for (int i = 0; i < 8; i++) { CHECK_EQ(expected, LANE(g, i)); } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunI16x16UnOpRevecTest(WasmOpcode opcode, Int16UnOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); WasmRunner r(TestExecutionTier::kTurbofan); int16_t* memory = r.builder().AddMemoryElems(32); // Build fn to load a I16x16 vector with test value, perform unop, and write // the result to another array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmS128); byte temp2 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET( temp1, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)))), WASM_LOCAL_SET( temp2, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp1)), WASM_SIMD_STORE_MEM_OFFSET(offset, WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp2)), WASM_ONE}); FOR_INT16_INPUTS(x) { r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[10], x); r.Call(0, 32); int16_t expected = expected_op(x); CHECK_EQ(expected, memory[17]); CHECK_EQ(expected, memory[26]); } } #endif template void RunI16x8BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, OpType expected_op) { WasmRunner r(execution_tier); // Global to hold output. T* g = r.builder().template AddGlobal(kWasmS128); // Build fn to splat test values, perform binop, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); for (T x : compiler::ValueHelper::GetVector()) { for (T y : compiler::ValueHelper::GetVector()) { r.Call(x, y); T expected = expected_op(x, y); for (int i = 0; i < 8; i++) { CHECK_EQ(expected, LANE(g, i)); } } } } // Explicit instantiations of uses. template void RunI16x8BinOpTest(TestExecutionTier, WasmOpcode, Int16BinOp); template void RunI16x8BinOpTest(TestExecutionTier, WasmOpcode, Uint16BinOp); void RunI16x8ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int16ShiftOp expected_op) { // Intentionally shift by 16, should be no-op. for (int shift = 1; shift <= 16; shift++) { WasmRunner r(execution_tier); int32_t* memory = r.builder().AddMemoryElems(1); int16_t* g_imm = r.builder().AddGlobal(kWasmS128); int16_t* g_mem = r.builder().AddGlobal(kWasmS128); uint8_t value = 0; uint8_t simd = r.AllocateLocal(kWasmS128); // Shift using an immediate, and shift using a value loaded from memory. r.Build({WASM_LOCAL_SET(simd, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(simd), WASM_I32V(shift))), WASM_GLOBAL_SET( 1, WASM_SIMD_SHIFT_OP( opcode, WASM_LOCAL_GET(simd), WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO))), WASM_ONE}); r.builder().WriteMemory(&memory[0], shift); FOR_INT16_INPUTS(x) { r.Call(x); int16_t expected = expected_op(x, shift); for (int i = 0; i < 8; i++) { CHECK_EQ(expected, LANE(g_imm, i)); CHECK_EQ(expected, LANE(g_mem, i)); } } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunI16x16ShiftOpRevecTest(WasmOpcode opcode, Int16ShiftOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); for (int shift = 1; shift <= 8; shift++) { WasmRunner r(TestExecutionTier::kTurbofan); int16_t* memory = r.builder().AddMemoryElems(34); // Build fn to load an I16x16 vector with test value, shift using an // immediate and a value loaded from memory. Write the result to another // array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmI32); byte temp2 = r.AllocateLocal(kWasmS128); byte temp3 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET(temp2, WASM_SIMD_SHIFT_OP( opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)), WASM_I32V(shift))), WASM_LOCAL_SET(temp3, WASM_SIMD_SHIFT_OP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)), WASM_I32V(shift))), WASM_LOCAL_SET(temp1, WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(64))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(temp2), WASM_LOCAL_GET(temp1))), WASM_SIMD_STORE_MEM_OFFSET( offset, WASM_LOCAL_GET(param2), WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(temp3), WASM_LOCAL_GET(temp1))), WASM_ONE}); r.builder().WriteMemory(reinterpret_cast(&memory[32]), shift); FOR_INT16_INPUTS(x) { r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[10], x); r.Call(0, 32); // Shift twice int16_t expected = expected_op(expected_op(x, shift), shift); CHECK_EQ(expected, memory[17]); CHECK_EQ(expected, memory[26]); } } } #endif void RunI16x8MixedRelationalOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int16BinOp expected_op) { WasmRunner r(execution_tier); uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); uint8_t temp3 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I16x8_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(value2))), WASM_LOCAL_SET(temp3, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_SIMD_I16x8_EXTRACT_LANE(0, WASM_LOCAL_GET(temp3))}); CHECK_EQ(expected_op(0xffff, static_cast(0x7fffffff)), r.Call(0xffff, 0x7fffffff)); CHECK_EQ(expected_op(0xfeff, static_cast(0x7fffffff)), r.Call(0xfeff, 0x7fffffff)); CHECK_EQ(expected_op(0xffff, static_cast(0x7ffffeff)), r.Call(0xffff, 0x7ffffeff)); } void RunI32x4UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int32UnOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. int32_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform unop, and write the result. uint8_t value = 0; uint8_t temp1 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(opcode, WASM_LOCAL_GET(temp1))), WASM_ONE}); FOR_INT32_INPUTS(x) { r.Call(x); int32_t expected = expected_op(x); for (int i = 0; i < 4; i++) { CHECK_EQ(expected, LANE(g, i)); } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunI32x8UnOpRevecTest(WasmOpcode opcode, Int32UnOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); WasmRunner r(TestExecutionTier::kTurbofan); int32_t* memory = r.builder().AddMemoryElems(16); // Build fn to load a I32x8 vector with test value, perform unop, and write // the result to another array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmS128); byte temp2 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET( temp1, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)))), WASM_LOCAL_SET( temp2, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp1)), WASM_SIMD_STORE_MEM_OFFSET(offset, WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp2)), WASM_ONE}); FOR_INT32_INPUTS(x) { r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[6], x); r.Call(0, 32); int32_t expected = expected_op(x); CHECK_EQ(expected, memory[9]); CHECK_EQ(expected, memory[14]); } } #endif void RunI32x4BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int32BinOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. int32_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test values, perform binop, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); FOR_INT32_INPUTS(x) { FOR_INT32_INPUTS(y) { r.Call(x, y); int32_t expected = expected_op(x, y); for (int i = 0; i < 4; i++) { CHECK_EQ(expected, LANE(g, i)); } } } } void RunI32x4ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int32ShiftOp expected_op) { // Intentionally shift by 32, should be no-op. for (int shift = 1; shift <= 32; shift++) { WasmRunner r(execution_tier); int32_t* memory = r.builder().AddMemoryElems(1); int32_t* g_imm = r.builder().AddGlobal(kWasmS128); int32_t* g_mem = r.builder().AddGlobal(kWasmS128); uint8_t value = 0; uint8_t simd = r.AllocateLocal(kWasmS128); // Shift using an immediate, and shift using a value loaded from memory. r.Build({WASM_LOCAL_SET(simd, WASM_SIMD_I32x4_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(simd), WASM_I32V(shift))), WASM_GLOBAL_SET( 1, WASM_SIMD_SHIFT_OP( opcode, WASM_LOCAL_GET(simd), WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO))), WASM_ONE}); r.builder().WriteMemory(&memory[0], shift); FOR_INT32_INPUTS(x) { r.Call(x); int32_t expected = expected_op(x, shift); for (int i = 0; i < 4; i++) { CHECK_EQ(expected, LANE(g_imm, i)); CHECK_EQ(expected, LANE(g_mem, i)); } } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunI32x8ShiftOpRevecTest(WasmOpcode opcode, Int32ShiftOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); for (int shift = 1; shift <= 16; shift++) { WasmRunner r(TestExecutionTier::kTurbofan); int32_t* memory = r.builder().AddMemoryElems(17); // Build fn to load an I32x8 vector with test value, shift using an // immediate and a value loaded from memory. Write the result to another // array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmI32); byte temp2 = r.AllocateLocal(kWasmS128); byte temp3 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET(temp2, WASM_SIMD_SHIFT_OP( opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)), WASM_I32V(shift))), WASM_LOCAL_SET(temp3, WASM_SIMD_SHIFT_OP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)), WASM_I32V(shift))), WASM_LOCAL_SET(temp1, WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(64))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(temp2), WASM_LOCAL_GET(temp1))), WASM_SIMD_STORE_MEM_OFFSET( offset, WASM_LOCAL_GET(param2), WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(temp3), WASM_LOCAL_GET(temp1))), WASM_ONE}); r.builder().WriteMemory(&memory[16], shift); FOR_INT32_INPUTS(x) { r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[6], x); r.Call(0, 32); // Shift twice int32_t expected = expected_op(expected_op(x, shift), shift); CHECK_EQ(expected, memory[9]); CHECK_EQ(expected, memory[14]); } } } #endif void RunI64x2UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int64UnOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. int64_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform unop, and write the result. uint8_t value = 0; uint8_t temp1 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I64x2_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(opcode, WASM_LOCAL_GET(temp1))), WASM_ONE}); FOR_INT64_INPUTS(x) { r.Call(x); int64_t expected = expected_op(x); for (int i = 0; i < 2; i++) { CHECK_EQ(expected, LANE(g, i)); } } } void RunI64x2BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int64BinOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. int64_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test values, perform binop, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_I64x2_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_I64x2_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); FOR_INT64_INPUTS(x) { FOR_INT64_INPUTS(y) { r.Call(x, y); int64_t expected = expected_op(x, y); for (int i = 0; i < 2; i++) { CHECK_EQ(expected, LANE(g, i)); } } } } void RunI64x2ShiftOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, Int64ShiftOp expected_op) { // Intentionally shift by 64, should be no-op. for (int shift = 1; shift <= 64; shift++) { WasmRunner r(execution_tier); int32_t* memory = r.builder().AddMemoryElems(1); int64_t* g_imm = r.builder().AddGlobal(kWasmS128); int64_t* g_mem = r.builder().AddGlobal(kWasmS128); uint8_t value = 0; uint8_t simd = r.AllocateLocal(kWasmS128); // Shift using an immediate, and shift using a value loaded from memory. r.Build({WASM_LOCAL_SET(simd, WASM_SIMD_I64x2_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(simd), WASM_I32V(shift))), WASM_GLOBAL_SET( 1, WASM_SIMD_SHIFT_OP( opcode, WASM_LOCAL_GET(simd), WASM_LOAD_MEM(MachineType::Int32(), WASM_ZERO))), WASM_ONE}); r.builder().WriteMemory(&memory[0], shift); FOR_INT64_INPUTS(x) { r.Call(x); int64_t expected = expected_op(x, shift); for (int i = 0; i < 2; i++) { CHECK_EQ(expected, LANE(g_imm, i)); CHECK_EQ(expected, LANE(g_mem, i)); } } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunI64x4ShiftOpRevecTest(WasmOpcode opcode, Int64ShiftOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); for (int shift = 1; shift <= 32; shift++) { WasmRunner r(TestExecutionTier::kTurbofan); int64_t* memory = r.builder().AddMemoryElems(9); // Build fn to load an I64x4 vector with test value, shift using an // immediate and a value loaded from memory. Write the result to another // array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmI32); byte temp2 = r.AllocateLocal(kWasmS128); byte temp3 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET(temp2, WASM_SIMD_SHIFT_OP( opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)), WASM_I32V(shift))), WASM_LOCAL_SET(temp3, WASM_SIMD_SHIFT_OP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)), WASM_I32V(shift))), WASM_LOCAL_SET(temp1, WASM_LOAD_MEM(MachineType::Int32(), WASM_I32V(64))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(temp2), WASM_LOCAL_GET(temp1))), WASM_SIMD_STORE_MEM_OFFSET( offset, WASM_LOCAL_GET(param2), WASM_SIMD_SHIFT_OP(opcode, WASM_LOCAL_GET(temp3), WASM_LOCAL_GET(temp1))), WASM_ONE}); r.builder().WriteMemory(reinterpret_cast(&memory[8]), shift); FOR_INT64_INPUTS(x) { r.builder().WriteMemory(&memory[0], x); r.builder().WriteMemory(&memory[3], x); r.Call(0, 32); // Shift twice int64_t expected = expected_op(expected_op(x, shift), shift); CHECK_EQ(expected, memory[4]); CHECK_EQ(expected, memory[7]); } } } #endif 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); } bool IsCanonical(float actual) { uint32_t actual_bits = base::bit_cast(actual); // Canonical NaN has quiet bit and no payload. return (actual_bits & 0xFFC00000) == actual_bits; } void CheckFloatResult(float x, float y, float expected, float actual, bool exact) { if (std::isnan(expected)) { CHECK(std::isnan(actual)); if (std::isnan(x) && IsSameNan(x, actual)) return; if (std::isnan(y) && IsSameNan(y, actual)) return; if (IsSameNan(expected, actual)) return; if (IsCanonical(actual)) return; // This is expected to assert; it's useful for debugging. CHECK_EQ(base::bit_cast(expected), base::bit_cast(actual)); } else { if (exact) { CHECK_EQ(expected, actual); // The sign of 0's must match. CHECK_EQ(std::signbit(expected), std::signbit(actual)); return; } // Otherwise, perform an approximate equality test. First check for // equality to handle +/-Infinity where approximate equality doesn't work. if (expected == actual) return; // 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); } } void RunF32x4UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, FloatUnOp expected_op, bool exact) { WasmRunner r(execution_tier); // Global to hold output. float* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform unop, and write the result. uint8_t value = 0; uint8_t temp1 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(opcode, WASM_LOCAL_GET(temp1))), WASM_ONE}); FOR_FLOAT32_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; // Extreme values have larger errors so skip them for approximation tests. if (!exact && IsExtreme(x)) continue; float expected = expected_op(x); #if V8_OS_AIX if (!MightReverseSign(expected_op)) expected = FpOpWorkaround(x, expected); #endif if (!PlatformCanRepresent(expected)) continue; r.Call(x); for (int i = 0; i < 4; i++) { float actual = LANE(g, i); CheckFloatResult(x, x, expected, actual, exact); } } FOR_FLOAT32_NAN_INPUTS(f) { float x = base::bit_cast(nan_test_array[f]); if (!PlatformCanRepresent(x)) continue; // Extreme values have larger errors so skip them for approximation tests. if (!exact && IsExtreme(x)) continue; float expected = expected_op(x); if (!PlatformCanRepresent(expected)) continue; r.Call(x); for (int i = 0; i < 4; i++) { float actual = LANE(g, i); CheckFloatResult(x, x, expected, actual, exact); } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunF32x8UnOpRevecTest(WasmOpcode opcode, FloatUnOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); WasmRunner r(TestExecutionTier::kTurbofan); float* memory = r.builder().AddMemoryElems(16); // Build fn to load a F32x8 vector with test value, perform unop, and write // the result to another array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmS128); byte temp2 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET( temp1, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)))), WASM_LOCAL_SET( temp2, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp1)), WASM_SIMD_STORE_MEM_OFFSET(offset, WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp2)), WASM_ONE}); FOR_FLOAT32_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; float expected = expected_op(x); #if V8_OS_AIX if (!MightReverseSign(expected_op)) expected = FpOpWorkaround(x, expected); #endif if (!PlatformCanRepresent(expected)) continue; r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[6], x); r.Call(0, 32); CheckFloatResult(x, x, expected, memory[9]); CheckFloatResult(x, x, expected, memory[14]); } FOR_FLOAT32_NAN_INPUTS(f) { float x = base::bit_cast(nan_test_array[f]); if (!PlatformCanRepresent(x)) continue; float expected = expected_op(x); if (!PlatformCanRepresent(expected)) continue; r.builder().WriteMemory(&memory[1], x); r.builder().WriteMemory(&memory[6], x); r.Call(0, 32); CheckFloatResult(x, x, expected, memory[9]); CheckFloatResult(x, x, expected, memory[14]); } } #endif namespace { // Relaxed-simd operations are deterministic only for some range of values. // Exclude those from being tested. Currently this is only used for f32x4, f64x2 // relaxed min and max. template typename std::enable_if::value, bool>::type ShouldSkipTestingConstants(WasmOpcode opcode, T lhs, T rhs) { bool has_nan = std::isnan(lhs) || std::isnan(rhs); bool zeroes_of_opposite_signs = (lhs == 0 && rhs == 0 && (std::signbit(lhs) != std::signbit(rhs))); return WasmOpcodes::IsRelaxedSimdOpcode(opcode) && (has_nan || zeroes_of_opposite_signs); } } // namespace void RunF32x4BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, FloatBinOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. float* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test values, perform binop, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); FOR_FLOAT32_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; FOR_FLOAT32_INPUTS(y) { if (!PlatformCanRepresent(y)) continue; if (ShouldSkipTestingConstants(opcode, x, y)) continue; float expected = expected_op(x, y); if (!PlatformCanRepresent(expected)) continue; r.Call(x, y); for (int i = 0; i < 4; i++) { float actual = g[i]; CheckFloatResult(x, y, expected, actual, true /* exact */); } } } FOR_FLOAT32_NAN_INPUTS(f) { float x = base::bit_cast(nan_test_array[f]); if (!PlatformCanRepresent(x)) continue; FOR_FLOAT32_NAN_INPUTS(j) { float y = base::bit_cast(nan_test_array[j]); if (!PlatformCanRepresent(y)) continue; if (ShouldSkipTestingConstants(opcode, x, y)) continue; float expected = expected_op(x, y); if (!PlatformCanRepresent(expected)) continue; r.Call(x, y); for (int i = 0; i < 4; i++) { float actual = LANE(g, i); CheckFloatResult(x, y, expected, actual, true /* exact */); } } } } void RunF32x4CompareOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, FloatCompareOp expected_op) { WasmRunner r(execution_tier); // Set up global to hold mask output. int32_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test values, perform compare op, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_F32x4_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); FOR_FLOAT32_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; FOR_FLOAT32_INPUTS(y) { if (!PlatformCanRepresent(y)) continue; 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, LANE(g, i)); } } } } bool IsExtreme(double x) { double abs_x = std::fabs(x); const double kSmallFloatThreshold = 1.0e-298; const double kLargeFloatThreshold = 1.0e298; return abs_x != 0.0f && // 0 or -0 are fine. (abs_x < kSmallFloatThreshold || abs_x > kLargeFloatThreshold); } bool IsCanonical(double actual) { uint64_t actual_bits = base::bit_cast(actual); // Canonical NaN has quiet bit and no payload. return (actual_bits & 0xFFF8000000000000) == actual_bits; } void CheckDoubleResult(double x, double y, double expected, double actual, bool exact) { if (std::isnan(expected)) { CHECK(std::isnan(actual)); if (std::isnan(x) && IsSameNan(x, actual)) return; if (std::isnan(y) && IsSameNan(y, actual)) return; if (IsSameNan(expected, actual)) return; if (IsCanonical(actual)) return; // This is expected to assert; it's useful for debugging. CHECK_EQ(base::bit_cast(expected), base::bit_cast(actual)); } else { if (exact) { CHECK_EQ(expected, actual); // The sign of 0's must match. CHECK_EQ(std::signbit(expected), std::signbit(actual)); return; } // Otherwise, perform an approximate equality test. First check for // equality to handle +/-Infinity where approximate equality doesn't work. if (expected == actual) return; // 1% error allows all platforms to pass easily. constexpr double kApproximationError = 0.01f; double abs_error = std::abs(expected) * kApproximationError, min = expected - abs_error, max = expected + abs_error; CHECK_LE(min, actual); CHECK_GE(max, actual); } } void RunF64x2UnOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, DoubleUnOp expected_op, bool exact) { WasmRunner r(execution_tier); // Global to hold output. double* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform unop, and write the result. uint8_t value = 0; uint8_t temp1 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value))), WASM_GLOBAL_SET(0, WASM_SIMD_UNOP(opcode, WASM_LOCAL_GET(temp1))), WASM_ONE}); FOR_FLOAT64_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; // Extreme values have larger errors so skip them for approximation tests. if (!exact && IsExtreme(x)) continue; double expected = expected_op(x); #if V8_OS_AIX if (!MightReverseSign(expected_op)) expected = FpOpWorkaround(x, expected); #endif if (!PlatformCanRepresent(expected)) continue; r.Call(x); for (int i = 0; i < 2; i++) { double actual = LANE(g, i); CheckDoubleResult(x, x, expected, actual, exact); } } FOR_FLOAT64_NAN_INPUTS(d) { double x = base::bit_cast(double_nan_test_array[d]); if (!PlatformCanRepresent(x)) continue; // Extreme values have larger errors so skip them for approximation tests. if (!exact && IsExtreme(x)) continue; double expected = expected_op(x); if (!PlatformCanRepresent(expected)) continue; r.Call(x); for (int i = 0; i < 2; i++) { double actual = LANE(g, i); CheckDoubleResult(x, x, expected, actual, exact); } } } #ifdef V8_ENABLE_WASM_SIMD256_REVEC void RunF64x4UnOpRevecTest(WasmOpcode opcode, DoubleUnOp expected_op) { EXPERIMENTAL_FLAG_SCOPE(revectorize); WasmRunner r(TestExecutionTier::kTurbofan); double* memory = r.builder().AddMemoryElems(8); // Build fn to load a F64x4 vector with test value, perform unop, and write // the result to another array. byte param1 = 0; byte param2 = 1; byte temp1 = r.AllocateLocal(kWasmS128); byte temp2 = r.AllocateLocal(kWasmS128); constexpr byte offset = 16; r.Build( {WASM_LOCAL_SET( temp1, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM(WASM_LOCAL_GET(param1)))), WASM_LOCAL_SET( temp2, WASM_SIMD_UNOP(opcode, WASM_SIMD_LOAD_MEM_OFFSET( offset, WASM_LOCAL_GET(param1)))), WASM_SIMD_STORE_MEM(WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp1)), WASM_SIMD_STORE_MEM_OFFSET(offset, WASM_LOCAL_GET(param2), WASM_LOCAL_GET(temp2)), WASM_ONE}); FOR_FLOAT64_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; double expected = expected_op(x); #if V8_OS_AIX if (!MightReverseSign(expected_op)) expected = FpOpWorkaround(x, expected); #endif if (!PlatformCanRepresent(expected)) continue; r.builder().WriteMemory(&memory[0], x); r.builder().WriteMemory(&memory[3], x); r.Call(0, 32); CheckDoubleResult(x, x, expected, memory[4]); CheckDoubleResult(x, x, expected, memory[7]); } FOR_FLOAT64_NAN_INPUTS(d) { double x = base::bit_cast(double_nan_test_array[d]); if (!PlatformCanRepresent(x)) continue; double expected = expected_op(x); if (!PlatformCanRepresent(expected)) continue; r.builder().WriteMemory(&memory[0], x); r.builder().WriteMemory(&memory[3], x); r.Call(0, 32); CheckDoubleResult(x, x, expected, memory[4]); CheckDoubleResult(x, x, expected, memory[7]); } } #endif void RunF64x2BinOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, DoubleBinOp expected_op) { WasmRunner r(execution_tier); // Global to hold output. double* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test value, perform binop, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp2, WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); FOR_FLOAT64_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; FOR_FLOAT64_INPUTS(y) { if (!PlatformCanRepresent(x)) continue; if (ShouldSkipTestingConstants(opcode, x, y)) continue; double expected = expected_op(x, y); if (!PlatformCanRepresent(expected)) continue; r.Call(x, y); for (int i = 0; i < 2; i++) { double actual = LANE(g, i); CheckDoubleResult(x, y, expected, actual, true /* exact */); } } } FOR_FLOAT64_NAN_INPUTS(d) { double x = base::bit_cast(double_nan_test_array[d]); if (!PlatformCanRepresent(x)) continue; FOR_FLOAT64_NAN_INPUTS(j) { double y = base::bit_cast(double_nan_test_array[j]); double expected = expected_op(x, y); if (!PlatformCanRepresent(expected)) continue; if (ShouldSkipTestingConstants(opcode, x, y)) continue; r.Call(x, y); for (int i = 0; i < 2; i++) { double actual = LANE(g, i); CheckDoubleResult(x, y, expected, actual, true /* exact */); } } } } void RunF64x2CompareOpTest(TestExecutionTier execution_tier, WasmOpcode opcode, DoubleCompareOp expected_op) { WasmRunner r(execution_tier); // Set up global to hold mask output. int64_t* g = r.builder().AddGlobal(kWasmS128); // Build fn to splat test values, perform compare op, and write the result. uint8_t value1 = 0, value2 = 1; uint8_t temp1 = r.AllocateLocal(kWasmS128); uint8_t temp2 = r.AllocateLocal(kWasmS128); // Make the lanes of each temp compare differently: // temp1 = y, x and temp2 = y, y. r.Build({WASM_LOCAL_SET(temp1, WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value1))), WASM_LOCAL_SET(temp1, WASM_SIMD_F64x2_REPLACE_LANE(1, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(value2))), WASM_LOCAL_SET(temp2, WASM_SIMD_F64x2_SPLAT(WASM_LOCAL_GET(value2))), WASM_GLOBAL_SET(0, WASM_SIMD_BINOP(opcode, WASM_LOCAL_GET(temp1), WASM_LOCAL_GET(temp2))), WASM_ONE}); FOR_FLOAT64_INPUTS(x) { if (!PlatformCanRepresent(x)) continue; FOR_FLOAT64_INPUTS(y) { if (!PlatformCanRepresent(y)) continue; double diff = x - y; // Model comparison as subtraction. if (!PlatformCanRepresent(diff)) continue; r.Call(x, y); int64_t expected0 = expected_op(x, y); int64_t expected1 = expected_op(y, y); CHECK_EQ(expected0, LANE(g, 0)); CHECK_EQ(expected1, LANE(g, 1)); } } } } // namespace wasm } // namespace internal } // namespace v8