Coverage Report

Created: 2026-07-26 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/abseil-cpp/absl/container/internal/raw_hash_set.cc
Line
Count
Source
1
// Copyright 2018 The Abseil Authors.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "absl/container/internal/raw_hash_set.h"
16
17
#include <atomic>
18
#include <cassert>
19
#include <cstddef>
20
#include <cstdint>
21
#include <cstring>
22
23
#include "absl/base/attributes.h"
24
#include "absl/base/config.h"
25
#include "absl/base/dynamic_annotations.h"
26
#include "absl/container/internal/container_memory.h"
27
#include "absl/hash/hash.h"
28
29
namespace absl {
30
ABSL_NAMESPACE_BEGIN
31
namespace container_internal {
32
33
// We have space for `growth_left` before a single block of control bytes. A
34
// single block of empty control bytes for tables without any slots allocated.
35
// This enables removing a branch in the hot path of find(). In order to ensure
36
// that the control bytes are aligned to 16, we have 16 bytes before the control
37
// bytes even though growth_left only needs 8.
38
0
constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); }
39
alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[32] = {
40
    ZeroCtrlT(),       ZeroCtrlT(),    ZeroCtrlT(),    ZeroCtrlT(),
41
    ZeroCtrlT(),       ZeroCtrlT(),    ZeroCtrlT(),    ZeroCtrlT(),
42
    ZeroCtrlT(),       ZeroCtrlT(),    ZeroCtrlT(),    ZeroCtrlT(),
43
    ZeroCtrlT(),       ZeroCtrlT(),    ZeroCtrlT(),    ZeroCtrlT(),
44
    ctrl_t::kSentinel, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty,
45
    ctrl_t::kEmpty,    ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty,
46
    ctrl_t::kEmpty,    ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty,
47
    ctrl_t::kEmpty,    ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty};
48
49
#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
50
constexpr size_t Group::kWidth;
51
#endif
52
53
namespace {
54
55
// Returns "random" seed.
56
0
inline size_t RandomSeed() {
57
0
#ifdef ABSL_HAVE_THREAD_LOCAL
58
0
  static thread_local size_t counter = 0;
59
  // On Linux kernels >= 5.4 the MSAN runtime has a false-positive when
60
  // accessing thread local storage data from loaded libraries
61
  // (https://github.com/google/sanitizers/issues/1265), for this reason counter
62
  // needs to be annotated as initialized.
63
0
  ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(&counter, sizeof(size_t));
64
0
  size_t value = ++counter;
65
#else   // ABSL_HAVE_THREAD_LOCAL
66
  static std::atomic<size_t> counter(0);
67
  size_t value = counter.fetch_add(1, std::memory_order_relaxed);
68
#endif  // ABSL_HAVE_THREAD_LOCAL
69
0
  return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter));
70
0
}
71
72
0
bool ShouldRehashForBugDetection(const ctrl_t* ctrl, size_t capacity) {
73
  // Note: we can't use the abseil-random library because abseil-random
74
  // depends on swisstable. We want to return true with probability
75
  // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this,
76
  // we probe based on a random hash and see if the offset is less than
77
  // RehashProbabilityConstant().
78
0
  return probe(ctrl, capacity, absl::HashOf(RandomSeed())).offset() <
79
0
         RehashProbabilityConstant();
80
0
}
81
82
}  // namespace
83
84
0
GenerationType* EmptyGeneration() {
85
0
  if (SwisstableGenerationsEnabled()) {
86
0
    constexpr size_t kNumEmptyGenerations = 1024;
87
0
    static constexpr GenerationType kEmptyGenerations[kNumEmptyGenerations]{};
88
0
    return const_cast<GenerationType*>(
89
0
        &kEmptyGenerations[RandomSeed() % kNumEmptyGenerations]);
90
0
  }
91
0
  return nullptr;
92
0
}
93
94
bool CommonFieldsGenerationInfoEnabled::
95
    should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl,
96
0
                                              size_t capacity) const {
97
0
  if (reserved_growth_ == kReservedGrowthJustRanOut) return true;
98
0
  if (reserved_growth_ > 0) return false;
99
0
  return ShouldRehashForBugDetection(ctrl, capacity);
100
0
}
101
102
bool CommonFieldsGenerationInfoEnabled::should_rehash_for_bug_detection_on_move(
103
0
    const ctrl_t* ctrl, size_t capacity) const {
104
0
  return ShouldRehashForBugDetection(ctrl, capacity);
105
0
}
106
107
0
bool ShouldInsertBackwards(size_t hash, const ctrl_t* ctrl) {
108
  // To avoid problems with weak hashes and single bit tests, we use % 13.
109
  // TODO(kfm,sbenza): revisit after we do unconditional mixing
110
0
  return (H1(hash, ctrl) ^ RandomSeed()) % 13 > 6;
111
0
}
112
113
0
void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) {
114
0
  assert(ctrl[capacity] == ctrl_t::kSentinel);
115
0
  assert(IsValidCapacity(capacity));
116
0
  for (ctrl_t* pos = ctrl; pos < ctrl + capacity; pos += Group::kWidth) {
117
0
    Group{pos}.ConvertSpecialToEmptyAndFullToDeleted(pos);
118
0
  }
119
  // Copy the cloned ctrl bytes.
120
0
  std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes());
121
0
  ctrl[capacity] = ctrl_t::kSentinel;
122
0
}
123
// Extern template instantiation for inline function.
124
template FindInfo find_first_non_full(const CommonFields&, size_t);
125
126
FindInfo find_first_non_full_outofline(const CommonFields& common,
127
8.16k
                                       size_t hash) {
128
8.16k
  return find_first_non_full(common, hash);
129
8.16k
}
130
131
// Returns the address of the slot just after slot assuming each slot has the
132
// specified size.
133
0
static inline void* NextSlot(void* slot, size_t slot_size) {
134
0
  return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + slot_size);
135
0
}
136
137
// Returns the address of the slot just before slot assuming each slot has the
138
// specified size.
139
0
static inline void* PrevSlot(void* slot, size_t slot_size) {
140
0
  return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size);
141
0
}
142
143
void DropDeletesWithoutResize(CommonFields& common,
144
0
                              const PolicyFunctions& policy, void* tmp_space) {
145
0
  void* set = &common;
146
0
  void* slot_array = common.slot_array();
147
0
  const size_t capacity = common.capacity();
148
0
  assert(IsValidCapacity(capacity));
149
0
  assert(!is_small(capacity));
150
  // Algorithm:
151
  // - mark all DELETED slots as EMPTY
152
  // - mark all FULL slots as DELETED
153
  // - for each slot marked as DELETED
154
  //     hash = Hash(element)
155
  //     target = find_first_non_full(hash)
156
  //     if target is in the same group
157
  //       mark slot as FULL
158
  //     else if target is EMPTY
159
  //       transfer element to target
160
  //       mark slot as EMPTY
161
  //       mark target as FULL
162
  //     else if target is DELETED
163
  //       swap current element with target element
164
  //       mark target as FULL
165
  //       repeat procedure for current slot with moved from element (target)
166
0
  ctrl_t* ctrl = common.control();
167
0
  ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity);
168
0
  auto hasher = policy.hash_slot;
169
0
  auto transfer = policy.transfer;
170
0
  const size_t slot_size = policy.slot_size;
171
172
0
  size_t total_probe_length = 0;
173
0
  void* slot_ptr = SlotAddress(slot_array, 0, slot_size);
174
0
  for (size_t i = 0; i != capacity;
175
0
       ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) {
176
0
    assert(slot_ptr == SlotAddress(slot_array, i, slot_size));
177
0
    if (!IsDeleted(ctrl[i])) continue;
178
0
    const size_t hash = (*hasher)(set, slot_ptr);
179
0
    const FindInfo target = find_first_non_full(common, hash);
180
0
    const size_t new_i = target.offset;
181
0
    total_probe_length += target.probe_length;
182
183
    // Verify if the old and new i fall within the same group wrt the hash.
184
    // If they do, we don't need to move the object as it falls already in the
185
    // best probe we can.
186
0
    const size_t probe_offset = probe(common, hash).offset();
187
0
    const auto probe_index = [probe_offset, capacity](size_t pos) {
188
0
      return ((pos - probe_offset) & capacity) / Group::kWidth;
189
0
    };
190
191
    // Element doesn't move.
192
0
    if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) {
193
0
      SetCtrl(common, i, H2(hash), slot_size);
194
0
      continue;
195
0
    }
196
197
0
    void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size);
198
0
    if (IsEmpty(ctrl[new_i])) {
199
      // Transfer element to the empty spot.
200
      // SetCtrl poisons/unpoisons the slots so we have to call it at the
201
      // right time.
202
0
      SetCtrl(common, new_i, H2(hash), slot_size);
203
0
      (*transfer)(set, new_slot_ptr, slot_ptr);
204
0
      SetCtrl(common, i, ctrl_t::kEmpty, slot_size);
205
0
    } else {
206
0
      assert(IsDeleted(ctrl[new_i]));
207
0
      SetCtrl(common, new_i, H2(hash), slot_size);
208
      // Until we are done rehashing, DELETED marks previously FULL slots.
209
210
      // Swap i and new_i elements.
211
0
      (*transfer)(set, tmp_space, new_slot_ptr);
212
0
      (*transfer)(set, new_slot_ptr, slot_ptr);
213
0
      (*transfer)(set, slot_ptr, tmp_space);
214
215
      // repeat the processing of the ith slot
216
0
      --i;
217
0
      slot_ptr = PrevSlot(slot_ptr, slot_size);
218
0
    }
219
0
  }
220
0
  ResetGrowthLeft(common);
221
0
  common.infoz().RecordRehash(total_probe_length);
222
0
}
223
224
0
static bool WasNeverFull(CommonFields& c, size_t index) {
225
0
  if (is_single_group(c.capacity())) {
226
0
    return true;
227
0
  }
228
0
  const size_t index_before = (index - Group::kWidth) & c.capacity();
229
0
  const auto empty_after = Group(c.control() + index).MaskEmpty();
230
0
  const auto empty_before = Group(c.control() + index_before).MaskEmpty();
231
232
  // We count how many consecutive non empties we have to the right and to the
233
  // left of `it`. If the sum is >= kWidth then there is at least one probe
234
  // window that might have seen a full group.
235
0
  return empty_before && empty_after &&
236
0
         static_cast<size_t>(empty_after.TrailingZeros()) +
237
0
                 empty_before.LeadingZeros() <
238
0
             Group::kWidth;
239
0
}
240
241
0
void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size) {
242
0
  assert(IsFull(c.control()[index]) && "erasing a dangling iterator");
243
0
  c.decrement_size();
244
0
  c.infoz().RecordErase();
245
246
0
  if (WasNeverFull(c, index)) {
247
0
    SetCtrl(c, index, ctrl_t::kEmpty, slot_size);
248
0
    c.set_growth_left(c.growth_left() + 1);
249
0
    return;
250
0
  }
251
252
0
  SetCtrl(c, index, ctrl_t::kDeleted, slot_size);
253
0
}
254
255
void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy,
256
2.04k
                       bool reuse) {
257
2.04k
  c.set_size(0);
258
2.04k
  if (reuse) {
259
2.04k
    ResetCtrl(c, policy.slot_size);
260
2.04k
    ResetGrowthLeft(c);
261
2.04k
    c.infoz().RecordStorageChanged(0, c.capacity());
262
2.04k
  } else {
263
    // We need to record infoz before calling dealloc, which will unregister
264
    // infoz.
265
0
    c.infoz().RecordClearedReservation();
266
0
    c.infoz().RecordStorageChanged(0, 0);
267
0
    (*policy.dealloc)(c, policy);
268
0
    c.set_control(EmptyGroup());
269
0
    c.set_generation_ptr(EmptyGeneration());
270
0
    c.set_slots(nullptr);
271
0
    c.set_capacity(0);
272
0
  }
273
2.04k
}
274
275
void HashSetResizeHelper::GrowIntoSingleGroupShuffleControlBytes(
276
2.04k
    ctrl_t* new_ctrl, size_t new_capacity) const {
277
2.04k
  assert(is_single_group(new_capacity));
278
2.04k
  constexpr size_t kHalfWidth = Group::kWidth / 2;
279
2.04k
  assert(old_capacity_ < kHalfWidth);
280
281
2.04k
  const size_t half_old_capacity = old_capacity_ / 2;
282
283
  // NOTE: operations are done with compile time known size = kHalfWidth.
284
  // Compiler optimizes that into single ASM operation.
285
286
  // Copy second half of bytes to the beginning.
287
  // We potentially copy more bytes in order to have compile time known size.
288
  // Mirrored bytes from the old_ctrl_ will also be copied.
289
  // In case of old_capacity_ == 3, we will copy 1st element twice.
290
  // Examples:
291
  // old_ctrl = 0S0EEEEEEE...
292
  // new_ctrl = S0EEEEEEEE...
293
  //
294
  // old_ctrl = 01S01EEEEE...
295
  // new_ctrl = 1S01EEEEEE...
296
  //
297
  // old_ctrl = 0123456S0123456EE...
298
  // new_ctrl = 456S0123?????????...
299
2.04k
  std::memcpy(new_ctrl, old_ctrl_ + half_old_capacity + 1, kHalfWidth);
300
  // Clean up copied kSentinel from old_ctrl.
301
2.04k
  new_ctrl[half_old_capacity] = ctrl_t::kEmpty;
302
303
  // Clean up damaged or uninitialized bytes.
304
305
  // Clean bytes after the intended size of the copy.
306
  // Example:
307
  // new_ctrl = 1E01EEEEEEE????
308
  // *new_ctrl= 1E0EEEEEEEE????
309
  // position      /
310
2.04k
  std::memset(new_ctrl + old_capacity_ + 1, static_cast<int8_t>(ctrl_t::kEmpty),
311
2.04k
              kHalfWidth);
312
  // Clean non-mirrored bytes that are not initialized.
313
  // For small old_capacity that may be inside of mirrored bytes zone.
314
  // Examples:
315
  // new_ctrl = 1E0EEEEEEEE??????????....
316
  // *new_ctrl= 1E0EEEEEEEEEEEEE?????....
317
  // position           /
318
  //
319
  // new_ctrl = 456E0123???????????...
320
  // *new_ctrl= 456E0123EEEEEEEE???...
321
  // position           /
322
2.04k
  std::memset(new_ctrl + kHalfWidth, static_cast<int8_t>(ctrl_t::kEmpty),
323
2.04k
              kHalfWidth);
324
  // Clean last mirrored bytes that are not initialized
325
  // and will not be overwritten by mirroring.
326
  // Examples:
327
  // new_ctrl = 1E0EEEEEEEEEEEEE????????
328
  // *new_ctrl= 1E0EEEEEEEEEEEEEEEEEEEEE
329
  // position           S       /
330
  //
331
  // new_ctrl = 456E0123EEEEEEEE???????????????
332
  // *new_ctrl= 456E0123EEEEEEEE???????EEEEEEEE
333
  // position                  S       /
334
2.04k
  std::memset(new_ctrl + new_capacity + kHalfWidth,
335
2.04k
              static_cast<int8_t>(ctrl_t::kEmpty), kHalfWidth);
336
337
  // Create mirrored bytes. old_capacity_ < kHalfWidth
338
  // Example:
339
  // new_ctrl = 456E0123EEEEEEEE???????EEEEEEEE
340
  // *new_ctrl= 456E0123EEEEEEEE456E0123EEEEEEE
341
  // position                  S/
342
2.04k
  ctrl_t g[kHalfWidth];
343
2.04k
  std::memcpy(g, new_ctrl, kHalfWidth);
344
2.04k
  std::memcpy(new_ctrl + new_capacity + 1, g, kHalfWidth);
345
346
  // Finally set sentinel to its place.
347
2.04k
  new_ctrl[new_capacity] = ctrl_t::kSentinel;
348
2.04k
}
349
350
void HashSetResizeHelper::GrowIntoSingleGroupShuffleTransferableSlots(
351
0
    void* old_slots, void* new_slots, size_t slot_size) const {
352
0
  assert(old_capacity_ > 0);
353
0
  const size_t half_old_capacity = old_capacity_ / 2;
354
355
0
  SanitizerUnpoisonMemoryRegion(old_slots, slot_size * old_capacity_);
356
0
  std::memcpy(new_slots,
357
0
              SlotAddress(old_slots, half_old_capacity + 1, slot_size),
358
0
              slot_size * half_old_capacity);
359
0
  std::memcpy(SlotAddress(new_slots, half_old_capacity + 1, slot_size),
360
0
              old_slots, slot_size * (half_old_capacity + 1));
361
0
}
362
363
void HashSetResizeHelper::GrowSizeIntoSingleGroupTransferable(
364
0
    CommonFields& c, void* old_slots, size_t slot_size) {
365
0
  assert(old_capacity_ < Group::kWidth / 2);
366
0
  assert(is_single_group(c.capacity()));
367
0
  assert(IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity()));
368
369
0
  GrowIntoSingleGroupShuffleControlBytes(c.control(), c.capacity());
370
0
  GrowIntoSingleGroupShuffleTransferableSlots(old_slots, c.slot_array(),
371
0
                                              slot_size);
372
373
  // We poison since GrowIntoSingleGroupShuffleTransferableSlots
374
  // may leave empty slots unpoisoned.
375
0
  PoisonSingleGroupEmptySlots(c, slot_size);
376
0
}
377
378
}  // namespace container_internal
379
ABSL_NAMESPACE_END
380
}  // namespace absl