blob: a4aa3a9669abfec32cb1dccc365ed44ffe7c1e82 [file] [log] [blame]
// Copyright 2025 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/container/internal/hashtable_control_bytes.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
namespace {
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
// Convenience function to static cast to ctrl_t.
ctrl_t CtrlT(int i) { return static_cast<ctrl_t>(i); }
TEST(BitMask, Smoke) {
EXPECT_FALSE((BitMask<uint8_t, 8>(0)));
EXPECT_TRUE((BitMask<uint8_t, 8>(5)));
EXPECT_THAT((BitMask<uint8_t, 8>(0)), ElementsAre());
EXPECT_THAT((BitMask<uint8_t, 8>(0x1)), ElementsAre(0));
EXPECT_THAT((BitMask<uint8_t, 8>(0x2)), ElementsAre(1));
EXPECT_THAT((BitMask<uint8_t, 8>(0x3)), ElementsAre(0, 1));
EXPECT_THAT((BitMask<uint8_t, 8>(0x4)), ElementsAre(2));
EXPECT_THAT((BitMask<uint8_t, 8>(0x5)), ElementsAre(0, 2));
EXPECT_THAT((BitMask<uint8_t, 8>(0x55)), ElementsAre(0, 2, 4, 6));
EXPECT_THAT((BitMask<uint8_t, 8>(0xAA)), ElementsAre(1, 3, 5, 7));
}
TEST(BitMask, WithShift_MatchPortable) {
// See the non-SSE version of Group for details on what this math is for.
uint64_t ctrl = 0x1716151413121110;
uint64_t hash = 0x12;
constexpr uint64_t lsbs = 0x0101010101010101ULL;
auto x = ctrl ^ (lsbs * hash);
uint64_t mask = (x - lsbs) & ~x & kMsbs8Bytes;
EXPECT_EQ(0x0000000080800000, mask);
BitMask<uint64_t, 8, 3> b(mask);
EXPECT_EQ(*b, 2);
}
constexpr uint64_t kSome8BytesMask = /* */ 0x8000808080008000ULL;
constexpr uint64_t kSome8BytesMaskAllOnes = 0xff00ffffff00ff00ULL;
constexpr auto kSome8BytesMaskBits = std::array<int, 5>{1, 3, 4, 5, 7};
TEST(BitMask, WithShift_FullMask) {
EXPECT_THAT((BitMask<uint64_t, 8, 3>(kMsbs8Bytes)),
ElementsAre(0, 1, 2, 3, 4, 5, 6, 7));
EXPECT_THAT(
(BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(kMsbs8Bytes)),
ElementsAre(0, 1, 2, 3, 4, 5, 6, 7));
EXPECT_THAT(
(BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(~uint64_t{0})),
ElementsAre(0, 1, 2, 3, 4, 5, 6, 7));
}
TEST(BitMask, WithShift_EmptyMask) {
EXPECT_THAT((BitMask<uint64_t, 8, 3>(0)), ElementsAre());
EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(0)),
ElementsAre());
}
TEST(BitMask, WithShift_SomeMask) {
EXPECT_THAT((BitMask<uint64_t, 8, 3>(kSome8BytesMask)),
ElementsAreArray(kSome8BytesMaskBits));
EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(
kSome8BytesMask)),
ElementsAreArray(kSome8BytesMaskBits));
EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(
kSome8BytesMaskAllOnes)),
ElementsAreArray(kSome8BytesMaskBits));
}
TEST(BitMask, WithShift_SomeMaskExtraBitsForNullify) {
// Verify that adding extra bits into non zero bytes is fine.
uint64_t extra_bits = 77;
for (int i = 0; i < 100; ++i) {
// Add extra bits, but keep zero bytes untouched.
uint64_t extra_mask = extra_bits & kSome8BytesMaskAllOnes;
EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(
kSome8BytesMask | extra_mask)),
ElementsAreArray(kSome8BytesMaskBits))
<< i << " " << extra_mask;
extra_bits = (extra_bits + 1) * 3;
}
}
TEST(BitMask, LeadingTrailing) {
EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).LeadingZeros()), 3);
EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).TrailingZeros()), 6);
EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).LeadingZeros()), 15);
EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).TrailingZeros()), 0);
EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).LeadingZeros()), 0);
EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).TrailingZeros()), 15);
EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).LeadingZeros()), 3);
EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).TrailingZeros()), 1);
EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).LeadingZeros()), 7);
EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).TrailingZeros()), 0);
EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).LeadingZeros()), 0);
EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).TrailingZeros()), 7);
}
template <class GroupTypeParam>
class GroupTest : public testing::Test {};
using GroupTypes =
::testing::Types<Group, GroupPortableImpl, GroupFullEmptyOrDeleted>;
TYPED_TEST_SUITE(GroupTest, GroupTypes);
TYPED_TEST(GroupTest, Match) {
using GroupType = TypeParam;
if (GroupType::kWidth == 16) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3),
ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7),
CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1),
CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)};
EXPECT_THAT(GroupType{group}.Match(0), ElementsAre());
EXPECT_THAT(GroupType{group}.Match(1), ElementsAre(1, 11, 12, 13, 14, 15));
EXPECT_THAT(GroupType{group}.Match(3), ElementsAre(3, 10));
EXPECT_THAT(GroupType{group}.Match(5), ElementsAre(5, 9));
EXPECT_THAT(GroupType{group}.Match(7), ElementsAre(7, 8));
} else if (GroupType::kWidth == 8) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2),
ctrl_t::kDeleted, CtrlT(2), CtrlT(1),
ctrl_t::kSentinel, CtrlT(1)};
EXPECT_THAT(GroupType{group}.Match(0), ElementsAre());
EXPECT_THAT(GroupType{group}.Match(1), ElementsAre(1, 5, 7));
EXPECT_THAT(GroupType{group}.Match(2), ElementsAre(2, 4));
} else {
FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth;
}
}
TYPED_TEST(GroupTest, MaskEmpty) {
using GroupType = TypeParam;
if (GroupType::kWidth == 16) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3),
ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7),
CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1),
CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskEmpty().LowestBitSet(), 0);
} else if (GroupType::kWidth == 8) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2),
ctrl_t::kDeleted, CtrlT(2), CtrlT(1),
ctrl_t::kSentinel, CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskEmpty().LowestBitSet(), 0);
} else {
FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth;
}
}
TYPED_TEST(GroupTest, MaskFull) {
using GroupType = TypeParam;
if (GroupType::kWidth == 16) {
ctrl_t group[] = {
ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3),
ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7),
CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1),
CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskFull(),
ElementsAre(1, 3, 5, 7, 8, 9, 11, 12, 15));
} else if (GroupType::kWidth == 8) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty,
ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel,
ctrl_t::kSentinel, CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskFull(), ElementsAre(1, 4, 7));
} else {
FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth;
}
}
TYPED_TEST(GroupTest, MaskNonFull) {
using GroupType = TypeParam;
if (GroupType::kWidth == 16) {
ctrl_t group[] = {
ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3),
ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7),
CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1),
CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskNonFull(),
ElementsAre(0, 2, 4, 6, 10, 13, 14));
} else if (GroupType::kWidth == 8) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty,
ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel,
ctrl_t::kSentinel, CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskNonFull(), ElementsAre(0, 2, 3, 5, 6));
} else {
FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth;
}
}
TYPED_TEST(GroupTest, MaskEmptyOrDeleted) {
using GroupType = TypeParam;
if (GroupType::kWidth == 16) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, CtrlT(3),
ctrl_t::kDeleted, CtrlT(5), ctrl_t::kSentinel, CtrlT(7),
CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1),
CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskEmptyOrDeleted().LowestBitSet(), 0);
} else if (GroupType::kWidth == 8) {
ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2),
ctrl_t::kDeleted, CtrlT(2), CtrlT(1),
ctrl_t::kSentinel, CtrlT(1)};
EXPECT_THAT(GroupType{group}.MaskEmptyOrDeleted().LowestBitSet(), 0);
} else {
FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth;
}
}
TYPED_TEST(GroupTest, MaskFullOrSentinel) {
using GroupType = TypeParam;
if (GroupType::kWidth == 16) {
ctrl_t group[] = {
ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kEmpty, CtrlT(3),
ctrl_t::kDeleted, CtrlT(5), ctrl_t::kSentinel, ctrl_t::kEmpty,
ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kDeleted, ctrl_t::kDeleted,
ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kDeleted, ctrl_t::kDeleted,
};
EXPECT_THAT(GroupType{group}.MaskFullOrSentinel().LowestBitSet(), 3);
} else if (GroupType::kWidth == 8) {
ctrl_t group[] = {ctrl_t::kEmpty, ctrl_t::kDeleted, CtrlT(2),
ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel,
ctrl_t::kDeleted, ctrl_t::kEmpty};
EXPECT_THAT(GroupType{group}.MaskFullOrSentinel().LowestBitSet(), 2);
} else {
FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth;
}
}
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
} // namespace absl