Polyfill GroupSse2Impl on non-x86 platforms
Bug: v8:12518 Change-Id: Ie22303416749affc0629d60fbed6f9dc4288b09d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3494443 Reviewed-by: Igor Sheludko <ishell@chromium.org> Commit-Queue: David Benjamin <davidben@chromium.org> Cr-Commit-Position: refs/heads/main@{#79834}
This commit is contained in:
parent
1ac9280d50
commit
6cf7330a61
@ -17,51 +17,50 @@
|
||||
#ifndef V8_OBJECTS_SWISS_HASH_TABLE_HELPERS_H_
|
||||
#define V8_OBJECTS_SWISS_HASH_TABLE_HELPERS_H_
|
||||
|
||||
// The following #defines are taken from Abseil's have_sse.h (but renamed). They
|
||||
// are only defined within this file. However, we also take cross platform
|
||||
// snapshot creation into account, by only using SSE if the target supports it,
|
||||
// too. The SSE implementation uses a group width of 16, whereas the non-SSE
|
||||
// version uses 8. We therefore have to avoid building a snapshot that contains
|
||||
// Swiss Tables with one group size and use it in code that excepts a different
|
||||
// group size.
|
||||
#ifndef SWISS_TABLE_HAVE_SSE2
|
||||
#if (defined(__SSE2__) || \
|
||||
(defined(_MSC_VER) && \
|
||||
(defined(_M_X64) || (defined(_M_IX86) && _M_IX86_FP >= 2)))) && \
|
||||
(defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64))
|
||||
#define SWISS_TABLE_HAVE_SSE2 1
|
||||
// The following #defines are taken from Abseil's have_sse.h (but renamed).
|
||||
#ifndef V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
#if (defined(__SSE2__) || \
|
||||
(defined(_MSC_VER) && \
|
||||
(defined(_M_X64) || (defined(_M_IX86) && _M_IX86_FP >= 2))))
|
||||
#define V8_SWISS_TABLE_HAVE_SSE2_HOST 1
|
||||
#else
|
||||
#define SWISS_TABLE_HAVE_SSE2 0
|
||||
#if defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64)
|
||||
// TODO(v8:11388) Currently, building on a non-SSE platform for a SSE target
|
||||
// means that we cannot use the (more performant) SSE implementations of Swiss
|
||||
// Tables, even if the target would support it, just because the host doesn't.
|
||||
// This is due to the difference in group sizes (see comment at the beginning of
|
||||
// the file). We can solve this by implementating a new non-SSE Group that
|
||||
// behaves like GroupSse2Impl (and uses group size 16) in the future.
|
||||
#warning "You should avoid building on a non-SSE platform for a SSE target!"
|
||||
#endif
|
||||
#define V8_SWISS_TABLE_HAVE_SSE2_HOST 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef SWISS_TABLE_HAVE_SSSE3
|
||||
#if defined(__SSSE3__) && \
|
||||
(defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_X64))
|
||||
#define SWISS_TABLE_HAVE_SSSE3 1
|
||||
#ifndef V8_SWISS_TABLE_HAVE_SSSE3_HOST
|
||||
#if defined(__SSSE3__)
|
||||
#define V8_SWISS_TABLE_HAVE_SSSE3_HOST 1
|
||||
#else
|
||||
#define SWISS_TABLE_HAVE_SSSE3 0
|
||||
#define V8_SWISS_TABLE_HAVE_SSSE3_HOST 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if SWISS_TABLE_HAVE_SSSE3 && !SWISS_TABLE_HAVE_SSE2
|
||||
#if V8_SWISS_TABLE_HAVE_SSSE3_HOST && !V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
#error "Bad configuration!"
|
||||
#endif
|
||||
|
||||
#if SWISS_TABLE_HAVE_SSE2
|
||||
// Unlike Abseil, we cannot select SSE purely by host capabilities. When
|
||||
// creating a snapshot, the group width must be compatible. The SSE
|
||||
// implementation uses a group width of 16, whereas the non-SSE version uses 8.
|
||||
// Thus we select the group size based on target capabilities and, if the host
|
||||
// does not match, select a polyfill implementation. This means, in supported
|
||||
// cross-compiling configurations, we must be able to determine matching target
|
||||
// capabilities from the host.
|
||||
#ifndef V8_SWISS_TABLE_HAVE_SSE2_TARGET
|
||||
#if V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64
|
||||
// x64 always has SSE2, and ia32 without SSE2 is not supported by V8.
|
||||
#define V8_SWISS_TABLE_HAVE_SSE2_TARGET 1
|
||||
#else
|
||||
#define V8_SWISS_TABLE_HAVE_SSE2_TARGET 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
#if SWISS_TABLE_HAVE_SSSE3
|
||||
#if V8_SWISS_TABLE_HAVE_SSSE3_HOST
|
||||
#include <tmmintrin.h>
|
||||
#endif
|
||||
|
||||
@ -126,9 +125,9 @@ class BitMask {
|
||||
|
||||
public:
|
||||
// These are useful for unit tests (gunit).
|
||||
// using value_type = int;
|
||||
// using iterator = BitMask;
|
||||
// using const_iterator = BitMask;
|
||||
using value_type = int;
|
||||
using iterator = BitMask;
|
||||
using const_iterator = BitMask;
|
||||
|
||||
explicit BitMask(T mask) : mask_(mask) {}
|
||||
BitMask& operator++() {
|
||||
@ -219,7 +218,7 @@ inline static swiss_table::ctrl_t H2(uint32_t hash) {
|
||||
return hash & ((1 << kH2Bits) - 1);
|
||||
}
|
||||
|
||||
#if SWISS_TABLE_HAVE_SSE2
|
||||
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
// https://github.com/abseil/abseil-cpp/issues/209
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87853
|
||||
// _mm_cmpgt_epi8 is broken under GCC with -funsigned-char
|
||||
@ -252,7 +251,7 @@ struct GroupSse2Impl {
|
||||
|
||||
// Returns a bitmask representing the positions of empty slots.
|
||||
BitMask<uint32_t, kWidth> MatchEmpty() const {
|
||||
#if SWISS_TABLE_HAVE_SSSE3
|
||||
#if V8_SWISS_TABLE_HAVE_SSSE3_HOST
|
||||
// This only works because kEmpty is -128.
|
||||
return BitMask<uint32_t, kWidth>(
|
||||
_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)));
|
||||
@ -278,7 +277,7 @@ struct GroupSse2Impl {
|
||||
void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
|
||||
auto msbs = _mm_set1_epi8(static_cast<char>(-128));
|
||||
auto x126 = _mm_set1_epi8(126);
|
||||
#if SWISS_TABLE_HAVE_SSSE3
|
||||
#if V8_SWISS_TABLE_HAVE_SSSE3_HOST
|
||||
auto res = _mm_or_si128(_mm_shuffle_epi8(x126, ctrl), msbs);
|
||||
#else
|
||||
auto zero = _mm_setzero_si128();
|
||||
@ -290,7 +289,64 @@ struct GroupSse2Impl {
|
||||
|
||||
__m128i ctrl;
|
||||
};
|
||||
#endif // SWISS_TABLE_HAVE_SSE2
|
||||
#endif // V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
|
||||
// A portable, inefficient version of GroupSse2Impl. This exists so SSE2-less
|
||||
// hosts can generate snapshots for SSE2-capable targets.
|
||||
struct GroupSse2Polyfill {
|
||||
static constexpr size_t kWidth = 16; // the number of slots per group
|
||||
|
||||
explicit GroupSse2Polyfill(const ctrl_t* pos) { memcpy(ctrl_, pos, kWidth); }
|
||||
|
||||
// Returns a bitmask representing the positions of slots that match |hash|.
|
||||
BitMask<uint32_t, kWidth> Match(h2_t hash) const {
|
||||
uint32_t mask = 0;
|
||||
for (size_t i = 0; i < kWidth; i++) {
|
||||
if (static_cast<h2_t>(ctrl_[i]) == hash) {
|
||||
mask |= 1u << i;
|
||||
}
|
||||
}
|
||||
return BitMask<uint32_t, kWidth>(mask);
|
||||
}
|
||||
|
||||
// Returns a bitmask representing the positions of empty slots.
|
||||
BitMask<uint32_t, kWidth> MatchEmpty() const {
|
||||
return Match(static_cast<h2_t>(kEmpty));
|
||||
}
|
||||
|
||||
// Returns a bitmask representing the positions of empty or deleted slots.
|
||||
BitMask<uint32_t, kWidth> MatchEmptyOrDeleted() const {
|
||||
return BitMask<uint32_t, kWidth>(MatchEmptyOrDeletedMask());
|
||||
}
|
||||
|
||||
// Returns the number of trailing empty or deleted elements in the group.
|
||||
uint32_t CountLeadingEmptyOrDeleted() const {
|
||||
return base::bits::CountTrailingZerosNonZero(MatchEmptyOrDeletedMask() + 1);
|
||||
}
|
||||
|
||||
void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
|
||||
for (size_t i = 0; i < kWidth; i++) {
|
||||
if (ctrl_[i] < 0) {
|
||||
dst[i] = kEmpty;
|
||||
} else {
|
||||
dst[i] = kDeleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t MatchEmptyOrDeletedMask() const {
|
||||
uint32_t mask = 0;
|
||||
for (size_t i = 0; i < kWidth; i++) {
|
||||
if (ctrl_[i] < kSentinel) {
|
||||
mask |= 1u << i;
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
ctrl_t ctrl_[kWidth];
|
||||
};
|
||||
|
||||
struct GroupPortableImpl {
|
||||
static constexpr size_t kWidth = 8; // the number of slots per group
|
||||
@ -366,16 +422,23 @@ struct GroupPortableImpl {
|
||||
// backend should only use SSE2 when compiling the SIMD version of
|
||||
// SwissNameDictionary into the builtin.
|
||||
using Group = GroupPortableImpl;
|
||||
#else
|
||||
#if SWISS_TABLE_HAVE_SSE2
|
||||
#elif V8_SWISS_TABLE_HAVE_SSE2_TARGET
|
||||
// Use a matching group size between host and target.
|
||||
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
using Group = GroupSse2Impl;
|
||||
#else
|
||||
#if V8_HOST_ARCH_IA32 || V8_HOST_ARCH_X64
|
||||
// If we do not detect SSE2 when building for the ia32/x64 target, the
|
||||
// V8_SWISS_TABLE_HAVE_SSE2_TARGET logic will incorrectly cause the final output
|
||||
// to use the inefficient polyfill implementation. Detect this case and warn if
|
||||
// it happens.
|
||||
#warning "Did not detect required SSE2 support on ia32/x64."
|
||||
#endif
|
||||
using Group = GroupSse2Polyfill;
|
||||
#endif
|
||||
#else
|
||||
using Group = GroupPortableImpl;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#undef SWISS_TABLE_HAVE_SSE2
|
||||
#undef SWISS_TABLE_HAVE_SSE3
|
||||
|
||||
} // namespace swiss_table
|
||||
} // namespace internal
|
||||
|
@ -368,6 +368,7 @@ v8_source_set("unittests_sources") {
|
||||
"numbers/conversions-unittest.cc",
|
||||
"objects/object-unittest.cc",
|
||||
"objects/osr-optimized-code-cache-unittest.cc",
|
||||
"objects/swiss-hash-table-helpers-unittest.cc",
|
||||
"objects/value-serializer-unittest.cc",
|
||||
"objects/weakarraylist-unittest.cc",
|
||||
"parser/ast-value-unittest.cc",
|
||||
|
109
test/unittests/objects/swiss-hash-table-helpers-unittest.cc
Normal file
109
test/unittests/objects/swiss-hash-table-helpers-unittest.cc
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2022 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/objects/swiss-hash-table-helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "testing/gmock/include/gmock/gmock-matchers.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
using testing::ElementsAre;
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace swiss_table {
|
||||
|
||||
template <typename T>
|
||||
class SwissTableGroupTest : public testing::Test {};
|
||||
|
||||
using GroupTypes = testing::Types<
|
||||
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
|
||||
GroupSse2Impl,
|
||||
#endif
|
||||
GroupSse2Polyfill, GroupPortableImpl>;
|
||||
TYPED_TEST_SUITE(SwissTableGroupTest, GroupTypes);
|
||||
|
||||
// Tests imported from Abseil's raw_hash_set_test.cc, modified to be
|
||||
// parameterized.
|
||||
|
||||
TYPED_TEST(SwissTableGroupTest, EmptyGroup) {
|
||||
const ctrl_t kEmptyGroup[16] = {
|
||||
kSentinel, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,
|
||||
kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty, kEmpty,
|
||||
};
|
||||
for (h2_t h = 0; h != 128; ++h) EXPECT_FALSE(TypeParam{kEmptyGroup}.Match(h));
|
||||
}
|
||||
|
||||
TYPED_TEST(SwissTableGroupTest, Match) {
|
||||
if (TypeParam::kWidth == 16) {
|
||||
ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
|
||||
7, 5, 3, 1, 1, 1, 1, 1};
|
||||
EXPECT_THAT(TypeParam{group}.Match(0), ElementsAre());
|
||||
EXPECT_THAT(TypeParam{group}.Match(1), ElementsAre(1, 11, 12, 13, 14, 15));
|
||||
EXPECT_THAT(TypeParam{group}.Match(3), ElementsAre(3, 10));
|
||||
EXPECT_THAT(TypeParam{group}.Match(5), ElementsAre(5, 9));
|
||||
EXPECT_THAT(TypeParam{group}.Match(7), ElementsAre(7, 8));
|
||||
} else if (TypeParam::kWidth == 8) {
|
||||
ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
|
||||
EXPECT_THAT(TypeParam{group}.Match(0), ElementsAre());
|
||||
EXPECT_THAT(TypeParam{group}.Match(1), ElementsAre(1, 5, 7));
|
||||
EXPECT_THAT(TypeParam{group}.Match(2), ElementsAre(2, 4));
|
||||
} else {
|
||||
FAIL() << "No test coverage for kWidth==" << TypeParam::kWidth;
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(SwissTableGroupTest, MatchEmpty) {
|
||||
if (TypeParam::kWidth == 16) {
|
||||
ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
|
||||
7, 5, 3, 1, 1, 1, 1, 1};
|
||||
EXPECT_THAT(TypeParam{group}.MatchEmpty(), ElementsAre(0, 4));
|
||||
} else if (TypeParam::kWidth == 8) {
|
||||
ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
|
||||
EXPECT_THAT(TypeParam{group}.MatchEmpty(), ElementsAre(0));
|
||||
} else {
|
||||
FAIL() << "No test coverage for kWidth==" << TypeParam::kWidth;
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(SwissTableGroupTest, MatchEmptyOrDeleted) {
|
||||
if (TypeParam::kWidth == 16) {
|
||||
ctrl_t group[] = {kEmpty, 1, kDeleted, 3, kEmpty, 5, kSentinel, 7,
|
||||
7, 5, 3, 1, 1, 1, 1, 1};
|
||||
EXPECT_THAT(TypeParam{group}.MatchEmptyOrDeleted(), ElementsAre(0, 2, 4));
|
||||
} else if (TypeParam::kWidth == 8) {
|
||||
ctrl_t group[] = {kEmpty, 1, 2, kDeleted, 2, 1, kSentinel, 1};
|
||||
EXPECT_THAT(TypeParam{group}.MatchEmptyOrDeleted(), ElementsAre(0, 3));
|
||||
} else {
|
||||
FAIL() << "No test coverage for kWidth==" << TypeParam::kWidth;
|
||||
}
|
||||
}
|
||||
|
||||
TYPED_TEST(SwissTableGroupTest, CountLeadingEmptyOrDeleted) {
|
||||
const std::vector<ctrl_t> empty_examples = {kEmpty, kDeleted};
|
||||
const std::vector<ctrl_t> full_examples = {0, 1, 2, 3, 5, 9, 127, kSentinel};
|
||||
|
||||
for (ctrl_t empty : empty_examples) {
|
||||
std::vector<ctrl_t> e(TypeParam::kWidth, empty);
|
||||
EXPECT_EQ(TypeParam::kWidth,
|
||||
TypeParam{e.data()}.CountLeadingEmptyOrDeleted());
|
||||
for (ctrl_t full : full_examples) {
|
||||
for (size_t i = 0; i != TypeParam::kWidth; ++i) {
|
||||
std::vector<ctrl_t> f(TypeParam::kWidth, empty);
|
||||
f[i] = full;
|
||||
EXPECT_EQ(i, TypeParam{f.data()}.CountLeadingEmptyOrDeleted());
|
||||
}
|
||||
std::vector<ctrl_t> f(TypeParam::kWidth, empty);
|
||||
f[TypeParam::kWidth * 2 / 3] = full;
|
||||
f[TypeParam::kWidth / 2] = full;
|
||||
EXPECT_EQ(TypeParam::kWidth / 2,
|
||||
TypeParam{f.data()}.CountLeadingEmptyOrDeleted());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace swiss_table
|
||||
} // namespace internal
|
||||
} // namespace v8
|
Loading…
Reference in New Issue
Block a user