#include "src/wasm/simd-shuffle.h"
#include <algorithm>
#include "src/common/globals.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace {
static constexpr uint8_t kShuffleSentinel = 0xFF;
template <size_t N, int kActiveBytes = kSimd128Size>
requires((kActiveBytes == kSimd128Size || kActiveBytes == kSimd128HalfSize) &&
kActiveBytes % N == 0)
constexpr SimdShuffle::ShuffleArray<kSimd128Size> expand(
const std::array<uint8_t, N>& in) {
SimdShuffle::ShuffleArray<kSimd128Size> res{};
constexpr std::size_t lane_bytes = kActiveBytes / N;
for (unsigned i = 0; i < N; ++i) {
for (unsigned j = 0; j < lane_bytes; ++j) {
res[i * lane_bytes + j] = lane_bytes * in[i] + j;
}
}
for (unsigned i = kActiveBytes; i < kSimd128Size; ++i) {
res[i] = kShuffleSentinel;
}
return res;
}
template <size_t N>
constexpr SimdShuffle::ShuffleArray<kSimd128Size> expandHalf(
const std::array<uint8_t, N>& in) {
return expand<N, kSimd128HalfSize>(in);
}
template <size_t N>
requires(N == kSimd128HalfSize || N == kSimd128Size)
SimdShuffle::CanonicalShuffle TryMatchCanonicalImpl(
const SimdShuffle::ShuffleArray<N>& shuffle) {
using CanonicalShuffle = SimdShuffle::CanonicalShuffle;
using CanonicalPair = std::pair<const SimdShuffle::ShuffleArray<kSimd128Size>,
const CanonicalShuffle>;
static constexpr auto canonical_shuffle_list = std::to_array<CanonicalPair>({
{expand<2>({0, 1}), CanonicalShuffle::kIdentity},
{expand<2>({0, 2}), CanonicalShuffle::kS64x2Even},
{expand<2>({1, 3}), CanonicalShuffle::kS64x2Odd},
{expand<2>({1, 0}), CanonicalShuffle::kS64x2Reverse},
{expand<4>({0, 2, 4, 6}), CanonicalShuffle::kS32x4Even},
{expand<4>({1, 3, 5, 7}), CanonicalShuffle::kS32x4Odd},
{expand<4>({0, 4, 1, 5}), CanonicalShuffle::kS32x4InterleaveLowHalves},
{expand<4>({2, 6, 3, 7}), CanonicalShuffle::kS32x4InterleaveHighHalves},
{expand<4>({3, 2, 1, 0}), CanonicalShuffle::kS32x4Reverse},
{expand<4>({0, 4, 2, 6}), CanonicalShuffle::kS32x4TransposeEven},
{expand<4>({1, 5, 3, 7}), CanonicalShuffle::kS32x4TransposeOdd},
{expand<4>({1, 0, 3, 2}), CanonicalShuffle::kS32x2Reverse},
{expandHalf<4>({0, 2, 8, 10}), CanonicalShuffle::kS16x4Even},
{expandHalf<4>({1, 3, 9, 11}), CanonicalShuffle::kS16x4Odd},
{expand<8>({0, 2, 4, 6, 8, 10, 12, 14}), CanonicalShuffle::kS16x8Even},
{expand<8>({1, 3, 5, 7, 9, 11, 13, 15}), CanonicalShuffle::kS16x8Odd},
{expand<8>({0, 8, 1, 9, 2, 10, 3, 11}),
CanonicalShuffle::kS16x8InterleaveLowHalves},
{expandHalf<4>({2, 10, 3, 11}),
CanonicalShuffle::kS16x4InterleaveHighHalves},
{expand<8>({4, 12, 5, 13, 6, 14, 7, 15}),
CanonicalShuffle::kS16x8InterleaveHighHalves},
{expand<8>({0, 8, 2, 10, 4, 12, 6, 14}),
CanonicalShuffle::kS16x8TransposeEven},
{expand<8>({1, 9, 3, 11, 5, 13, 7, 15}),
CanonicalShuffle::kS16x8TransposeOdd},
{expand<8>({1, 0, 3, 2, 5, 4, 7, 6}), CanonicalShuffle::kS16x2Reverse},
{expand<8>({3, 2, 1, 0, 7, 6, 5, 4}), CanonicalShuffle::kS16x4Reverse},
{{7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8},
CanonicalShuffle::kS64x2ReverseBytes},
{{3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12},
CanonicalShuffle::kS32x4ReverseBytes},
{{1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14},
CanonicalShuffle::kS16x8ReverseBytes},
{{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23},
CanonicalShuffle::kS8x16InterleaveLowHalves},
{expandHalf<8>({4, 20, 5, 21, 6, 22, 7, 23}),
CanonicalShuffle::kS8x8InterleaveHighHalves},
{{8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31},
CanonicalShuffle::kS8x16InterleaveHighHalves},
{expandHalf<8>({0, 2, 4, 6, 16, 18, 20, 22}),
CanonicalShuffle::kS8x8Even},
{expandHalf<8>({1, 3, 5, 7, 17, 19, 21, 23}), CanonicalShuffle::kS8x8Odd},
{{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30},
CanonicalShuffle::kS8x16Even},
{{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31},
CanonicalShuffle::kS8x16Odd},
{{0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30},
CanonicalShuffle::kS8x16TransposeEven},
{{1, 17, 3, 19, 5, 21, 7, 23, 9, 25, 11, 27, 13, 29, 15, 31},
CanonicalShuffle::kS8x16TransposeOdd},
{{0, 4, 8, 12, 16, 20, 24, 28},
CanonicalShuffle::kS8x8DeinterleaveEvenEven},
{{1, 5, 9, 13, 17, 21, 25, 29},
CanonicalShuffle::kS8x8DeinterleaveOddEven},
{{2, 6, 10, 14, 18, 22, 26, 30},
CanonicalShuffle::kS8x8DeinterleaveEvenOdd},
{{3, 7, 11, 15, 19, 23, 27, 31},
CanonicalShuffle::kS8x8DeinterleaveOddOdd},
});
for (const auto& [lanes, canonical] : canonical_shuffle_list) {
if (std::equal(lanes.begin(), lanes.begin() + N, shuffle.begin())) {
return canonical;
}
}
return CanonicalShuffle::kUnknown;
}
}
SimdShuffle::CanonicalShuffle SimdShuffle::TryMatchCanonical(
const ShuffleArray<kSimd128Size>& shuffle) {
return TryMatchCanonicalImpl<kSimd128Size>(shuffle);
}
SimdShuffle::CanonicalShuffle SimdShuffle::TryMatchCanonical(
const ShuffleArray<kSimd128HalfSize>& shuffle) {
return TryMatchCanonicalImpl<kSimd128HalfSize>(shuffle);
}
bool SimdShuffle::TryMatchIdentity(const uint8_t* shuffle) {
for (int i = 0; i < kSimd128Size; ++i) {
if (shuffle[i] != i) return false;
}
return true;
}
bool SimdShuffle::TryMatch32x4Rotate(const uint8_t* shuffle,
uint8_t* shuffle32x4, bool is_swizzle) {
uint8_t offset;
bool is_concat = TryMatchConcat(shuffle, &offset);
DCHECK_NE(offset, 0);
if (!is_concat || !is_swizzle || offset % 4 != 0) {
return false;
}
uint8_t offset_32 = offset / 4;
for (int i = 0; i < 4; i++) {
shuffle32x4[i] = (offset_32 + i) % 4;
}
return true;
}
bool SimdShuffle::TryMatch32x4Reverse(const uint8_t* shuffle32x4) {
return shuffle32x4[0] == 3 && shuffle32x4[1] == 2 && shuffle32x4[2] == 1 &&
shuffle32x4[3] == 0;
}
bool SimdShuffle::TryMatch32x4OneLaneSwizzle(const uint8_t* shuffle32x4,
uint8_t* from_lane,
uint8_t* to_lane) {
constexpr uint32_t patterns[12]{
0x30200000,
0x30000100,
0x00020100,
0x03020101,
0x03010100,
0x01020100,
0x03020102,
0x03020200,
0x02020100,
0x03020103,
0x03020300,
0x03030100
};
unsigned pattern_idx = 0;
uint32_t shuffle = *reinterpret_cast<const uint32_t*>(shuffle32x4);
#ifdef V8_TARGET_BIG_ENDIAN
shuffle = base::bits::ReverseBytes(shuffle);
#endif
for (unsigned from = 0; from < 4; ++from) {
for (unsigned to = 0; to < 4; ++to) {
if (from == to) {
continue;
}
if (shuffle == patterns[pattern_idx]) {
*from_lane = from;
*to_lane = to;
return true;
}
++pattern_idx;
}
}
return false;
}
bool SimdShuffle::TryMatch64x2Shuffle(const uint8_t* shuffle,
uint8_t* shuffle64x2) {
constexpr std::array<std::array<uint8_t, 8>, 4> element_patterns = {
{{0, 1, 2, 3, 4, 5, 6, 7},
{8, 9, 10, 11, 12, 13, 14, 15},
{16, 17, 18, 19, 20, 21, 22, 23},
{24, 25, 26, 27, 28, 29, 30, 31}}};
for (unsigned i = 0; i < 2; ++i) {
uint64_t element = *reinterpret_cast<const uint64_t*>(&shuffle[i * 8]);
for (unsigned j = 0; j < 4; ++j) {
uint64_t pattern =
*reinterpret_cast<const uint64_t*>(element_patterns[j].data());
if (pattern == element) {
shuffle64x2[i] = j;
break;
}
if (j == 3) {
return false;
}
}
}
return true;
}
template <int kLanes, int kLaneBytes>
bool MatchHelper(const uint8_t* input, uint8_t* output) {
for (int i = 0; i < kLanes; ++i) {
if (input[i * kLaneBytes] % kLaneBytes != 0) return false;
for (int j = 1; j < kLaneBytes; ++j) {
if (input[i * kLaneBytes + j] - input[i * kLaneBytes + j - 1] != 1)
return false;
}
output[i] = input[i * kLaneBytes] / kLaneBytes;
}
return true;
}
bool SimdShuffle::TryMatch64x1Shuffle(const uint8_t* shuffle,
uint8_t* shuffle64x1) {
return MatchHelper<1, 8>(shuffle, shuffle64x1);
}
bool SimdShuffle::TryMatch32x1Shuffle(const uint8_t* shuffle,
uint8_t* shuffle32x1) {
return MatchHelper<1, 4>(shuffle, shuffle32x1);
}
bool SimdShuffle::TryMatch32x2Shuffle(const uint8_t* shuffle,
uint8_t* shuffle32x2) {
return MatchHelper<2, 4>(shuffle, shuffle32x2);
}
bool SimdShuffle::TryMatch32x4Shuffle(const uint8_t* shuffle,
uint8_t* shuffle32x4) {
return MatchHelper<4, 4>(shuffle, shuffle32x4);
}
bool SimdShuffle::TryMatch32x8Shuffle(const uint8_t* shuffle,
uint8_t* shuffle32x8) {
return MatchHelper<8, 4>(shuffle, shuffle32x8);
}
bool SimdShuffle::TryMatch16x1Shuffle(const uint8_t* shuffle,
uint8_t* shuffle16x1) {
return MatchHelper<1, 2>(shuffle, shuffle16x1);
}
bool SimdShuffle::TryMatch16x2Shuffle(const uint8_t* shuffle,
uint8_t* shuffle16x2) {
return MatchHelper<2, 2>(shuffle, shuffle16x2);
}
bool SimdShuffle::TryMatch16x4Shuffle(const uint8_t* shuffle,
uint8_t* shuffle16x4) {
return MatchHelper<4, 2>(shuffle, shuffle16x4);
}
bool SimdShuffle::TryMatch16x8Shuffle(const uint8_t* shuffle,
uint8_t* shuffle16x8) {
return MatchHelper<8, 2>(shuffle, shuffle16x8);
}
bool SimdShuffle::TryMatchConcat(const uint8_t* shuffle, uint8_t* offset) {
uint8_t start = shuffle[0];
if (start == 0) return false;
DCHECK_GT(kSimd128Size, start);
for (int i = 1; i < kSimd128Size; ++i) {
if ((shuffle[i]) != ((shuffle[i - 1] + 1))) {
if (shuffle[i - 1] != 15) return false;
if (shuffle[i] % kSimd128Size != 0) return false;
}
}
*offset = start;
return true;
}
bool SimdShuffle::TryMatchBlend(const uint8_t* shuffle) {
for (int i = 0; i < 16; ++i) {
if ((shuffle[i] & 0xF) != i) return false;
}
return true;
}
bool SimdShuffle::TryMatchByteToDwordZeroExtend(const uint8_t* shuffle) {
for (int i = 0; i < 16; ++i) {
if ((i % 4 != 0) && (shuffle[i] < 16)) return false;
if ((i % 4 == 0) && (shuffle[i] > 15 || (shuffle[i] != shuffle[0] + i / 4)))
return false;
}
return true;
}
namespace {
bool TryMatch32x4Pairwise(const uint8_t* shuffle) {
constexpr uint8_t low_pattern_arr[4] = {4, 5, 6, 7};
uint32_t low_shuffle = reinterpret_cast<const uint32_t*>(shuffle)[0];
constexpr uint8_t high_pattern_arr[4] = {12, 13, 14, 15};
uint32_t high_shuffle = reinterpret_cast<const uint32_t*>(shuffle)[2];
uint32_t low_pattern = *reinterpret_cast<const uint32_t*>(low_pattern_arr);
uint32_t high_pattern = *reinterpret_cast<const uint32_t*>(high_pattern_arr);
return low_shuffle == low_pattern && high_shuffle == high_pattern;
}
bool TryMatch32x2Pairwise(const uint8_t* shuffle) {
constexpr uint8_t pattern_arr[4] = {8, 9, 10, 11};
uint32_t low_shuffle = reinterpret_cast<const uint32_t*>(shuffle)[0];
uint32_t pattern = *reinterpret_cast<const uint32_t*>(pattern_arr);
return low_shuffle == pattern;
}
bool TryMatchUpperToLowerFirst(const uint8_t* shuffle) {
constexpr uint8_t low_pattern_arr[8] = {8, 9, 10, 11, 12, 13, 14, 15};
uint64_t low_shuffle = reinterpret_cast<const uint64_t*>(shuffle)[0];
uint64_t low_pattern = *reinterpret_cast<const uint64_t*>(low_pattern_arr);
return low_shuffle == low_pattern;
}
bool TryMatchUpperToLowerSecond(const uint8_t* shuffle) {
constexpr uint8_t low_pattern_arr[4] = {4, 5, 6, 7};
uint32_t low_shuffle = reinterpret_cast<const uint32_t*>(shuffle)[0];
uint32_t low_pattern = *reinterpret_cast<const uint32_t*>(low_pattern_arr);
return low_shuffle == low_pattern;
}
bool TryMatchUpperToLowerThird(const uint8_t* shuffle) {
constexpr uint8_t low_pattern_arr[2] = {2, 3};
uint16_t low_shuffle = reinterpret_cast<const uint16_t*>(shuffle)[0];
uint16_t low_pattern = *reinterpret_cast<const uint16_t*>(low_pattern_arr);
return low_shuffle == low_pattern;
}
bool TryMatchUpperToLowerFourth(const uint8_t* shuffle) {
return shuffle[0] == 1;
}
}
bool SimdShuffle::TryMatch8x16UpperToLowerReduce(const uint8_t* shuffle1,
const uint8_t* shuffle2,
const uint8_t* shuffle3,
const uint8_t* shuffle4) {
return TryMatchUpperToLowerFirst(shuffle1) &&
TryMatchUpperToLowerSecond(shuffle2) &&
TryMatchUpperToLowerThird(shuffle3) &&
TryMatchUpperToLowerFourth(shuffle4);
}
bool SimdShuffle::TryMatch16x8UpperToLowerReduce(const uint8_t* shuffle1,
const uint8_t* shuffle2,
const uint8_t* shuffle3) {
return TryMatchUpperToLowerFirst(shuffle1) &&
TryMatchUpperToLowerSecond(shuffle2) &&
TryMatchUpperToLowerThird(shuffle3);
}
bool SimdShuffle::TryMatch32x4UpperToLowerReduce(const uint8_t* shuffle1,
const uint8_t* shuffle2) {
return TryMatchUpperToLowerFirst(shuffle1) &&
TryMatchUpperToLowerSecond(shuffle2);
}
bool SimdShuffle::TryMatch32x4PairwiseReduce(const uint8_t* shuffle1,
const uint8_t* shuffle2) {
return TryMatch32x4Pairwise(shuffle1) && TryMatch32x2Pairwise(shuffle2);
}
bool SimdShuffle::TryMatch64x2Reduce(const uint8_t* shuffle64x2) {
return shuffle64x2[0] == 1;
}
uint8_t SimdShuffle::PackShuffle4(uint8_t* shuffle) {
return (shuffle[0] & 3) | ((shuffle[1] & 3) << 2) | ((shuffle[2] & 3) << 4) |
((shuffle[3] & 3) << 6);
}
uint8_t SimdShuffle::PackBlend8(const uint8_t* shuffle16x8) {
int8_t result = 0;
for (int i = 0; i < 8; ++i) {
result |= (shuffle16x8[i] >= 8 ? 1 : 0) << i;
}
return result;
}
uint8_t SimdShuffle::PackBlend4(const uint8_t* shuffle32x4) {
int8_t result = 0;
for (int i = 0; i < 4; ++i) {
result |= (shuffle32x4[i] >= 4 ? 0x3 : 0) << (i * 2);
}
return result;
}
int32_t SimdShuffle::Pack2Lanes(const std::array<uint8_t, 2>& shuffle) {
int32_t result = 0;
for (int i = 1; i >= 0; --i) {
result <<= 8;
result |= shuffle[i];
}
return result;
}
int32_t SimdShuffle::Pack4Lanes(const uint8_t* shuffle) {
int32_t result = 0;
for (int i = 3; i >= 0; --i) {
result <<= 8;
result |= shuffle[i];
}
return result;
}
void SimdShuffle::Pack16Lanes(uint32_t* dst, const uint8_t* shuffle) {
for (int i = 0; i < 4; i++) {
dst[i] = wasm::SimdShuffle::Pack4Lanes(shuffle + (i * 4));
}
}
#ifdef V8_TARGET_ARCH_X64
bool SimdShuffle::TryMatchVpshufd(const uint8_t* shuffle32x8,
uint8_t* control) {
*control = 0;
for (int i = 0; i < 4; ++i) {
uint8_t mask;
if (shuffle32x8[i] < 4 && shuffle32x8[i + 4] - shuffle32x8[i] == 4) {
mask = shuffle32x8[i];
*control |= mask << (2 * i);
continue;
}
return false;
}
return true;
}
bool SimdShuffle::TryMatchShufps256(const uint8_t* shuffle32x8,
uint8_t* control) {
*control = 0;
for (int i = 0; i < 4; ++i) {
if (shuffle32x8[i + 4] - shuffle32x8[i] == 4) {
if ((i < 2 && shuffle32x8[i] < 4) ||
(i >= 2 && shuffle32x8[i] >= 8 && shuffle32x8[i] < 12)) {
*control |= (shuffle32x8[i] % 4) << (2 * i);
continue;
}
return false;
}
return false;
}
return true;
}
#endif
bool SimdSwizzle::AllInRangeOrTopBitSet(
std::array<uint8_t, kSimd128Size> shuffle) {
return std::all_of(shuffle.begin(), shuffle.end(),
[](auto i) { return (i < kSimd128Size) || (i & 0x80); });
}
}
}
}