[wasm-simd] Move shuffle pattern matching out of instruction-selector

These functions match on specific patterns of shuffle that have more
optimized implementations. Moving them out of instruction-selector
allows us to reuse them in Liftoff. Most of these pattern matching
functions do not depend on InstructionSelector, since they work on byte
arrays. (The only one is CanonicalizeShuffle, which swaps node inputs.)

This is only the first pass of moving those functions out. In particular
we can clean things up more by moving the tests out of
instruction-selector as well. Those will come in follow-up changes.

Bug: v8:10696
Change-Id: I4a4333cd8c0259875a672179e72d34dad5f7a008
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2308057
Commit-Queue: Zhi An Ng <zhin@chromium.org>
Reviewed-by: Bill Budge <bbudge@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69051}
This commit is contained in:
Ng Zhi An 2020-07-23 14:43:18 -07:00 committed by Commit Bot
parent 244916cd18
commit 6bd7549890
5 changed files with 193 additions and 119 deletions

View File

@ -3251,6 +3251,8 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/object-access.h",
"src/wasm/signature-map.cc",
"src/wasm/signature-map.h",
"src/wasm/simd-shuffle.cc",
"src/wasm/simd-shuffle.h",
"src/wasm/streaming-decoder.cc",
"src/wasm/streaming-decoder.h",
"src/wasm/struct-types.h",

View File

@ -17,6 +17,7 @@
#include "src/compiler/schedule.h"
#include "src/compiler/state-values-utils.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/wasm/simd-shuffle.h"
namespace v8 {
namespace internal {
@ -3153,50 +3154,6 @@ FrameStateDescriptor* InstructionSelector::GetFrameStateDescriptor(
return desc;
}
// static
void InstructionSelector::CanonicalizeShuffle(bool inputs_equal,
uint8_t* shuffle,
bool* needs_swap,
bool* is_swizzle) {
*needs_swap = false;
// Inputs equal, then it's a swizzle.
if (inputs_equal) {
*is_swizzle = true;
} else {
// Inputs are distinct; check that both are required.
bool src0_is_used = false;
bool src1_is_used = false;
for (int i = 0; i < kSimd128Size; ++i) {
if (shuffle[i] < kSimd128Size) {
src0_is_used = true;
} else {
src1_is_used = true;
}
}
if (src0_is_used && !src1_is_used) {
*is_swizzle = true;
} else if (src1_is_used && !src0_is_used) {
*needs_swap = true;
*is_swizzle = true;
} else {
*is_swizzle = false;
// Canonicalize general 2 input shuffles so that the first input lanes are
// encountered first. This makes architectural shuffle pattern matching
// easier, since we only need to consider 1 input ordering instead of 2.
if (shuffle[0] >= kSimd128Size) {
// The second operand is used first. Swap inputs and adjust the shuffle.
*needs_swap = true;
for (int i = 0; i < kSimd128Size; ++i) {
shuffle[i] ^= kSimd128Size;
}
}
}
}
if (*is_swizzle) {
for (int i = 0; i < kSimd128Size; ++i) shuffle[i] &= kSimd128Size - 1;
}
}
void InstructionSelector::CanonicalizeShuffle(Node* node, uint8_t* shuffle,
bool* is_swizzle) {
// Get raw shuffle indices.
@ -3204,7 +3161,7 @@ void InstructionSelector::CanonicalizeShuffle(Node* node, uint8_t* shuffle,
bool needs_swap;
bool inputs_equal = GetVirtualRegister(node->InputAt(0)) ==
GetVirtualRegister(node->InputAt(1));
CanonicalizeShuffle(inputs_equal, shuffle, &needs_swap, is_swizzle);
wasm::CanonicalizeShuffle(inputs_equal, shuffle, &needs_swap, is_swizzle);
if (needs_swap) {
SwapShuffleInputs(node);
}
@ -3223,65 +3180,38 @@ void InstructionSelector::SwapShuffleInputs(Node* node) {
node->ReplaceInput(1, input0);
}
// static
bool InstructionSelector::TryMatchIdentity(const uint8_t* shuffle) {
for (int i = 0; i < kSimd128Size; ++i) {
if (shuffle[i] != i) return false;
void InstructionSelector::CanonicalizeShuffleForTesting(bool inputs_equal,
uint8_t* shuffle,
bool* needs_swap,
bool* is_swizzle) {
wasm::CanonicalizeShuffle(inputs_equal, shuffle, needs_swap, is_swizzle);
}
return true;
bool InstructionSelector::TryMatchIdentityForTesting(const uint8_t* shuffle) {
return TryMatchIdentity(shuffle);
}
// static
bool InstructionSelector::TryMatch32x4Shuffle(const uint8_t* shuffle,
uint8_t* shuffle32x4) {
for (int i = 0; i < 4; ++i) {
if (shuffle[i * 4] % 4 != 0) return false;
for (int j = 1; j < 4; ++j) {
if (shuffle[i * 4 + j] - shuffle[i * 4 + j - 1] != 1) return false;
}
shuffle32x4[i] = shuffle[i * 4] / 4;
}
return true;
return wasm::TryMatch32x4Shuffle(shuffle, shuffle32x4);
}
// static
bool InstructionSelector::TryMatch16x8Shuffle(const uint8_t* shuffle,
uint8_t* shuffle16x8) {
for (int i = 0; i < 8; ++i) {
if (shuffle[i * 2] % 2 != 0) return false;
for (int j = 1; j < 2; ++j) {
if (shuffle[i * 2 + j] - shuffle[i * 2 + j - 1] != 1) return false;
}
shuffle16x8[i] = shuffle[i * 2] / 2;
}
return true;
return wasm::TryMatch16x8Shuffle(shuffle, shuffle16x8);
}
// static
bool InstructionSelector::TryMatchConcat(const uint8_t* shuffle,
uint8_t* offset) {
// Don't match the identity shuffle (e.g. [0 1 2 ... 15]).
uint8_t start = shuffle[0];
if (start == 0) return false;
DCHECK_GT(kSimd128Size, start); // The shuffle should be canonicalized.
// A concatenation is a series of consecutive indices, with at most one jump
// in the middle from the last lane to the first.
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;
return wasm::TryMatchConcat(shuffle, offset);
}
// static
bool InstructionSelector::TryMatchBlend(const uint8_t* shuffle) {
for (int i = 0; i < 16; ++i) {
if ((shuffle[i] & 0xF) != i) return false;
}
return true;
return wasm::TryMatchBlend(shuffle);
}
// static

View File

@ -16,6 +16,7 @@
#include "src/compiler/linkage.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "src/wasm/simd-shuffle.h"
#include "src/zone/zone-containers.h"
namespace v8 {
@ -457,14 +458,9 @@ class V8_EXPORT_PRIVATE InstructionSelector final {
// Expose these SIMD helper functions for testing.
static void CanonicalizeShuffleForTesting(bool inputs_equal, uint8_t* shuffle,
bool* needs_swap,
bool* is_swizzle) {
CanonicalizeShuffle(inputs_equal, shuffle, needs_swap, is_swizzle);
}
bool* needs_swap, bool* is_swizzle);
static bool TryMatchIdentityForTesting(const uint8_t* shuffle) {
return TryMatchIdentity(shuffle);
}
static bool TryMatchIdentityForTesting(const uint8_t* shuffle);
template <int LANES>
static bool TryMatchDupForTesting(const uint8_t* shuffle, int* index) {
return TryMatchDup<LANES>(shuffle, index);
@ -657,14 +653,6 @@ class V8_EXPORT_PRIVATE InstructionSelector final {
// ============= Vector instruction (SIMD) helper fns. =======================
// ===========================================================================
// Converts a shuffle into canonical form, meaning that the first lane index
// is in the range [0 .. 15]. Set |inputs_equal| true if this is an explicit
// swizzle. Returns canonicalized |shuffle|, |needs_swap|, and |is_swizzle|.
// If |needs_swap| is true, inputs must be swapped. If |is_swizzle| is true,
// the second input can be ignored.
static void CanonicalizeShuffle(bool inputs_equal, uint8_t* shuffle,
bool* needs_swap, bool* is_swizzle);
// Canonicalize shuffles to make pattern matching simpler. Returns the shuffle
// indices, and a boolean indicating if the shuffle is a swizzle (one input).
void CanonicalizeShuffle(Node* node, uint8_t* shuffle, bool* is_swizzle);
@ -676,30 +664,15 @@ class V8_EXPORT_PRIVATE InstructionSelector final {
// Tries to match an 8x16 byte shuffle to the identity shuffle, which is
// [0 1 ... 15]. This should be called after canonicalizing the shuffle, so
// the second identity shuffle, [16 17 .. 31] is converted to the first one.
static bool TryMatchIdentity(const uint8_t* shuffle);
static bool TryMatchIdentity(const uint8_t* shuffle) {
return wasm::TryMatchIdentity(shuffle);
}
// Tries to match a byte shuffle to a scalar splat operation. Returns the
// index of the lane if successful.
template <int LANES>
static bool TryMatchDup(const uint8_t* shuffle, int* index) {
const int kBytesPerLane = kSimd128Size / LANES;
// Get the first lane's worth of bytes and check that indices start at a
// lane boundary and are consecutive.
uint8_t lane0[kBytesPerLane];
lane0[0] = shuffle[0];
if (lane0[0] % kBytesPerLane != 0) return false;
for (int i = 1; i < kBytesPerLane; ++i) {
lane0[i] = shuffle[i];
if (lane0[i] != lane0[0] + i) return false;
}
// Now check that the other lanes are identical to lane0.
for (int i = 1; i < LANES; ++i) {
for (int j = 0; j < kBytesPerLane; ++j) {
if (lane0[j] != shuffle[i * kBytesPerLane + j]) return false;
}
}
*index = lane0[0] / kBytesPerLane;
return true;
return wasm::TryMatchSplat<LANES>(shuffle, index);
}
// Tries to match an 8x16 byte shuffle to an equivalent 32x4 shuffle. If

109
src/wasm/simd-shuffle.cc Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2020 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 "src/wasm/simd-shuffle.h"
#include "src/common/globals.h"
namespace v8 {
namespace internal {
namespace wasm {
void CanonicalizeShuffle(bool inputs_equal, uint8_t* shuffle, bool* needs_swap,
bool* is_swizzle) {
*needs_swap = false;
// Inputs equal, then it's a swizzle.
if (inputs_equal) {
*is_swizzle = true;
} else {
// Inputs are distinct; check that both are required.
bool src0_is_used = false;
bool src1_is_used = false;
for (int i = 0; i < kSimd128Size; ++i) {
if (shuffle[i] < kSimd128Size) {
src0_is_used = true;
} else {
src1_is_used = true;
}
}
if (src0_is_used && !src1_is_used) {
*is_swizzle = true;
} else if (src1_is_used && !src0_is_used) {
*needs_swap = true;
*is_swizzle = true;
} else {
*is_swizzle = false;
// Canonicalize general 2 input shuffles so that the first input lanes are
// encountered first. This makes architectural shuffle pattern matching
// easier, since we only need to consider 1 input ordering instead of 2.
if (shuffle[0] >= kSimd128Size) {
// The second operand is used first. Swap inputs and adjust the shuffle.
*needs_swap = true;
for (int i = 0; i < kSimd128Size; ++i) {
shuffle[i] ^= kSimd128Size;
}
}
}
}
if (*is_swizzle) {
for (int i = 0; i < kSimd128Size; ++i) shuffle[i] &= kSimd128Size - 1;
}
}
bool TryMatchIdentity(const uint8_t* shuffle) {
for (int i = 0; i < kSimd128Size; ++i) {
if (shuffle[i] != i) return false;
}
return true;
}
bool TryMatch32x4Shuffle(const uint8_t* shuffle, uint8_t* shuffle32x4) {
for (int i = 0; i < 4; ++i) {
if (shuffle[i * 4] % 4 != 0) return false;
for (int j = 1; j < 4; ++j) {
if (shuffle[i * 4 + j] - shuffle[i * 4 + j - 1] != 1) return false;
}
shuffle32x4[i] = shuffle[i * 4] / 4;
}
return true;
}
bool TryMatch16x8Shuffle(const uint8_t* shuffle, uint8_t* shuffle16x8) {
for (int i = 0; i < 8; ++i) {
if (shuffle[i * 2] % 2 != 0) return false;
for (int j = 1; j < 2; ++j) {
if (shuffle[i * 2 + j] - shuffle[i * 2 + j - 1] != 1) return false;
}
shuffle16x8[i] = shuffle[i * 2] / 2;
}
return true;
}
bool TryMatchConcat(const uint8_t* shuffle, uint8_t* offset) {
// Don't match the identity shuffle (e.g. [0 1 2 ... 15]).
uint8_t start = shuffle[0];
if (start == 0) return false;
DCHECK_GT(kSimd128Size, start); // The shuffle should be canonicalized.
// A concatenation is a series of consecutive indices, with at most one jump
// in the middle from the last lane to the first.
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 TryMatchBlend(const uint8_t* shuffle) {
for (int i = 0; i < 16; ++i) {
if ((shuffle[i] & 0xF) != i) return false;
}
return true;
}
} // namespace wasm
} // namespace internal
} // namespace v8

60
src/wasm/simd-shuffle.h Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2020 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.
#ifndef V8_WASM_SIMD_SHUFFLE_H_
#define V8_WASM_SIMD_SHUFFLE_H_
#include "src/common/globals.h"
namespace v8 {
namespace internal {
namespace wasm {
// Converts a shuffle into canonical form, meaning that the first lane index
// is in the range [0 .. 15]. Set |inputs_equal| true if this is an explicit
// swizzle. Returns canonicalized |shuffle|, |needs_swap|, and |is_swizzle|.
// If |needs_swap| is true, inputs must be swapped. If |is_swizzle| is true,
// the second input can be ignored.
void CanonicalizeShuffle(bool inputs_equal, uint8_t* shuffle, bool* needs_swap,
bool* is_swizzle);
bool TryMatchIdentity(const uint8_t* shuffle);
// Tries to match a byte shuffle to a scalar splat operation. Returns the
// index of the lane if successful.
template <int LANES>
bool TryMatchSplat(const uint8_t* shuffle, int* index) {
const int kBytesPerLane = kSimd128Size / LANES;
// Get the first lane's worth of bytes and check that indices start at a
// lane boundary and are consecutive.
uint8_t lane0[kBytesPerLane];
lane0[0] = shuffle[0];
if (lane0[0] % kBytesPerLane != 0) return false;
for (int i = 1; i < kBytesPerLane; ++i) {
lane0[i] = shuffle[i];
if (lane0[i] != lane0[0] + i) return false;
}
// Now check that the other lanes are identical to lane0.
for (int i = 1; i < LANES; ++i) {
for (int j = 0; j < kBytesPerLane; ++j) {
if (lane0[j] != shuffle[i * kBytesPerLane + j]) return false;
}
}
*index = lane0[0] / kBytesPerLane;
return true;
}
bool TryMatch32x4Shuffle(const uint8_t* shuffle, uint8_t* shuffle32x4);
bool TryMatch16x8Shuffle(const uint8_t* shuffle, uint8_t* shuffle16x8);
bool TryMatchConcat(const uint8_t* shuffle, uint8_t* offset);
bool TryMatchBlend(const uint8_t* shuffle);
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_SIMD_SHUFFLE_H_