Skip to content

Commit b22423e

Browse files
goldvitalycopybara-github
authored andcommitted
ROLLBACK: Limit slot_size to 2^16-1 and maximum table size to 2^43-1.
Limiting `slot_size` may subtly break workflows with seemingly irrelevant changes. PiperOrigin-RevId: 731443006 Change-Id: I458610d280c99f773b0a957bb8f3d6d00529551a
1 parent fbc0df2 commit b22423e

File tree

3 files changed

+48
-136
lines changed

3 files changed

+48
-136
lines changed

absl/container/internal/raw_hash_set.cc

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,6 @@ static_assert(NumControlBytes(SooCapacity()) <= 17,
6969

7070
namespace {
7171

72-
[[noreturn]] ABSL_ATTRIBUTE_NOINLINE void HashTableSizeOverflow() {
73-
ABSL_RAW_LOG(FATAL, "Hash table size overflow");
74-
}
75-
76-
void ValidateMaxSize(size_t size, size_t slot_size) {
77-
if (IsAboveMaxValidSize(size, slot_size)) {
78-
HashTableSizeOverflow();
79-
}
80-
}
81-
8272
// Returns "random" seed.
8373
inline size_t RandomSeed() {
8474
#ifdef ABSL_HAVE_THREAD_LOCAL
@@ -525,6 +515,9 @@ void ResizeEmptyNonAllocatedTableImpl(CommonFields& common, size_t new_capacity,
525515
assert(common.capacity() <= policy.soo_capacity);
526516
assert(common.empty());
527517
const size_t slot_size = policy.slot_size;
518+
if (ABSL_PREDICT_FALSE(new_capacity > MaxValidCapacity(slot_size))) {
519+
HashTableSizeOverflow();
520+
}
528521
HashtablezInfoHandle infoz;
529522
const bool should_sample =
530523
policy.is_hashtablez_eligible && (force_infoz || ShouldSampleNextTable());
@@ -930,25 +923,10 @@ void ResizeAllocatedTable(CommonFields& common, size_t new_capacity,
930923
common, new_capacity, common.infoz(), policy);
931924
}
932925

933-
void ReserveEmptyNonAllocatedTableToFitNewSize(CommonFields& common,
934-
size_t new_size,
935-
const PolicyFunctions& policy) {
936-
ValidateMaxSize(new_size, policy.slot_size);
937-
ResizeEmptyNonAllocatedTableImpl(
938-
common, NormalizeCapacity(GrowthToLowerboundCapacity(new_size)),
939-
/*force_infoz=*/false, policy);
940-
// This is after resize, to ensure that we have completed the allocation
941-
// and have potentially sampled the hashtable.
942-
common.infoz().RecordReservation(new_size);
943-
common.reset_reserved_growth(new_size);
944-
common.set_reservation_size(new_size);
945-
}
946-
947-
void ReserveEmptyNonAllocatedTableToFitBucketCount(
948-
CommonFields& common, size_t bucket_count, const PolicyFunctions& policy) {
949-
ValidateMaxSize(bucket_count, policy.slot_size);
950-
ResizeEmptyNonAllocatedTableImpl(common, NormalizeCapacity(bucket_count),
951-
/*force_infoz=*/false, policy);
926+
void ResizeEmptyNonAllocatedTable(CommonFields& common, size_t new_capacity,
927+
const PolicyFunctions& policy) {
928+
ResizeEmptyNonAllocatedTableImpl(common, new_capacity, /*force_infoz=*/false,
929+
policy);
952930
}
953931

954932
void GrowEmptySooTableToNextCapacityForceSampling(
@@ -1015,11 +993,12 @@ void Rehash(CommonFields& common, size_t n, const PolicyFunctions& policy) {
1015993
NormalizeCapacity(n | GrowthToLowerboundCapacity(common.size()));
1016994
// n == 0 unconditionally rehashes as per the standard.
1017995
if (n == 0 || new_capacity > cap) {
1018-
ValidateMaxSize(n, policy.slot_size);
996+
if (ABSL_PREDICT_FALSE(new_capacity > MaxValidCapacity(slot_size))) {
997+
HashTableSizeOverflow();
998+
}
1019999
if (cap == policy.soo_capacity) {
10201000
if (common.empty()) {
1021-
ResizeEmptyNonAllocatedTableImpl(common, new_capacity,
1022-
/*force_infoz=*/false, policy);
1001+
ResizeEmptyNonAllocatedTable(common, new_capacity, policy);
10231002
} else {
10241003
ResizeFullSooTable(common, new_capacity,
10251004
ResizeFullSooTableSamplingMode::kNoSampling, policy);
@@ -1035,9 +1014,6 @@ void Rehash(CommonFields& common, size_t n, const PolicyFunctions& policy) {
10351014

10361015
void ReserveAllocatedTable(CommonFields& common, size_t n,
10371016
const PolicyFunctions& policy) {
1038-
common.reset_reserved_growth(n);
1039-
common.set_reservation_size(n);
1040-
10411017
const size_t cap = common.capacity();
10421018
assert(!common.empty() || cap > policy.soo_capacity);
10431019
assert(cap > 0);
@@ -1047,8 +1023,10 @@ void ReserveAllocatedTable(CommonFields& common, size_t n,
10471023
if (n <= max_size_before_growth) {
10481024
return;
10491025
}
1050-
ValidateMaxSize(n, policy.slot_size);
10511026
const size_t new_capacity = NormalizeCapacity(GrowthToLowerboundCapacity(n));
1027+
if (ABSL_PREDICT_FALSE(new_capacity > MaxValidCapacity(policy.slot_size))) {
1028+
HashTableSizeOverflow();
1029+
}
10521030
if (cap == policy.soo_capacity) {
10531031
assert(!common.empty());
10541032
ResizeFullSooTable(common, new_capacity,
@@ -1088,6 +1066,10 @@ size_t PrepareInsertNonSoo(CommonFields& common, size_t hash,
10881066
return target.offset;
10891067
}
10901068

1069+
void HashTableSizeOverflow() {
1070+
ABSL_RAW_LOG(FATAL, "Hash table size overflow");
1071+
}
1072+
10911073
} // namespace container_internal
10921074
ABSL_NAMESPACE_END
10931075
} // namespace absl

absl/container/internal/raw_hash_set.h

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,14 @@ constexpr size_t NormalizeCapacity(size_t n) {
10971097
return n ? ~size_t{} >> countl_zero(n) : 1;
10981098
}
10991099

1100+
constexpr size_t MaxValidCapacity(size_t slot_size) {
1101+
return NormalizeCapacity((std::numeric_limits<size_t>::max)() / 4 /
1102+
slot_size);
1103+
}
1104+
1105+
// Use a non-inlined function to avoid code bloat.
1106+
[[noreturn]] void HashTableSizeOverflow();
1107+
11001108
// General notes on capacity/growth methods below:
11011109
// - We use 7/8th as maximum load factor. For 16-wide groups, that gives an
11021110
// average of two empty slots per group.
@@ -1529,7 +1537,7 @@ ABSL_ATTRIBUTE_NOINLINE void DeallocateBackingArray(
15291537
struct PolicyFunctions {
15301538
uint32_t key_size;
15311539
uint32_t value_size;
1532-
uint16_t slot_size;
1540+
uint32_t slot_size;
15331541
uint16_t slot_align;
15341542
uint8_t soo_capacity;
15351543
bool is_hashtablez_eligible;
@@ -1576,20 +1584,14 @@ constexpr size_t SooSlotIndex() { return 1; }
15761584
// Allowing till 16 would require additional store that can be avoided.
15771585
constexpr size_t MaxSmallAfterSooCapacity() { return 7; }
15781586

1579-
// Resizes empty non-allocated table to the capacity to fit new_size elements.
1587+
// Resizes empty non-allocated table to the new capacity.
15801588
// Requires:
15811589
// 1. `c.capacity() == policy.soo_capacity`.
15821590
// 2. `c.empty()`.
1583-
// 3. `new_size > policy.soo_capacity`.
1591+
// 3. `new_capacity > policy.soo_capacity`.
15841592
// The table will be attempted to be sampled.
1585-
void ReserveEmptyNonAllocatedTableToFitNewSize(CommonFields& common,
1586-
size_t new_size,
1587-
const PolicyFunctions& policy);
1588-
1589-
// The same as ReserveEmptyNonAllocatedTableToFitNewSize, but resizes to the
1590-
// next valid capacity after `bucket_count`.
1591-
void ReserveEmptyNonAllocatedTableToFitBucketCount(
1592-
CommonFields& common, size_t bucket_count, const PolicyFunctions& policy);
1593+
void ResizeEmptyNonAllocatedTable(CommonFields& common, size_t new_capacity,
1594+
const PolicyFunctions& policy);
15931595

15941596
// Resizes empty non-allocated SOO table to NextCapacity(SooCapacity()) and
15951597
// forces the table to be sampled.
@@ -1657,33 +1659,6 @@ InitializeThreeElementsControlBytesAfterSoo(size_t hash, ctrl_t* new_ctrl) {
16571659
// new_ctrl after 2nd store = EHESEHEEEEE
16581660
}
16591661

1660-
// Template parameter is only used to enable testing.
1661-
template <size_t kSizeOfSizeT = sizeof(size_t)>
1662-
constexpr size_t MaxValidSize(size_t slot_size) {
1663-
if constexpr (kSizeOfSizeT == 4) {
1664-
return (size_t{1} << (kSizeOfSizeT * 8 - 2)) / slot_size - 1;
1665-
} else {
1666-
static_assert(kSizeOfSizeT == 8);
1667-
constexpr size_t kSizeBits = 43;
1668-
static_assert(
1669-
kSizeBits + sizeof(PolicyFunctions::slot_size) * 8 < 64,
1670-
"we expect that slot size is small enough that allocation size "
1671-
"will not overflow");
1672-
return CapacityToGrowth(static_cast<size_t>(uint64_t{1} << kSizeBits) - 1);
1673-
}
1674-
}
1675-
1676-
// Template parameter is only used to enable testing.
1677-
template <size_t kSizeOfSizeT = sizeof(size_t)>
1678-
constexpr size_t IsAboveMaxValidSize(size_t size, size_t slot_size) {
1679-
if constexpr (kSizeOfSizeT == 4) {
1680-
return uint64_t{size} * slot_size >
1681-
MaxValidSize<kSizeOfSizeT>(/*slot_size=*/1);
1682-
} else {
1683-
return size > MaxValidSize(slot_size);
1684-
}
1685-
}
1686-
16871662
// Returns the optimal size for memcpy when transferring SOO slot.
16881663
// Otherwise, returns the optimal size for memcpy SOO slot transfer
16891664
// to SooSlotIndex().
@@ -2150,8 +2125,8 @@ class raw_hash_set {
21502125
: settings_(CommonFields::CreateDefault<SooEnabled()>(), hash, eq,
21512126
alloc) {
21522127
if (bucket_count > DefaultCapacity()) {
2153-
ReserveEmptyNonAllocatedTableToFitBucketCount(common(), bucket_count,
2154-
GetPolicyFunctions());
2128+
ResizeEmptyNonAllocatedTable(common(), NormalizeCapacity(bucket_count),
2129+
GetPolicyFunctions());
21552130
}
21562131
}
21572132

@@ -2427,7 +2402,9 @@ class raw_hash_set {
24272402
ABSL_ASSUME(cap >= kDefaultCapacity);
24282403
return cap;
24292404
}
2430-
size_t max_size() const { return MaxValidSize(sizeof(slot_type)); }
2405+
size_t max_size() const {
2406+
return CapacityToGrowth(MaxValidCapacity(sizeof(slot_type)));
2407+
}
24312408

24322409
ABSL_ATTRIBUTE_REINITIALIZES void clear() {
24332410
if (SwisstableGenerationsEnabled() &&
@@ -2836,10 +2813,16 @@ class raw_hash_set {
28362813
ReserveAllocatedTable(common(), n, GetPolicyFunctions());
28372814
} else {
28382815
if (ABSL_PREDICT_TRUE(n > DefaultCapacity())) {
2839-
ReserveEmptyNonAllocatedTableToFitNewSize(common(), n,
2840-
GetPolicyFunctions());
2816+
ResizeEmptyNonAllocatedTable(
2817+
common(), NormalizeCapacity(GrowthToLowerboundCapacity(n)),
2818+
GetPolicyFunctions());
2819+
// This is after resize, to ensure that we have completed the allocation
2820+
// and have potentially sampled the hashtable.
2821+
infoz().RecordReservation(n);
28412822
}
28422823
}
2824+
common().reset_reserved_growth(n);
2825+
common().set_reservation_size(n);
28432826
}
28442827

28452828
// Extension API: support for heterogeneous keys.
@@ -3575,15 +3558,10 @@ class raw_hash_set {
35753558
}
35763559

35773560
static const PolicyFunctions& GetPolicyFunctions() {
3578-
static_assert(sizeof(slot_type) <= (std::numeric_limits<uint16_t>::max)(),
3579-
"Slot size is too large. Use std::unique_ptr for value type "
3580-
"or use absl::node_hash_{map,set}.");
3581-
static_assert(alignof(slot_type) <=
3582-
size_t{(std::numeric_limits<uint16_t>::max)()});
3583-
static_assert(sizeof(key_type) <=
3584-
size_t{(std::numeric_limits<uint32_t>::max)()});
3585-
static_assert(sizeof(value_type) <=
3586-
size_t{(std::numeric_limits<uint32_t>::max)()});
3561+
static_assert(sizeof(slot_type) <= (std::numeric_limits<uint32_t>::max)());
3562+
static_assert(alignof(slot_type) <= (std::numeric_limits<uint16_t>::max)());
3563+
static_assert(sizeof(key_type) <= (std::numeric_limits<uint32_t>::max)());
3564+
static_assert(sizeof(value_type) <= (std::numeric_limits<uint32_t>::max)());
35873565
static constexpr size_t kBackingArrayAlignment =
35883566
BackingArrayAlignment(alignof(slot_type));
35893567
static constexpr PolicyFunctions value = {

absl/container/internal/raw_hash_set_test.cc

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4039,60 +4039,12 @@ TEST(Table, MovedFromCallsFail) {
40394039
}
40404040
}
40414041

4042-
TEST(Table, MaxValidSize) {
4043-
IntTable t;
4044-
EXPECT_EQ(MaxValidSize(sizeof(IntTable::value_type)), t.max_size());
4045-
if constexpr (sizeof(size_t) == 8) {
4046-
for (size_t i = 0; i < 16; ++i) {
4047-
size_t slot_size = size_t{1} << i;
4048-
size_t max_size = MaxValidSize(slot_size);
4049-
ASSERT_LT(max_size, uint64_t{1} << 60);
4050-
ASSERT_TRUE(IsAboveMaxValidSize(max_size + 1, slot_size));
4051-
ASSERT_TRUE(IsAboveMaxValidSize(uint64_t{1} << 63, slot_size));
4052-
ASSERT_TRUE(IsAboveMaxValidSize(~size_t{}, slot_size));
4053-
ASSERT_TRUE(IsAboveMaxValidSize(~size_t{} / 8 * 7, slot_size));
4054-
// Given that key size have to be at least 6 bytes to reach so many
4055-
// different values, total memory usage of the table will be at least
4056-
// 2^42*7 bytes (28 TB).
4057-
// So that value should be enough for all practical purposes.
4058-
ASSERT_GE(max_size, uint64_t{1} << 42);
4059-
// We leave some headroom for the table metadata.
4060-
ASSERT_LT(NormalizeCapacity(GrowthToLowerboundCapacity(max_size)),
4061-
uint64_t{1} << 44);
4062-
}
4063-
}
4064-
EXPECT_LT(MaxValidSize</*kSizeOfSizeT=*/4>(1), 1 << 30);
4065-
EXPECT_LT(MaxValidSize</*kSizeOfSizeT=*/4>(2), 1 << 29);
4066-
EXPECT_TRUE(IsAboveMaxValidSize</*kSizeOfSizeT=*/4>(1 << 30, 1));
4067-
EXPECT_TRUE(IsAboveMaxValidSize</*kSizeOfSizeT=*/4>(1 << 29, 2));
4068-
EXPECT_TRUE(IsAboveMaxValidSize</*kSizeOfSizeT=*/4>(~uint32_t{}, 1));
4069-
EXPECT_TRUE(IsAboveMaxValidSize</*kSizeOfSizeT=*/4>(~uint32_t{} / 8 * 7, 1));
4070-
for (size_t i = 0; i < 16; ++i) {
4071-
size_t slot_size = size_t{1} << i;
4072-
size_t max_size = MaxValidSize</*kSizeOfSizeT=*/4>(slot_size);
4073-
ASSERT_LT(max_size, 1 << 30);
4074-
ASSERT_TRUE(
4075-
IsAboveMaxValidSize</*kSizeOfSizeT=*/4>(max_size + 1, slot_size));
4076-
size_t max_capacity =
4077-
NormalizeCapacity(GrowthToLowerboundCapacity(max_size));
4078-
ASSERT_LT(max_capacity, (size_t{1} << 31) / slot_size);
4079-
ASSERT_GT(max_capacity, (1 << 29) / slot_size);
4080-
}
4081-
}
4082-
40834042
TEST(Table, MaxSizeOverflow) {
40844043
size_t overflow = (std::numeric_limits<size_t>::max)();
40854044
EXPECT_DEATH_IF_SUPPORTED(IntTable t(overflow), "Hash table size overflow");
40864045
IntTable t;
40874046
EXPECT_DEATH_IF_SUPPORTED(t.reserve(overflow), "Hash table size overflow");
40884047
EXPECT_DEATH_IF_SUPPORTED(t.rehash(overflow), "Hash table size overflow");
4089-
size_t slightly_overflow = MaxValidSize(sizeof(IntTable::value_type)) + 1;
4090-
EXPECT_DEATH_IF_SUPPORTED(IntTable t2(slightly_overflow),
4091-
"Hash table size overflow");
4092-
EXPECT_DEATH_IF_SUPPORTED(t.reserve(slightly_overflow),
4093-
"Hash table size overflow");
4094-
EXPECT_DEATH_IF_SUPPORTED(t.rehash(slightly_overflow),
4095-
"Hash table size overflow");
40964048
}
40974049

40984050
// TODO(b/397453582): Remove support for const hasher and ermove this test.

0 commit comments

Comments
 (0)