#ifndef V8_OBJECTS_SWISS_HASH_TABLE_HELPERS_H_
#define V8_OBJECTS_SWISS_HASH_TABLE_HELPERS_H_
#include <climits>
#include <cstdint>
#include <type_traits>
#include "src/base/bits.h"
#include "src/base/logging.h"
#include "src/base/memory.h"
#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 V8_SWISS_TABLE_HAVE_SSE2_HOST 0
#endif
#endif
#ifndef V8_SWISS_TABLE_HAVE_SSSE3_HOST
#if defined(__SSSE3__)
#define V8_SWISS_TABLE_HAVE_SSSE3_HOST 1
#else
#define V8_SWISS_TABLE_HAVE_SSSE3_HOST 0
#endif
#endif
#if V8_SWISS_TABLE_HAVE_SSSE3_HOST && !V8_SWISS_TABLE_HAVE_SSE2_HOST
#error "Bad configuration!"
#endif
#ifndef V8_SWISS_TABLE_HAVE_SSE2_TARGET
#if V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64
#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 V8_SWISS_TABLE_HAVE_SSSE3_HOST
#include <tmmintrin.h>
#endif
namespace v8 {
namespace internal {
namespace swiss_table {
template <size_t GroupSize>
class ProbeSequence {
public:
ProbeSequence(uint32_t hash, uint32_t mask) {
DCHECK_EQ(0, ((mask + 1) & mask));
mask_ = mask;
offset_ = hash & mask_;
}
uint32_t offset() const { return offset_; }
uint32_t offset(int i) const { return (offset_ + i) & mask_; }
void next() {
index_ += GroupSize;
offset_ += index_;
offset_ &= mask_;
}
size_t index() const { return index_; }
private:
uint32_t mask_;
uint32_t offset_;
uint32_t index_ = 0;
};
template <class T, int SignificantBits, int Shift = 0>
class BitMask {
static_assert(std::is_unsigned_v<T>);
static_assert(Shift == 0 || Shift == 3);
public:
using value_type = int;
using iterator = BitMask;
using const_iterator = BitMask;
explicit BitMask(T mask) : mask_(mask) {}
BitMask& operator++() {
mask_ &= (mask_ - 1);
return *this;
}
explicit operator bool() const { return mask_ != 0; }
int operator*() const { return LowestBitSet(); }
int LowestBitSet() const { return TrailingZeros(); }
int HighestBitSet() const {
return (sizeof(T) * CHAR_BIT - base::bits::CountLeadingZeros(mask_) - 1) >>
Shift;
}
BitMask begin() const { return *this; }
BitMask end() const { return BitMask(0); }
int TrailingZeros() const {
DCHECK_NE(mask_, 0);
return base::bits::CountTrailingZerosNonZero(mask_) >> Shift;
}
int LeadingZeros() const {
constexpr int total_significant_bits = SignificantBits << Shift;
constexpr int extra_bits = sizeof(T) * 8 - total_significant_bits;
return base::bits::CountLeadingZeros(mask_ << extra_bits) >> Shift;
}
private:
friend bool operator==(const BitMask& a, const BitMask& b) {
return a.mask_ == b.mask_;
}
friend bool operator!=(const BitMask& a, const BitMask& b) {
return a.mask_ != b.mask_;
}
T mask_;
};
using ctrl_t = signed char;
using h2_t = uint8_t;
enum Ctrl : ctrl_t {
kEmpty = -128,
kDeleted = -2,
kSentinel = -1,
};
static_assert(
kEmpty & kDeleted & kSentinel & 0x80,
"Special markers need to have the MSB to make checking for them efficient");
static_assert(kEmpty < kSentinel && kDeleted < kSentinel,
"kEmpty and kDeleted must be smaller than kSentinel to make the "
"SIMD test of IsEmptyOrDeleted() efficient");
static_assert(kSentinel == -1,
"kSentinel must be -1 to elide loading it from memory into SIMD "
"registers (pcmpeqd xmm, xmm)");
static_assert(kEmpty == -128,
"kEmpty must be -128 to make the SIMD check for its "
"existence efficient (psignb xmm, xmm)");
static_assert(~kEmpty & ~kDeleted & kSentinel & 0x7F,
"kEmpty and kDeleted must share an unset bit that is not shared "
"by kSentinel to make the scalar test for MatchEmptyOrDeleted() "
"efficient");
static_assert(kDeleted == -2,
"kDeleted must be -2 to make the implementation of "
"ConvertSpecialToEmptyAndFullToDeleted efficient");
static constexpr int kH2Bits = 7;
static constexpr int kNotFullMask = (1 << kH2Bits);
static_assert(
kEmpty & kDeleted & kSentinel & kNotFullMask,
"Special markers need to have the MSB to make checking for them efficient");
inline static uint32_t H1(uint32_t hash) { return (hash >> kH2Bits); }
inline static swiss_table::ctrl_t H2(uint32_t hash) {
return hash & ((1 << kH2Bits) - 1);
}
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
struct GroupSse2Impl {
static constexpr size_t kWidth = 16;
explicit GroupSse2Impl(const ctrl_t* pos) {
ctrl = _mm_loadu_si128(reinterpret_cast<const __m128i*>(pos));
}
BitMask<uint32_t, kWidth> Match(h2_t hash) const {
auto match = _mm_set1_epi8(hash);
return BitMask<uint32_t, kWidth>(
_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)));
}
BitMask<uint32_t, kWidth> MatchEmpty() const {
#if V8_SWISS_TABLE_HAVE_SSSE3_HOST
return BitMask<uint32_t, kWidth>(
_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)));
#else
return Match(static_cast<h2_t>(kEmpty));
#endif
}
__m128i ctrl;
};
#endif
struct GroupSse2Polyfill {
static constexpr size_t kWidth = 16;
explicit GroupSse2Polyfill(const ctrl_t* pos) { memcpy(ctrl_, pos, kWidth); }
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);
}
BitMask<uint32_t, kWidth> MatchEmpty() const {
return Match(static_cast<h2_t>(kEmpty));
}
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;
explicit GroupPortableImpl(const ctrl_t* pos)
: ctrl(base::ReadLittleEndianValue<uint64_t>(
reinterpret_cast<uintptr_t>(const_cast<ctrl_t*>(pos)))) {}
static constexpr uint64_t kMsbs = 0x8080808080808080ULL;
static constexpr uint64_t kLsbs = 0x0101010101010101ULL;
BitMask<uint64_t, kWidth, 3> Match(h2_t hash) const {
auto x = ctrl ^ (kLsbs * hash);
return BitMask<uint64_t, kWidth, 3>((x - kLsbs) & ~x & kMsbs);
}
BitMask<uint64_t, kWidth, 3> MatchEmpty() const {
return BitMask<uint64_t, kWidth, 3>((ctrl & (~ctrl << 6)) & kMsbs);
}
uint64_t ctrl;
};
#if defined(V8_ENABLE_SWISS_NAME_DICTIONARY) && DEBUG
using Group = GroupPortableImpl;
#elif V8_SWISS_TABLE_HAVE_SSE2_TARGET
#if V8_SWISS_TABLE_HAVE_SSE2_HOST
using Group = GroupSse2Impl;
#else
#if V8_HOST_ARCH_IA32 || V8_HOST_ARCH_X64
#warning "Did not detect required SSE2 support on ia32/x64."
#endif
using Group = GroupSse2Polyfill;
#endif
#else
using Group = GroupPortableImpl;
#endif
}
}
}
#endif