| // 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 |