proxygen
F14Table.h
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <cstddef>
20 #include <cstdint>
21 #include <cstring>
22 
23 #include <array>
24 #include <iterator>
25 #include <limits>
26 #include <memory>
27 #include <new>
28 #include <type_traits>
29 #include <utility>
30 #include <vector>
31 
32 #include <folly/Bits.h>
33 #include <folly/ConstexprMath.h>
34 #include <folly/Likely.h>
35 #include <folly/Portability.h>
36 #include <folly/ScopeGuard.h>
37 #include <folly/Traits.h>
40 #include <folly/lang/Align.h>
41 #include <folly/lang/Assume.h>
42 #include <folly/lang/Exception.h>
43 #include <folly/lang/Launder.h>
44 #include <folly/lang/SafeAssert.h>
46 
50 
51 #if FOLLY_ASAN_ENABLED && defined(FOLLY_TLS)
52 #define FOLLY_F14_TLS_IF_ASAN FOLLY_TLS
53 #else
54 #define FOLLY_F14_TLS_IF_ASAN
55 #endif
56 
57 #if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
58 
59 #if FOLLY_F14_CRC_INTRINSIC_AVAILABLE
60 #if FOLLY_NEON
61 #include <arm_acle.h> // __crc32cd
62 #else
63 #include <nmmintrin.h> // _mm_crc32_u64
64 #endif
65 #else
66 #ifdef _WIN32
67 #include <intrin.h> // _mul128 in fallback bit mixer
68 #endif
69 #endif
70 
71 #if FOLLY_NEON
72 #include <arm_neon.h> // uint8x16t intrinsics
73 #else // SSE2
74 #include <immintrin.h> // __m128i intrinsics
75 #include <xmmintrin.h> // _mm_prefetch
76 #endif
77 
78 #endif
79 
80 namespace folly {
81 
82 struct F14TableStats {
83  char const* policy;
84  std::size_t size{0};
85  std::size_t valueSize{0};
86  std::size_t bucketCount{0};
87  std::size_t chunkCount{0};
88  std::vector<std::size_t> chunkOccupancyHisto;
89  std::vector<std::size_t> chunkOutboundOverflowHisto;
90  std::vector<std::size_t> chunkHostedOverflowHisto;
91  std::vector<std::size_t> keyProbeLengthHisto;
92  std::vector<std::size_t> missProbeLengthHisto;
93  std::size_t totalBytes{0};
94  std::size_t overheadBytes{0};
95 
96  private:
97  template <typename T>
98  static auto computeHelper(T const* m) -> decltype(m->computeStats()) {
99  return m->computeStats();
100  }
101 
103  return {};
104  }
105 
106  public:
107  template <typename T>
108  static F14TableStats compute(T const& m) {
109  return computeHelper(&m);
110  }
111 };
112 
113 namespace f14 {
114 namespace detail {
115 
116 template <F14IntrinsicsMode>
117 struct F14LinkCheck {};
118 
119 template <>
121  // The purpose of this method is to trigger a link failure if
122  // compilation flags vary across compilation units. The definition
123  // is in F14Table.cpp, so only one of F14LinkCheck<None>::check,
124  // F14LinkCheck<Simd>::check, or F14LinkCheck<SimdAndCrc>::check will
125  // be available at link time.
126  //
127  // To cause a link failure the function must be invoked in code that
128  // is not optimized away, so we call it on a couple of cold paths
129  // (exception handling paths in copy construction and rehash). LTO may
130  // remove it entirely, but that's fine.
131  static void check() noexcept;
132 };
133 
134 #if defined(_LIBCPP_VERSION)
135 
136 template <typename K, typename V, typename H>
137 struct StdNodeReplica {
138  void* next;
139  std::size_t hash;
140  V value;
141 };
142 
143 #else
144 
145 template <typename H>
147 template <>
148 struct StdIsFastHash<std::hash<long double>> : std::false_type {};
149 template <typename... Args>
150 struct StdIsFastHash<std::hash<std::basic_string<Args...>>> : std::false_type {
151 };
152 
153 // TODO: add specialization for std::basic_string_view
154 
155 // mimic internal node of unordered containers in STL to estimate the size
156 template <typename K, typename V, typename H, typename Enable = void>
158  void* next;
159  V value;
160 };
161 template <typename K, typename V, typename H>
163  K,
164  V,
165  H,
166  std::enable_if_t<
167  !StdIsFastHash<H>::value || !is_nothrow_invocable<H, K>::value>> {
168  void* next;
169  V value;
170  std::size_t hash;
171 };
172 
173 #endif
174 
175 } // namespace detail
176 } // namespace f14
177 
178 #if FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
179 namespace f14 {
180 namespace detail {
181 template <typename Policy>
182 class F14Table;
183 } // namespace detail
184 } // namespace f14
185 
186 class F14HashToken final {
187  public:
188  F14HashToken() = default;
189 
190  private:
191  using HashPair = std::pair<std::size_t, std::size_t>;
192 
193  explicit F14HashToken(HashPair hp) : hp_(hp) {}
194  explicit operator HashPair() const {
195  return hp_;
196  }
197 
198  HashPair hp_;
199 
200  template <typename Policy>
201  friend class f14::detail::F14Table;
202 };
203 
204 namespace f14 {
205 namespace detail {
207 
208 template <typename Arg, typename Default>
209 using VoidDefault =
211 
212 template <typename Arg, typename Default>
213 using Defaulted =
215 
216 template <
217  typename TableKey,
218  typename Hasher,
219  typename KeyEqual,
220  typename ArgKey,
221  typename Void = void>
222 struct EligibleForHeterogeneousFind : std::false_type {};
223 
224 template <
225  typename TableKey,
226  typename Hasher,
227  typename KeyEqual,
228  typename ArgKey>
229 struct EligibleForHeterogeneousFind<
230  TableKey,
231  Hasher,
232  KeyEqual,
233  ArgKey,
234  void_t<typename Hasher::is_transparent, typename KeyEqual::is_transparent>>
235  : std::true_type {};
236 
237 template <
238  typename TableKey,
239  typename Hasher,
240  typename KeyEqual,
241  typename ArgKey>
242 using EligibleForHeterogeneousInsert = Conjunction<
243  EligibleForHeterogeneousFind<TableKey, Hasher, KeyEqual, ArgKey>,
244  std::is_constructible<TableKey, ArgKey>>;
245 
246 template <
247  typename TableKey,
248  typename Hasher,
249  typename KeyEqual,
250  typename KeyArg0OrBool,
251  typename... KeyArgs>
252 using KeyTypeForEmplaceHelper = std::conditional_t<
253  sizeof...(KeyArgs) == 1 &&
254  (std::is_same<remove_cvref_t<KeyArg0OrBool>, TableKey>::value ||
255  EligibleForHeterogeneousFind<
256  TableKey,
257  Hasher,
258  KeyEqual,
259  KeyArg0OrBool>::value),
260  KeyArg0OrBool&&,
261  TableKey>;
262 
263 template <
264  typename TableKey,
265  typename Hasher,
266  typename KeyEqual,
267  typename... KeyArgs>
268 using KeyTypeForEmplace = KeyTypeForEmplaceHelper<
269  TableKey,
270  Hasher,
271  KeyEqual,
272  std::tuple_element_t<0, std::tuple<KeyArgs..., bool>>,
273  KeyArgs...>;
274 
276 
277 template <typename T>
278 FOLLY_ALWAYS_INLINE static void prefetchAddr(T const* ptr) {
279 #ifndef _WIN32
280  __builtin_prefetch(static_cast<void const*>(ptr));
281 #elif FOLLY_NEON
282  __prefetch(static_cast<void const*>(ptr));
283 #else
284  _mm_prefetch(
285  static_cast<char const*>(static_cast<void const*>(ptr)), _MM_HINT_T0);
286 #endif
287 }
288 
289 template <typename T>
290 FOLLY_ALWAYS_INLINE static unsigned findFirstSetNonZero(T mask) {
291  assume(mask != 0);
292  if (sizeof(mask) == sizeof(unsigned)) {
293  return __builtin_ctz(static_cast<unsigned>(mask));
294  } else {
295  return __builtin_ctzll(mask);
296  }
297 }
298 
299 #if FOLLY_NEON
300 using TagVector = uint8x16_t;
301 
302 using MaskType = uint64_t;
303 
304 constexpr unsigned kMaskSpacing = 4;
305 #else // SSE2
306 using TagVector = __m128i;
307 
308 using MaskType = uint32_t;
309 
310 constexpr unsigned kMaskSpacing = 1;
311 #endif
312 
313 // We could use unaligned loads to relax this requirement, but that
314 // would be both a performance penalty and require a bulkier packed
315 // ItemIter format
316 constexpr std::size_t kRequiredVectorAlignment =
317  constexpr_max(std::size_t{16}, alignof(max_align_t));
318 
319 using EmptyTagVectorType = std::aligned_storage_t<
320  sizeof(TagVector) + kRequiredVectorAlignment,
321  alignof(max_align_t)>;
322 
323 extern EmptyTagVectorType kEmptyTagVector;
324 
326 extern FOLLY_F14_TLS_IF_ASAN std::size_t asanRehashState;
327 
328 template <unsigned BitCount>
329 struct FullMask {
330  static constexpr MaskType value =
331  (FullMask<BitCount - 1>::value << kMaskSpacing) + 1;
332 };
333 
334 template <>
335 struct FullMask<1> : std::integral_constant<MaskType, 1> {};
336 
337 #if FOLLY_ARM
338 // Mask iteration is different for ARM because that is the only platform
339 // for which the mask is bigger than a register.
340 
341 // Iterates a mask, optimized for the case that only a few bits are set
342 class SparseMaskIter {
343  static_assert(kMaskSpacing == 4, "");
344 
345  uint32_t interleavedMask_;
346 
347  public:
348  explicit SparseMaskIter(MaskType mask)
349  : interleavedMask_{static_cast<uint32_t>(((mask >> 32) << 2) | mask)} {}
350 
351  bool hasNext() {
352  return interleavedMask_ != 0;
353  }
354 
355  unsigned next() {
356  FOLLY_SAFE_DCHECK(hasNext(), "");
357  unsigned i = findFirstSetNonZero(interleavedMask_);
358  interleavedMask_ &= (interleavedMask_ - 1);
359  return ((i >> 2) | (i << 2)) & 0xf;
360  }
361 };
362 
363 // Iterates a mask, optimized for the case that most bits are set
364 class DenseMaskIter {
365  static_assert(kMaskSpacing == 4, "");
366 
367  std::size_t count_;
368  unsigned index_;
369  uint8_t const* tags_;
370 
371  public:
372  explicit DenseMaskIter(uint8_t const* tags, MaskType mask) {
373  if (mask == 0) {
374  count_ = 0;
375  } else {
376  count_ = popcount(static_cast<uint32_t>(((mask >> 32) << 2) | mask));
377  if (LIKELY((mask & 1) != 0)) {
378  index_ = 0;
379  } else {
380  index_ = findFirstSetNonZero(mask) / kMaskSpacing;
381  }
382  tags_ = tags;
383  }
384  }
385 
386  bool hasNext() {
387  return count_ > 0;
388  }
389 
390  unsigned next() {
391  auto rv = index_;
392  --count_;
393  if (count_ > 0) {
394  do {
395  ++index_;
396  } while ((tags_[index_] & 0x80) == 0);
397  }
398  FOLLY_SAFE_DCHECK(index_ < 16, "");
399  return rv;
400  }
401 };
402 
403 #else
404 // Iterates a mask, optimized for the case that only a few bits are set
405 class SparseMaskIter {
406  MaskType mask_;
407 
408  public:
409  explicit SparseMaskIter(MaskType mask) : mask_{mask} {}
410 
411  bool hasNext() {
412  return mask_ != 0;
413  }
414 
415  unsigned next() {
416  FOLLY_SAFE_DCHECK(hasNext(), "");
417  unsigned i = findFirstSetNonZero(mask_);
418  mask_ &= (mask_ - 1);
419  return i / kMaskSpacing;
420  }
421 };
422 
423 // Iterates a mask, optimized for the case that most bits are set
424 class DenseMaskIter {
425  MaskType mask_;
426  unsigned index_{0};
427 
428  public:
429  explicit DenseMaskIter(uint8_t const*, MaskType mask) : mask_{mask} {}
430 
431  bool hasNext() {
432  return mask_ != 0;
433  }
434 
435  unsigned next() {
436  FOLLY_SAFE_DCHECK(hasNext(), "");
437  if (LIKELY((mask_ & 1) != 0)) {
438  mask_ >>= kMaskSpacing;
439  return index_++;
440  } else {
441  unsigned s = findFirstSetNonZero(mask_);
442  unsigned rv = index_ + (s / kMaskSpacing);
443  mask_ >>= (s + kMaskSpacing);
444  index_ = rv + 1;
445  return rv;
446  }
447  }
448 };
449 #endif
450 
451 // Iterates a mask, returning pairs of [begin,end) index covering blocks
452 // of set bits
453 class MaskRangeIter {
454  MaskType mask_;
455  unsigned shift_{0};
456 
457  public:
458  explicit MaskRangeIter(MaskType mask) {
459  // If kMaskSpacing is > 1 then there will be empty bits even for
460  // contiguous ranges. Fill them in.
461  mask_ = mask * ((1 << kMaskSpacing) - 1);
462  }
463 
464  bool hasNext() {
465  return mask_ != 0;
466  }
467 
468  std::pair<unsigned, unsigned> next() {
469  FOLLY_SAFE_DCHECK(hasNext(), "");
470  auto s = shift_;
471  unsigned b = findFirstSetNonZero(mask_);
472  unsigned e = findFirstSetNonZero(~(mask_ | (mask_ - 1)));
473  mask_ >>= e;
474  shift_ = s + e;
475  return std::make_pair((s + b) / kMaskSpacing, (s + e) / kMaskSpacing);
476  }
477 };
478 
479 // Holds the result of an index query that has an optional result,
480 // interpreting a mask of 0 to be the empty answer and the index of the
481 // last set bit to be the non-empty answer
482 class LastOccupiedInMask {
483  MaskType mask_;
484 
485  public:
486  explicit LastOccupiedInMask(MaskType mask) : mask_{mask} {}
487 
488  bool hasIndex() const {
489  return mask_ != 0;
490  }
491 
492  unsigned index() const {
493  assume(mask_ != 0);
494  return (findLastSet(mask_) - 1) / kMaskSpacing;
495  }
496 };
497 
498 // Holds the result of an index query that has an optional result,
499 // interpreting a mask of 0 to be the empty answer and the index of the
500 // first set bit to be the non-empty answer
501 class FirstEmptyInMask {
502  MaskType mask_;
503 
504  public:
505  explicit FirstEmptyInMask(MaskType mask) : mask_{mask} {}
506 
507  bool hasIndex() const {
508  return mask_ != 0;
509  }
510 
511  unsigned index() const {
512  FOLLY_SAFE_DCHECK(mask_ != 0, "");
513  return findFirstSetNonZero(mask_) / kMaskSpacing;
514  }
515 };
516 
517 template <typename ItemType>
518 struct alignas(kRequiredVectorAlignment) F14Chunk {
519  using Item = ItemType;
520 
521  // For our 16 byte vector alignment (and assuming alignof(Item) >=
522  // 4) kCapacity of 14 is the most space efficient. Slightly smaller
523  // or larger capacities can help with cache alignment in a couple of
524  // cases without wasting too much space, but once the items are larger
525  // then we're unlikely to get much benefit anyway. The only case we
526  // optimize is using kCapacity of 12 for 4 byte items, which makes the
527  // chunk take exactly 1 cache line, and adding 16 bytes of padding for
528  // 16 byte items so that a chunk takes exactly 4 cache lines.
529  static constexpr unsigned kCapacity = sizeof(Item) == 4 ? 12 : 14;
530 
531  static constexpr unsigned kDesiredCapacity = kCapacity - 2;
532 
533  static constexpr unsigned kAllocatedCapacity =
534  kCapacity + (sizeof(Item) == 16 ? 1 : 0);
535 
536  static constexpr MaskType kFullMask = FullMask<kCapacity>::value;
537 
538  // Non-empty tags have their top bit set. tags_ array might be bigger
539  // than kCapacity to keep alignment of first item.
540  std::array<uint8_t, 14> tags_;
541 
542  // Bits 0..3 record the actual capacity of the chunk if this is chunk
543  // zero, or hold 0000 for other chunks. Bits 4-7 are a 4-bit counter
544  // of the number of values in this chunk that were placed because they
545  // overflowed their desired chunk (hostedOverflowCount).
546  uint8_t control_;
547 
548  // The number of values that would have been placed into this chunk if
549  // there had been space, including values that also overflowed previous
550  // full chunks. This value saturates; once it becomes 255 it no longer
551  // increases nor decreases.
552  uint8_t outboundOverflowCount_;
553 
554  std::array<
555  std::aligned_storage_t<sizeof(Item), alignof(Item)>,
556  kAllocatedCapacity>
557  rawItems_;
558 
559  static F14Chunk* emptyInstance() {
560  auto raw = reinterpret_cast<char*>(&kEmptyTagVector);
561  if (kRequiredVectorAlignment > alignof(max_align_t)) {
562  auto delta = kRequiredVectorAlignment -
563  (reinterpret_cast<uintptr_t>(raw) % kRequiredVectorAlignment);
564  raw += delta;
565  }
566  auto rv = reinterpret_cast<F14Chunk*>(raw);
568  (reinterpret_cast<uintptr_t>(rv) % kRequiredVectorAlignment) == 0, "");
569  return rv;
570  }
571 
572  void clear() {
573  // tags_ = {}; control_ = 0; outboundOverflowCount_ = 0;
574 
575  // gcc < 6 doesn't exploit chunk alignment to generate the optimal
576  // SSE clear from memset. This is very hot code, so it is worth
577  // handling that case specially.
578 #if FOLLY_SSE >= 2 && __GNUC__ <= 5 && !__clang__
579  // this doesn't violate strict aliasing rules because __m128i is
580  // tagged as __may_alias__
581  auto* v = static_cast<__m128i*>(static_cast<void*>(&tags_[0]));
582  _mm_store_si128(v, _mm_setzero_si128());
583 #else
584  std::memset(&tags_[0], '\0', 16);
585 #endif
586  }
587 
588  void copyOverflowInfoFrom(F14Chunk const& rhs) {
589  FOLLY_SAFE_DCHECK(hostedOverflowCount() == 0, "");
590  control_ += static_cast<uint8_t>(rhs.control_ & 0xf0);
591  outboundOverflowCount_ = rhs.outboundOverflowCount_;
592  }
593 
594  unsigned hostedOverflowCount() const {
595  return control_ >> 4;
596  }
597 
598  static constexpr uint8_t kIncrHostedOverflowCount = 0x10;
599  static constexpr uint8_t kDecrHostedOverflowCount =
600  static_cast<uint8_t>(-0x10);
601 
602  void adjustHostedOverflowCount(uint8_t op) {
603  control_ += op;
604  }
605 
606  bool eof() const {
607  return (control_ & 0xf) != 0;
608  }
609 
610  std::size_t chunk0Capacity() const {
611  return control_ & 0xf;
612  }
613 
614  void markEof(std::size_t c0c) {
616  this != emptyInstance() && control_ == 0 && c0c > 0 && c0c <= 0xf &&
617  c0c <= kCapacity,
618  "");
619  control_ = static_cast<uint8_t>(c0c);
620  }
621 
622  unsigned outboundOverflowCount() const {
623  return outboundOverflowCount_;
624  }
625 
626  void incrOutboundOverflowCount() {
627  if (outboundOverflowCount_ != 255) {
628  ++outboundOverflowCount_;
629  }
630  }
631 
632  void decrOutboundOverflowCount() {
633  if (outboundOverflowCount_ != 255) {
634  --outboundOverflowCount_;
635  }
636  }
637 
638  std::size_t tag(std::size_t index) const {
639  return tags_[index];
640  }
641 
642  void setTag(std::size_t index, std::size_t tag) {
644  this != emptyInstance() && tag >= 0x80 && tag <= 0xff, "");
645  tags_[index] = static_cast<uint8_t>(tag);
646  }
647 
648  void clearTag(std::size_t index) {
649  tags_[index] = 0;
650  }
651 
652 #if FOLLY_NEON
653  // Tag filtering using NEON intrinsics
655 
656  SparseMaskIter tagMatchIter(std::size_t needle) const {
657  FOLLY_SAFE_DCHECK(needle >= 0x80 && needle < 0x100, "");
658  uint8x16_t tagV = vld1q_u8(&tags_[0]);
659  auto needleV = vdupq_n_u8(static_cast<uint8_t>(needle));
660  auto eqV = vceqq_u8(tagV, needleV);
661  // get info from every byte into the bottom half of every uint16_t
662  // by shifting right 4, then round to get it into a 64-bit vector
663  uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(eqV), 4);
664  uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(maskV), 0) & kFullMask;
665  return SparseMaskIter(mask);
666  }
667 
668  MaskType occupiedMask() const {
669  uint8x16_t tagV = vld1q_u8(&tags_[0]);
670  // signed shift extends top bit to all bits
671  auto occupiedV =
672  vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_u8(tagV), 7));
673  uint8x8_t maskV = vshrn_n_u16(vreinterpretq_u16_u8(occupiedV), 4);
674  return vget_lane_u64(vreinterpret_u64_u8(maskV), 0) & kFullMask;
675  }
676 #else
677  // Tag filtering using SSE2 intrinsics
679 
680  TagVector const* tagVector() const {
681  return static_cast<TagVector const*>(static_cast<void const*>(&tags_[0]));
682  }
683 
684  SparseMaskIter tagMatchIter(std::size_t needle) const {
685  FOLLY_SAFE_DCHECK(needle >= 0x80 && needle < 0x100, "");
686  auto tagV = _mm_load_si128(tagVector());
687 
688  // TRICKY! It may seem strange to have a std::size_t needle and narrow
689  // it at the last moment, rather than making HashPair::second be a
690  // uint8_t, but the latter choice sometimes leads to a performance
691  // problem.
692  //
693  // On architectures with SSE2 but not AVX2, _mm_set1_epi8 expands
694  // to multiple instructions. One of those is a MOVD of either 4 or
695  // 8 byte width. Only the bottom byte of that move actually affects
696  // the result, but if a 1-byte needle has been spilled then this will
697  // be a 4 byte load. GCC 5.5 has been observed to reload needle
698  // (or perhaps fuse a reload and part of a previous static_cast)
699  // needle using a MOVZX with a 1 byte load in parallel with the MOVD.
700  // This combination causes a failure of store-to-load forwarding,
701  // which has a big performance penalty (60 nanoseconds per find on
702  // a microbenchmark). Keeping needle >= 4 bytes avoids the problem
703  // and also happens to result in slightly more compact assembly.
704  auto needleV = _mm_set1_epi8(static_cast<uint8_t>(needle));
705  auto eqV = _mm_cmpeq_epi8(tagV, needleV);
706  auto mask = _mm_movemask_epi8(eqV) & kFullMask;
707  return SparseMaskIter{mask};
708  }
709 
710  MaskType occupiedMask() const {
711  auto tagV = _mm_load_si128(tagVector());
712  return _mm_movemask_epi8(tagV) & kFullMask;
713  }
714 #endif
715 
716  DenseMaskIter occupiedIter() const {
717  return DenseMaskIter{&tags_[0], occupiedMask()};
718  }
719 
720  MaskRangeIter occupiedRangeIter() const {
721  return MaskRangeIter{occupiedMask()};
722  }
723 
724  LastOccupiedInMask lastOccupied() const {
725  return LastOccupiedInMask{occupiedMask()};
726  }
727 
728  FirstEmptyInMask firstEmpty() const {
729  return FirstEmptyInMask{occupiedMask() ^ kFullMask};
730  }
731 
732  bool occupied(std::size_t index) const {
733  FOLLY_SAFE_DCHECK(tags_[index] == 0 || (tags_[index] & 0x80) != 0, "");
734  return tags_[index] != 0;
735  }
736 
737  Item* itemAddr(std::size_t i) const {
738  return static_cast<Item*>(
739  const_cast<void*>(static_cast<void const*>(&rawItems_[i])));
740  }
741 
742  Item& item(std::size_t i) {
743  FOLLY_SAFE_DCHECK(this->occupied(i), "");
744  return *launder(itemAddr(i));
745  }
746 
747  Item const& citem(std::size_t i) const {
748  FOLLY_SAFE_DCHECK(this->occupied(i), "");
749  return *launder(itemAddr(i));
750  }
751 
752  static F14Chunk& owner(Item& item, std::size_t index) {
753  auto rawAddr =
754  static_cast<uint8_t*>(static_cast<void*>(std::addressof(item))) -
755  offsetof(F14Chunk, rawItems_) - index * sizeof(Item);
756  auto chunkAddr = static_cast<F14Chunk*>(static_cast<void*>(rawAddr));
757  FOLLY_SAFE_DCHECK(std::addressof(item) == chunkAddr->itemAddr(index), "");
758  return *chunkAddr;
759  }
760 };
761 
763 
764 // PackedChunkItemPtr points to an Item in an F14Chunk, allowing both the
765 // Item& and its index to be recovered. It sorts by the address of the
766 // item, and it only works for items that are in a properly-aligned chunk.
767 
768 // generic form, not actually packed
769 template <typename Ptr>
770 class PackedChunkItemPtr {
771  public:
772  PackedChunkItemPtr(Ptr p, std::size_t i) noexcept : ptr_{p}, index_{i} {
773  FOLLY_SAFE_DCHECK(ptr_ != nullptr || index_ == 0, "");
774  }
775 
776  Ptr ptr() const {
777  return ptr_;
778  }
779 
780  std::size_t index() const {
781  return index_;
782  }
783 
784  bool operator<(PackedChunkItemPtr const& rhs) const {
785  FOLLY_SAFE_DCHECK(ptr_ != rhs.ptr_ || index_ == rhs.index_, "");
786  return ptr_ < rhs.ptr_;
787  }
788 
789  bool operator==(PackedChunkItemPtr const& rhs) const {
790  FOLLY_SAFE_DCHECK(ptr_ != rhs.ptr_ || index_ == rhs.index_, "");
791  return ptr_ == rhs.ptr_;
792  }
793 
794  bool operator!=(PackedChunkItemPtr const& rhs) const {
795  return !(*this == rhs);
796  }
797 
798  private:
799  Ptr ptr_;
800  std::size_t index_;
801 };
802 
803 // Bare pointer form, packed into a uintptr_t. Uses only bits wasted by
804 // alignment, so it works on 32-bit and 64-bit platforms
805 template <typename T>
806 class PackedChunkItemPtr<T*> {
807  static_assert((alignof(F14Chunk<T>) % 16) == 0, "");
808 
809  // Chunks are 16-byte aligned, so we can maintain a packed pointer to a
810  // chunk item by packing the 4-bit item index into the least significant
811  // bits of a pointer to the chunk itself. This makes ItemIter::pack
812  // more expensive, however, since it has to compute the chunk address.
813  //
814  // Chunk items have varying alignment constraints, so it would seem
815  // to be that we can't do a similar trick while using only bit masking
816  // operations on the Item* itself. It happens to be, however, that if
817  // sizeof(Item) is not a multiple of 16 then we can recover a portion
818  // of the index bits from the knowledge that the Item-s are stored in
819  // an array that is itself 16-byte aligned.
820  //
821  // If kAlignBits is the number of trailing zero bits in sizeof(Item)
822  // (up to 4), then we can borrow those bits to store kAlignBits of the
823  // index directly. We can recover (4 - kAlignBits) bits of the index
824  // from the item pointer itself, by defining/observing that
825  //
826  // A = kAlignBits (A <= 4)
827  //
828  // S = (sizeof(Item) % 16) >> A (shifted-away bits are all zero)
829  //
830  // R = (itemPtr % 16) >> A (shifted-away bits are all zero)
831  //
832  // M = 16 >> A
833  //
834  // itemPtr % 16 = (index * sizeof(Item)) % 16
835  //
836  // (R * 2^A) % 16 = (index * (sizeof(Item) % 16)) % 16
837  //
838  // (R * 2^A) % 16 = (index * 2^A * S) % 16
839  //
840  // R % M = (index * S) % M
841  //
842  // S is relatively prime with M, so a multiplicative inverse is easy
843  // to compute
844  //
845  // Sinv = S^(M - 1) % M
846  //
847  // (R * Sinv) % M = index % M
848  //
849  // This lets us recover the bottom bits of the index. When sizeof(T)
850  // is 8-byte aligned kSizeInverse will always be 1. When sizeof(T)
851  // is 4-byte aligned kSizeInverse will be either 1 or 3.
852 
853  // returns pow(x, y) % m
854  static constexpr uintptr_t powerMod(uintptr_t x, uintptr_t y, uintptr_t m) {
855  return y == 0 ? 1 : (x * powerMod(x, y - 1, m)) % m;
856  }
857 
858  static constexpr uintptr_t kIndexBits = 4;
859  static constexpr uintptr_t kIndexMask = (uintptr_t{1} << kIndexBits) - 1;
860 
861  static constexpr uintptr_t kAlignBits = constexpr_min(
862  uintptr_t{4},
863  constexpr_find_first_set(uintptr_t{sizeof(T)}) - 1);
864 
865  static constexpr uintptr_t kAlignMask = (uintptr_t{1} << kAlignBits) - 1;
866 
867  static constexpr uintptr_t kModulus = uintptr_t{1}
868  << (kIndexBits - kAlignBits);
869  static constexpr uintptr_t kSizeInverse =
870  powerMod(sizeof(T) >> kAlignBits, kModulus - 1, kModulus);
871 
872  public:
873  PackedChunkItemPtr(T* p, std::size_t i) noexcept {
874  uintptr_t encoded = i >> (kIndexBits - kAlignBits);
875  assume((encoded & ~kAlignMask) == 0);
876  raw_ = reinterpret_cast<uintptr_t>(p) | encoded;
877  FOLLY_SAFE_DCHECK(p == ptr(), "");
878  FOLLY_SAFE_DCHECK(i == index(), "");
879  }
880 
881  T* ptr() const {
882  return reinterpret_cast<T*>(raw_ & ~kAlignMask);
883  }
884 
885  std::size_t index() const {
886  auto encoded = (raw_ & kAlignMask) << (kIndexBits - kAlignBits);
887  auto deduced =
888  ((raw_ >> kAlignBits) * kSizeInverse) & (kIndexMask >> kAlignBits);
889  return encoded | deduced;
890  }
891 
892  bool operator<(PackedChunkItemPtr const& rhs) const {
893  return raw_ < rhs.raw_;
894  }
895  bool operator==(PackedChunkItemPtr const& rhs) const {
896  return raw_ == rhs.raw_;
897  }
898  bool operator!=(PackedChunkItemPtr const& rhs) const {
899  return !(*this == rhs);
900  }
901 
902  private:
903  uintptr_t raw_;
904 };
905 
906 template <typename ChunkPtr>
907 class F14ItemIter {
908  private:
909  using Chunk = typename std::pointer_traits<ChunkPtr>::element_type;
910 
911  public:
912  using Item = typename Chunk::Item;
913  using ItemPtr = typename std::pointer_traits<ChunkPtr>::template rebind<Item>;
914  using ItemConstPtr =
915  typename std::pointer_traits<ChunkPtr>::template rebind<Item const>;
916 
917  using Packed = PackedChunkItemPtr<ItemPtr>;
918 
920 
921  F14ItemIter() noexcept : itemPtr_{nullptr}, index_{0} {}
922 
923  // default copy and move constructors and assignment operators are correct
924 
925  explicit F14ItemIter(Packed const& packed)
926  : itemPtr_{packed.ptr()}, index_{packed.index()} {}
927 
928  F14ItemIter(ChunkPtr chunk, std::size_t index)
929  : itemPtr_{std::pointer_traits<ItemPtr>::pointer_to(chunk->item(index))},
930  index_{index} {
931  FOLLY_SAFE_DCHECK(index < Chunk::kCapacity, "");
932  assume(
933  std::pointer_traits<ItemPtr>::pointer_to(chunk->item(index)) !=
934  nullptr);
935  assume(itemPtr_ != nullptr);
936  }
937 
938  FOLLY_ALWAYS_INLINE void advanceImpl(bool checkEof, bool likelyDead) {
939  auto c = chunk();
940 
941  // common case is packed entries
942  while (index_ > 0) {
943  --index_;
944  --itemPtr_;
945  if (LIKELY(c->occupied(index_))) {
946  return;
947  }
948  }
949 
950  // It's fairly common for an iterator to be advanced and then become
951  // dead, for example in the return value from erase(iter) or in
952  // the last step of a loop. We'd like to make sure that the entire
953  // advance() method can be eliminated by the compiler's dead code
954  // elimination pass. To do that it must eliminate the loops, which
955  // requires it to prove that they have no side effects. It's easy
956  // to show that there are no escaping stores, but at the moment
957  // compilers also consider an infinite loop to be a side effect.
958  // (There are parts of the standard that would allow them to treat
959  // this as undefined behavior, but at the moment they don't exploit
960  // those clauses.)
961  //
962  // The following loop should really be a while loop, which would
963  // save a register, some instructions, and a conditional branch,
964  // but by writing it as a for loop the compiler can prove to itself
965  // that it will eventually terminate. (No matter that even if the
966  // loop executed in a single cycle it would take about 200 years to
967  // run all 2^64 iterations.)
968  //
969  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82776 has the bug we
970  // filed about the issue. while (true) {
971  for (std::size_t i = 1; !likelyDead || i != 0; ++i) {
972  if (checkEof) {
973  // exhausted the current chunk
974  if (UNLIKELY(c->eof())) {
975  FOLLY_SAFE_DCHECK(index_ == 0, "");
976  itemPtr_ = nullptr;
977  return;
978  }
979  } else {
980  FOLLY_SAFE_DCHECK(!c->eof(), "");
981  }
982  --c;
983  auto last = c->lastOccupied();
984  if (checkEof && !likelyDead) {
985  prefetchAddr(&*c - 1);
986  }
987  if (LIKELY(last.hasIndex())) {
988  index_ = last.index();
989  itemPtr_ = std::pointer_traits<ItemPtr>::pointer_to(c->item(index_));
990  return;
991  }
992  }
993  }
994 
995  void precheckedAdvance() {
996  advanceImpl(false, false);
997  }
998 
1000  advanceImpl(true, false);
1001  }
1002 
1003  FOLLY_ALWAYS_INLINE void advanceLikelyDead() {
1004  advanceImpl(true, true);
1005  }
1006 
1007  ChunkPtr chunk() const {
1008  return std::pointer_traits<ChunkPtr>::pointer_to(
1009  Chunk::owner(*itemPtr_, index_));
1010  }
1011 
1012  std::size_t index() const {
1013  return index_;
1014  }
1015 
1016  Item* itemAddr() const {
1017  return std::addressof(*itemPtr_);
1018  }
1019  Item& item() const {
1020  return *itemPtr_;
1021  }
1022  Item const& citem() const {
1023  return *itemPtr_;
1024  }
1025 
1026  bool atEnd() const {
1027  return itemPtr_ == nullptr;
1028  }
1029 
1030  Packed pack() const {
1031  return Packed{itemPtr_, static_cast<uint8_t>(index_)};
1032  }
1033 
1034  bool operator==(F14ItemIter const& rhs) const {
1035  // this form makes iter == end() into a single null check after inlining
1036  // and constant propagation
1037  return itemPtr_ == rhs.itemPtr_;
1038  }
1039 
1040  bool operator!=(F14ItemIter const& rhs) const {
1041  return !(*this == rhs);
1042  }
1043 
1044  private:
1045  ItemPtr itemPtr_;
1046  std::size_t index_;
1047 };
1048 
1050 
1051 template <typename SizeType, typename ItemIter, bool EnablePackedItemIter>
1052 struct SizeAndPackedBegin {
1053  SizeType size_{0};
1054 
1055  private:
1056  typename ItemIter::Packed packedBegin_{ItemIter{}.pack()};
1057 
1058  public:
1059  typename ItemIter::Packed& packedBegin() {
1060  return packedBegin_;
1061  }
1062 
1063  typename ItemIter::Packed const& packedBegin() const {
1064  return packedBegin_;
1065  }
1066 };
1067 
1068 template <typename SizeType, typename ItemIter>
1069 struct SizeAndPackedBegin<SizeType, ItemIter, false> {
1070  SizeType size_{0};
1071 
1072  [[noreturn]] typename ItemIter::Packed& packedBegin() {
1074  }
1075 
1076  [[noreturn]] typename ItemIter::Packed const& packedBegin() const {
1078  }
1079 };
1080 
1081 template <typename Policy>
1082 class F14Table : public Policy {
1083  public:
1084  using Item = typename Policy::Item;
1085 
1086  using value_type = typename Policy::Value;
1087  using allocator_type = typename Policy::Alloc;
1088 
1089  private:
1090  using Alloc = typename Policy::Alloc;
1091  using AllocTraits = typename Policy::AllocTraits;
1092  using Hasher = typename Policy::Hasher;
1093  using InternalSizeType = typename Policy::InternalSizeType;
1094  using KeyEqual = typename Policy::KeyEqual;
1095 
1096  using Policy::kAllocIsAlwaysEqual;
1097  using Policy::kDefaultConstructIsNoexcept;
1098  using Policy::kEnableItemIteration;
1099  using Policy::kSwapIsNoexcept;
1100 
1101  using Policy::destroyItemOnClear;
1102  using Policy::isAvalanchingHasher;
1103  using Policy::prefetchBeforeCopy;
1104  using Policy::prefetchBeforeDestroy;
1105  using Policy::prefetchBeforeRehash;
1106 
1107  using ByteAlloc = typename AllocTraits::template rebind_alloc<uint8_t>;
1108  using BytePtr = typename std::allocator_traits<ByteAlloc>::pointer;
1109 
1110  using Chunk = F14Chunk<Item>;
1111  using ChunkPtr =
1112  typename std::pointer_traits<BytePtr>::template rebind<Chunk>;
1113 
1114  using HashPair = typename F14HashToken::HashPair;
1115 
1116  public:
1117  using ItemIter = F14ItemIter<ChunkPtr>;
1118 
1119  private:
1121 
1122  ChunkPtr chunks_{Chunk::emptyInstance()};
1123  InternalSizeType chunkMask_{0};
1124  SizeAndPackedBegin<InternalSizeType, ItemIter, kEnableItemIteration>
1125  sizeAndPackedBegin_;
1126 
1128 
1129  void swapContents(F14Table& rhs) noexcept {
1130  using std::swap;
1131  swap(chunks_, rhs.chunks_);
1132  swap(chunkMask_, rhs.chunkMask_);
1133  swap(sizeAndPackedBegin_.size_, rhs.sizeAndPackedBegin_.size_);
1134  if (kEnableItemIteration) {
1135  swap(
1136  sizeAndPackedBegin_.packedBegin(),
1137  rhs.sizeAndPackedBegin_.packedBegin());
1138  }
1139  }
1140 
1141  public:
1142  F14Table(
1143  std::size_t initialCapacity,
1144  Hasher const& hasher,
1145  KeyEqual const& keyEqual,
1146  Alloc const& alloc)
1147  : Policy{hasher, keyEqual, alloc} {
1148  if (initialCapacity > 0) {
1149  reserve(initialCapacity);
1150  }
1151  }
1152 
1153  F14Table(F14Table const& rhs) : Policy{rhs} {
1154  buildFromF14Table(rhs);
1155  }
1156 
1157  F14Table(F14Table const& rhs, Alloc const& alloc) : Policy{rhs, alloc} {
1158  buildFromF14Table(rhs);
1159  }
1160 
1161  F14Table(F14Table&& rhs) noexcept(
1165  : Policy{std::move(rhs)} {
1166  swapContents(rhs);
1167  }
1168 
1169  F14Table(F14Table&& rhs, Alloc const& alloc) noexcept(kAllocIsAlwaysEqual)
1170  : Policy{std::move(rhs), alloc} {
1171  if (kAllocIsAlwaysEqual || this->alloc() == rhs.alloc()) {
1172  // move storage (common case)
1173  swapContents(rhs);
1174  } else {
1175  // new storage because allocators unequal, move values (rare case)
1176  buildFromF14Table(std::move(rhs));
1177  }
1178  }
1179 
1180  F14Table& operator=(F14Table const& rhs) {
1181  if (this != &rhs) {
1182  reset();
1183  static_cast<Policy&>(*this) = rhs;
1184  buildFromF14Table(rhs);
1185  }
1186  return *this;
1187  }
1188 
1189  F14Table& operator=(F14Table&& rhs) noexcept(
1192  (kAllocIsAlwaysEqual ||
1195  if (this != &rhs) {
1196  reset();
1197  static_cast<Policy&>(*this) = std::move(rhs);
1199  kAllocIsAlwaysEqual || this->alloc() == rhs.alloc()) {
1200  // move storage (common case)
1201  swapContents(rhs);
1202  } else {
1203  // new storage because allocators unequal, move values (rare case)
1204  buildFromF14Table(std::move(rhs));
1205  }
1206  }
1207  return *this;
1208  }
1209 
1210  ~F14Table() {
1211  reset();
1212  }
1213 
1214  void swap(F14Table& rhs) noexcept(kSwapIsNoexcept) {
1215  // If propagate_on_container_swap is false and allocators are
1216  // not equal, the only way to accomplish a swap would be to do
1217  // dynamic allocation and then move (or swap) each contained value.
1218  // AllocatorAwareContainer-s are not supposed to attempt this, but
1219  // rather are supposed to have undefined behavior in that case.
1222  kAllocIsAlwaysEqual || this->alloc() == rhs.alloc(),
1223  "swap is undefined for unequal non-propagating allocators");
1224  this->swapPolicy(rhs);
1225  swapContents(rhs);
1226  }
1227 
1228  private:
1230 
1231  // Hash values are used to compute the desired position, which is the
1232  // chunk index at which we would like to place a value (if there is no
1233  // overflow), and the tag, which is an additional 8 bits of entropy.
1234  //
1235  // The standard's definition of hash function quality only refers to
1236  // the probability of collisions of the entire hash value, not to the
1237  // probability of collisions of the results of shifting or masking the
1238  // hash value. Some hash functions, however, provide this stronger
1239  // guarantee (not quite the same as the definition of avalanching,
1240  // but similar).
1241  //
1242  // If the user-supplied hasher is an avalanching one (each bit of the
1243  // hash value has a 50% chance of being the same for differing hash
1244  // inputs), then we can just take 1 byte of the hash value for the tag
1245  // and the rest for the desired position. Avalanching hashers also
1246  // let us map hash value to array index position with just a bitmask
1247  // without risking clumping. (Many hash tables just accept the risk
1248  // and do it regardless.)
1249  //
1250  // std::hash<std::string> avalanches in all implementations we've
1251  // examined: libstdc++-v3 uses MurmurHash2, and libc++ uses CityHash
1252  // or MurmurHash2. The other std::hash specializations, however, do not
1253  // have this property. std::hash for integral and pointer values is the
1254  // identity function on libstdc++-v3 and libc++, in particular. In our
1255  // experience it is also fairly common for user-defined specializations
1256  // of std::hash to combine fields in an ad-hoc way that does not evenly
1257  // distribute entropy among the bits of the result (a + 37 * b, for
1258  // example, where a and b are integer fields).
1259  //
1260  // For hash functions we don't trust to avalanche, we repair things by
1261  // applying a bit mixer to the user-supplied hash.
1262 
1263 #if FOLLY_X64 || FOLLY_AARCH64
1264  // 64-bit
1265  static HashPair splitHash(std::size_t hash) {
1266  static_assert(sizeof(std::size_t) == sizeof(uint64_t), "");
1267  std::size_t tag;
1268  if (!isAvalanchingHasher()) {
1269 #if FOLLY_F14_CRC_INTRINSIC_AVAILABLE
1270 #if FOLLY_SSE
1271  // SSE4.2 CRC
1272  std::size_t c = _mm_crc32_u64(0, hash);
1273  tag = (c >> 24) | 0x80;
1274  hash += c;
1275 #else
1276  // CRC is optional on armv8 (-march=armv8-a+crc), standard on armv8.1
1277  std::size_t c = __crc32cd(0, hash);
1278  tag = (c >> 24) | 0x80;
1279  hash += c;
1280 #endif
1281 #else
1282  // The mixer below is not fully avalanching for all 64 bits of
1283  // output, but looks quite good for bits 18..63 and puts plenty
1284  // of entropy even lower when considering multiple bits together
1285  // (like the tag). Importantly, when under register pressure it
1286  // uses fewer registers, instructions, and immediate constants
1287  // than the alternatives, resulting in compact code that is more
1288  // easily inlinable. In one instantiation a modified Murmur mixer
1289  // was 48 bytes of assembly (even after using the same multiplicand
1290  // for both steps) and this one was 27 bytes, for example.
1291  auto const kMul = 0xc4ceb9fe1a85ec53ULL;
1292 #ifdef _WIN32
1293  __int64 signedHi;
1294  __int64 signedLo = _mul128(
1295  static_cast<__int64>(hash), static_cast<__int64>(kMul), &signedHi);
1296  auto hi = static_cast<uint64_t>(signedHi);
1297  auto lo = static_cast<uint64_t>(signedLo);
1298 #else
1299  auto hi = static_cast<uint64_t>(
1300  (static_cast<unsigned __int128>(hash) * kMul) >> 64);
1301  auto lo = hash * kMul;
1302 #endif
1303  hash = hi ^ lo;
1304  hash *= kMul;
1305  tag = ((hash >> 15) & 0x7f) | 0x80;
1306  hash >>= 22;
1307 #endif
1308  } else {
1309  // we don't trust the top bit
1310  tag = (hash >> 56) | 0x80;
1311  }
1312  return std::make_pair(hash, tag);
1313  }
1314 #else
1315  // 32-bit
1316  static HashPair splitHash(std::size_t hash) {
1317  static_assert(sizeof(std::size_t) == sizeof(uint32_t), "");
1318  uint8_t tag;
1319  if (!isAvalanchingHasher()) {
1320 #if FOLLY_F14_CRC_INTRINSIC_AVAILABLE
1321 #if FOLLY_SSE
1322  // SSE4.2 CRC
1323  auto c = _mm_crc32_u32(0, hash);
1324  tag = static_cast<uint8_t>(~(c >> 25));
1325  hash += c;
1326 #else
1327  auto c = __crc32cw(0, hash);
1328  tag = static_cast<uint8_t>(~(c >> 25));
1329  hash += c;
1330 #endif
1331 #else
1332  // finalizer for 32-bit murmur2
1333  hash ^= hash >> 13;
1334  hash *= 0x5bd1e995;
1335  hash ^= hash >> 15;
1336  tag = static_cast<uint8_t>(~(hash >> 25));
1337 #endif
1338  } else {
1339  // we don't trust the top bit
1340  tag = (hash >> 24) | 0x80;
1341  }
1342  return std::make_pair(hash, tag);
1343  }
1344 #endif
1345 
1347 
1348  static std::size_t chunkAllocSize(
1349  std::size_t chunkCount,
1350  std::size_t maxSizeWithoutRehash) {
1351  if (chunkCount == 1) {
1352  FOLLY_SAFE_DCHECK((maxSizeWithoutRehash % 2) == 0, "");
1353  static_assert(offsetof(Chunk, rawItems_) == 16, "");
1354  return 16 + sizeof(Item) * maxSizeWithoutRehash;
1355  } else {
1356  return sizeof(Chunk) * chunkCount;
1357  }
1358  }
1359 
1360  ChunkPtr initializeChunks(
1361  BytePtr raw,
1362  std::size_t chunkCount,
1363  std::size_t maxSizeWithoutRehash) {
1364  static_assert(std::is_trivial<Chunk>::value, "F14Chunk should be POD");
1365  auto chunks = static_cast<Chunk*>(static_cast<void*>(&*raw));
1366  for (std::size_t i = 0; i < chunkCount; ++i) {
1367  chunks[i].clear();
1368  }
1369  chunks[0].markEof(chunkCount == 1 ? maxSizeWithoutRehash : 1);
1370  return std::pointer_traits<ChunkPtr>::pointer_to(*chunks);
1371  }
1372 
1373  public:
1374  ItemIter begin() const noexcept {
1375  FOLLY_SAFE_DCHECK(kEnableItemIteration, "");
1376  return ItemIter{sizeAndPackedBegin_.packedBegin()};
1377  }
1378 
1379  ItemIter end() const noexcept {
1380  return ItemIter{};
1381  }
1382 
1383  bool empty() const noexcept {
1384  return size() == 0;
1385  }
1386 
1387  InternalSizeType size() const noexcept {
1388  return sizeAndPackedBegin_.size_;
1389  }
1390 
1391  std::size_t max_size() const noexcept {
1392  auto& a = this->alloc();
1393  return std::min<std::size_t>(
1395  AllocTraits::max_size(a));
1396  }
1397 
1398  std::size_t bucket_count() const noexcept {
1399  // bucket_count is just a synthetic construct for the outside world
1400  // so that size, bucket_count, load_factor, and max_load_factor are
1401  // all self-consistent. The only one of those that is real is size().
1402  if (chunkMask_ != 0) {
1403  return (chunkMask_ + 1) * Chunk::kDesiredCapacity;
1404  } else {
1405  return chunks_->chunk0Capacity();
1406  }
1407  }
1408 
1409  std::size_t max_bucket_count() const noexcept {
1410  return max_size();
1411  }
1412 
1413  float load_factor() const noexcept {
1414  return empty()
1415  ? 0.0f
1416  : static_cast<float>(size()) / static_cast<float>(bucket_count());
1417  }
1418 
1419  float max_load_factor() const noexcept {
1420  return 1.0f;
1421  }
1422 
1423  void max_load_factor(float) noexcept {
1424  // Probing hash tables can't run load factors >= 1 (unlike chaining
1425  // tables). In addition, we have measured that there is little or
1426  // no performance advantage to running a smaller load factor (cache
1427  // locality losses outweigh the small reduction in probe lengths,
1428  // often making it slower). Therefore, we've decided to just fix
1429  // max_load_factor at 1.0f regardless of what the user requests.
1430  // This has an additional advantage that we don't have to store it.
1431  // Taking alignment into consideration this makes every F14 table
1432  // 8 bytes smaller, and is part of the reason an empty F14NodeMap
1433  // is almost half the size of an empty std::unordered_map (32 vs
1434  // 56 bytes).
1435  //
1436  // I don't have a strong opinion on whether we should remove this
1437  // method or leave a stub, let ngbronson or xshi know if you have a
1438  // compelling argument either way.
1439  }
1440 
1441  private:
1442  // Our probe strategy is to advance through additional chunks with
1443  // a stride that is key-specific. This is called double hashing,
1444  // and is a well known and high quality probing strategy. So long as
1445  // the stride and the chunk count are relatively prime, we will visit
1446  // every chunk once and then return to the original chunk, letting us
1447  // detect and end the cycle. The chunk count is a power of two, so
1448  // we can satisfy the relatively prime part by choosing an odd stride.
1449  // We've already computed a high quality secondary hash value for the
1450  // tag, so we just use it for the second probe hash as well.
1451  //
1452  // At the maximum load factor of 12/14, expected probe length for a
1453  // find hit is 1.041, with 99% of keys found in the first three chunks.
1454  // Expected probe length for a find miss (or insert) is 1.275, with a
1455  // p99 probe length of 4 (fewer than 1% of failing find look at 5 or
1456  // more chunks).
1457  //
1458  // This code is structured so you can try various ways of encoding
1459  // the current probe state. For example, at the moment the probe's
1460  // state is the position in the cycle and the resulting chunk index is
1461  // computed from that inside probeCurrentIndex. We could also make the
1462  // probe state the chunk index, and then increment it by hp.second *
1463  // 2 + 1 in probeAdvance. Wrapping can be applied early or late as
1464  // well. This particular code seems to be easier for the optimizer
1465  // to understand.
1466  //
1467  // We could also implement probing strategies that resulted in the same
1468  // tour for every key initially assigned to a chunk (linear probing or
1469  // quadratic), but that results in longer probe lengths. In particular,
1470  // the cache locality wins of linear probing are not worth the increase
1471  // in probe lengths (extra work and less branch predictability) in
1472  // our experiments.
1473 
1474  std::size_t probeDelta(HashPair hp) const {
1475  return 2 * hp.second + 1;
1476  }
1477 
1478  template <typename K>
1479  FOLLY_ALWAYS_INLINE ItemIter findImpl(HashPair hp, K const& key) const {
1480  std::size_t index = hp.first;
1481  std::size_t step = probeDelta(hp);
1482  for (std::size_t tries = 0; tries <= chunkMask_; ++tries) {
1483  ChunkPtr chunk = chunks_ + (index & chunkMask_);
1484  if (sizeof(Chunk) > 64) {
1485  prefetchAddr(chunk->itemAddr(8));
1486  }
1487  auto hits = chunk->tagMatchIter(hp.second);
1488  while (hits.hasNext()) {
1489  auto i = hits.next();
1490  if (LIKELY(this->keyMatchesItem(key, chunk->item(i)))) {
1491  // Tag match and key match were both successful. The chance
1492  // of a false tag match is 1/128 for each key in the chunk
1493  // (with a proper hash function).
1494  return ItemIter{chunk, i};
1495  }
1496  }
1497  if (LIKELY(chunk->outboundOverflowCount() == 0)) {
1498  // No keys that wanted to be placed in this chunk were denied
1499  // entry, so our search is over. This is the common case.
1500  break;
1501  }
1502  index += step;
1503  }
1504  // Loop exit because tries is exhausted is rare, but possible.
1505  // That means that for every chunk there is currently a key present
1506  // in the map that visited that chunk on its probe search but ended
1507  // up somewhere else, and we have searched every chunk.
1508  return ItemIter{};
1509  }
1510 
1511  public:
1512  // Prehashing splits the work of find(key) into two calls, enabling you
1513  // to manually implement loop pipelining for hot bulk lookups. prehash
1514  // computes the hash and prefetches the first computed memory location,
1515  // and the two-arg find(F14HashToken,K) performs the rest of the search.
1516  template <typename K>
1517  F14HashToken prehash(K const& key) const {
1518  FOLLY_SAFE_DCHECK(chunks_ != nullptr, "");
1519  auto hp = splitHash(this->computeKeyHash(key));
1520  ChunkPtr firstChunk = chunks_ + (hp.first & chunkMask_);
1521  prefetchAddr(firstChunk);
1522  return F14HashToken(std::move(hp));
1523  }
1524 
1525  template <typename K>
1526  FOLLY_ALWAYS_INLINE ItemIter find(K const& key) const {
1527  auto hp = splitHash(this->computeKeyHash(key));
1528  return findImpl(hp, key);
1529  }
1530 
1531  template <typename K>
1532  FOLLY_ALWAYS_INLINE ItemIter
1533  find(F14HashToken const& token, K const& key) const {
1535  splitHash(this->computeKeyHash(key)) == static_cast<HashPair>(token),
1536  "");
1537  return findImpl(static_cast<HashPair>(token), key);
1538  }
1539 
1540  private:
1541  void adjustSizeAndBeginAfterInsert(ItemIter iter) {
1542  if (kEnableItemIteration) {
1543  // packedBegin is the max of all valid ItemIter::pack()
1544  auto packed = iter.pack();
1545  if (sizeAndPackedBegin_.packedBegin() < packed) {
1546  sizeAndPackedBegin_.packedBegin() = packed;
1547  }
1548  }
1549 
1550  ++sizeAndPackedBegin_.size_;
1551  }
1552 
1553  // Ignores hp if pos.chunk()->hostedOverflowCount() == 0
1554  void eraseBlank(ItemIter iter, HashPair hp) {
1555  iter.chunk()->clearTag(iter.index());
1556 
1557  if (iter.chunk()->hostedOverflowCount() != 0) {
1558  // clean up
1559  std::size_t index = hp.first;
1560  std::size_t delta = probeDelta(hp);
1561  uint8_t hostedOp = 0;
1562  while (true) {
1563  ChunkPtr chunk = chunks_ + (index & chunkMask_);
1564  if (chunk == iter.chunk()) {
1565  chunk->adjustHostedOverflowCount(hostedOp);
1566  break;
1567  }
1568  chunk->decrOutboundOverflowCount();
1569  hostedOp = Chunk::kDecrHostedOverflowCount;
1570  index += delta;
1571  }
1572  }
1573  }
1574 
1575  void adjustSizeAndBeginBeforeErase(ItemIter iter) {
1576  --sizeAndPackedBegin_.size_;
1577  if (kEnableItemIteration) {
1578  if (iter.pack() == sizeAndPackedBegin_.packedBegin()) {
1579  if (size() == 0) {
1580  iter = ItemIter{};
1581  } else {
1582  iter.precheckedAdvance();
1583  }
1584  sizeAndPackedBegin_.packedBegin() = iter.pack();
1585  }
1586  }
1587  }
1588 
1589  template <typename... Args>
1590  void insertAtBlank(ItemIter pos, HashPair hp, Args&&... args) {
1591  try {
1592  auto dst = pos.itemAddr();
1593  this->constructValueAtItem(size(), dst, std::forward<Args>(args)...);
1594  } catch (...) {
1595  eraseBlank(pos, hp);
1596  throw;
1597  }
1598  adjustSizeAndBeginAfterInsert(pos);
1599  }
1600 
1601  ItemIter allocateTag(uint8_t* fullness, HashPair hp) {
1602  ChunkPtr chunk;
1603  std::size_t index = hp.first;
1604  std::size_t delta = probeDelta(hp);
1605  uint8_t hostedOp = 0;
1606  while (true) {
1607  index &= chunkMask_;
1608  chunk = chunks_ + index;
1609  if (LIKELY(fullness[index] < Chunk::kCapacity)) {
1610  break;
1611  }
1612  chunk->incrOutboundOverflowCount();
1613  hostedOp = Chunk::kIncrHostedOverflowCount;
1614  index += delta;
1615  }
1616  unsigned itemIndex = fullness[index]++;
1617  FOLLY_SAFE_DCHECK(!chunk->occupied(itemIndex), "");
1618  chunk->setTag(itemIndex, hp.second);
1619  chunk->adjustHostedOverflowCount(hostedOp);
1620  return ItemIter{chunk, itemIndex};
1621  }
1622 
1623  ChunkPtr lastOccupiedChunk() const {
1624  FOLLY_SAFE_DCHECK(size() > 0, "");
1625  if (kEnableItemIteration) {
1626  return begin().chunk();
1627  } else {
1628  return chunks_ + chunkMask_;
1629  }
1630  }
1631 
1632  template <typename T>
1633  void directBuildFrom(T&& src) {
1634  FOLLY_SAFE_DCHECK(src.size() > 0 && chunkMask_ == src.chunkMask_, "");
1635 
1636  // We use std::forward<T> to allow portions of src to be moved out by
1637  // either beforeBuild or afterBuild, but we are just relying on good
1638  // behavior of our Policy superclass to ensure that any particular
1639  // field of this is a donor at most once.
1640 
1641  auto undoState =
1642  this->beforeBuild(src.size(), bucket_count(), std::forward<T>(src));
1643  bool success = false;
1644  SCOPE_EXIT {
1645  this->afterBuild(
1646  undoState, success, src.size(), bucket_count(), std::forward<T>(src));
1647  };
1648 
1649  // Copy can fail part-way through if a Value copy constructor throws.
1650  // Failing afterBuild is limited in its cleanup power in this case,
1651  // because it can't enumerate the items that were actually copied.
1652  // Fortunately we can divide the situation into cases where all of
1653  // the state is owned by the table itself (F14Node and F14Value),
1654  // for which clearImpl() can do partial cleanup, and cases where all
1655  // of the values are owned by the policy (F14Vector), in which case
1656  // partial failure should not occur. Sorry for the subtle invariants
1657  // in the Policy API.
1658 
1659  if (is_trivially_copyable<Item>::value && !this->destroyItemOnClear() &&
1660  bucket_count() == src.bucket_count()) {
1661  // most happy path
1662  auto n = chunkAllocSize(chunkMask_ + 1, bucket_count());
1663  std::memcpy(&chunks_[0], &src.chunks_[0], n);
1664  sizeAndPackedBegin_.size_ = src.size();
1665  if (kEnableItemIteration) {
1666  auto srcBegin = src.begin();
1667  sizeAndPackedBegin_.packedBegin() =
1668  ItemIter{chunks_ + (srcBegin.chunk() - src.chunks_),
1669  srcBegin.index()}
1670  .pack();
1671  }
1672  } else {
1673  std::size_t maxChunkIndex = src.lastOccupiedChunk() - src.chunks_;
1674 
1675  // happy path, no rehash but pack items toward bottom of chunk and
1676  // use copy constructor
1677  auto srcChunk = &src.chunks_[maxChunkIndex];
1678  Chunk* dstChunk = &chunks_[maxChunkIndex];
1679  do {
1680  dstChunk->copyOverflowInfoFrom(*srcChunk);
1681 
1682  auto iter = srcChunk->occupiedIter();
1683  if (prefetchBeforeCopy()) {
1684  for (auto piter = iter; piter.hasNext();) {
1685  this->prefetchValue(srcChunk->citem(piter.next()));
1686  }
1687  }
1688 
1689  std::size_t dstI = 0;
1690  for (; iter.hasNext(); ++dstI) {
1691  auto srcI = iter.next();
1692  auto&& srcArg =
1693  std::forward<T>(src).buildArgForItem(srcChunk->item(srcI));
1694  auto dst = dstChunk->itemAddr(dstI);
1695  this->constructValueAtItem(
1696  0, dst, std::forward<decltype(srcArg)>(srcArg));
1697  dstChunk->setTag(dstI, srcChunk->tag(srcI));
1698  ++sizeAndPackedBegin_.size_;
1699  }
1700 
1701  --srcChunk;
1702  --dstChunk;
1703  } while (size() != src.size());
1704 
1705  // reset doesn't care about packedBegin, so we don't fix it until the end
1706  if (kEnableItemIteration) {
1707  sizeAndPackedBegin_.packedBegin() =
1708  ItemIter{chunks_ + maxChunkIndex,
1709  chunks_[maxChunkIndex].lastOccupied().index()}
1710  .pack();
1711  }
1712  }
1713 
1714  success = true;
1715  }
1716 
1717  template <typename T>
1718  void rehashBuildFrom(T&& src) {
1719  FOLLY_SAFE_DCHECK(src.chunkMask_ > chunkMask_, "");
1720 
1721  // 1 byte per chunk means < 1 bit per value temporary overhead
1722  std::array<uint8_t, 256> stackBuf;
1723  uint8_t* fullness;
1724  auto cc = chunkMask_ + 1;
1725  if (cc <= stackBuf.size()) {
1726  fullness = stackBuf.data();
1727  } else {
1728  ByteAlloc a{this->alloc()};
1729  fullness = &*std::allocator_traits<ByteAlloc>::allocate(a, cc);
1730  }
1731  SCOPE_EXIT {
1732  if (cc > stackBuf.size()) {
1733  ByteAlloc a{this->alloc()};
1734  std::allocator_traits<ByteAlloc>::deallocate(
1735  a,
1736  std::pointer_traits<typename std::allocator_traits<
1737  ByteAlloc>::pointer>::pointer_to(*fullness),
1738  cc);
1739  }
1740  };
1741  std::memset(fullness, '\0', cc);
1742 
1743  // We use std::forward<T> to allow portions of src to be moved out by
1744  // either beforeBuild or afterBuild, but we are just relying on good
1745  // behavior of our Policy superclass to ensure that any particular
1746  // field of this is a donor at most once.
1747 
1748  // Exception safety requires beforeBuild to happen after all of the
1749  // allocate() calls.
1750  auto undoState =
1751  this->beforeBuild(src.size(), bucket_count(), std::forward<T>(src));
1752  bool success = false;
1753  SCOPE_EXIT {
1754  this->afterBuild(
1755  undoState, success, src.size(), bucket_count(), std::forward<T>(src));
1756  };
1757 
1758  // The current table is at a valid state at all points for policies
1759  // in which non-trivial values are owned by the main table (F14Node
1760  // and F14Value), so reset() will clean things up properly if we
1761  // fail partway through. For the case that the policy manages value
1762  // lifecycle (F14Vector) then nothing after beforeBuild can throw and
1763  // we don't have to worry about partial failure.
1764 
1765  std::size_t srcChunkIndex = src.lastOccupiedChunk() - src.chunks_;
1766  while (true) {
1767  auto srcChunk = &src.chunks_[srcChunkIndex];
1768  auto iter = srcChunk->occupiedIter();
1769  if (prefetchBeforeRehash()) {
1770  for (auto piter = iter; piter.hasNext();) {
1771  this->prefetchValue(srcChunk->item(piter.next()));
1772  }
1773  }
1774  if (srcChunk->hostedOverflowCount() == 0) {
1775  // all items are in their preferred chunk (no probing), so we
1776  // don't need to compute any hash values
1777  while (iter.hasNext()) {
1778  auto i = iter.next();
1779  auto& srcItem = srcChunk->item(i);
1780  auto&& srcArg = std::forward<T>(src).buildArgForItem(srcItem);
1781  HashPair hp{srcChunkIndex, srcChunk->tag(i)};
1782  insertAtBlank(
1783  allocateTag(fullness, hp),
1784  hp,
1785  std::forward<decltype(srcArg)>(srcArg));
1786  }
1787  } else {
1788  // any chunk's items might be in here
1789  while (iter.hasNext()) {
1790  auto i = iter.next();
1791  auto& srcItem = srcChunk->item(i);
1792  auto&& srcArg = std::forward<T>(src).buildArgForItem(srcItem);
1793  auto const& srcKey = src.keyForValue(srcArg);
1794  auto hp = splitHash(this->computeKeyHash(srcKey));
1795  FOLLY_SAFE_DCHECK(hp.second == srcChunk->tag(i), "");
1796  insertAtBlank(
1797  allocateTag(fullness, hp),
1798  hp,
1799  std::forward<decltype(srcArg)>(srcArg));
1800  }
1801  }
1802  if (srcChunkIndex == 0) {
1803  break;
1804  }
1805  --srcChunkIndex;
1806  }
1807 
1808  success = true;
1809  }
1810 
1811  template <typename T>
1812  FOLLY_NOINLINE void buildFromF14Table(T&& src) {
1813  FOLLY_SAFE_DCHECK(size() == 0, "");
1814  if (src.size() == 0) {
1815  return;
1816  }
1817 
1818  reserveForInsert(src.size());
1819  try {
1820  if (chunkMask_ == src.chunkMask_) {
1821  directBuildFrom(std::forward<T>(src));
1822  } else {
1823  rehashBuildFrom(std::forward<T>(src));
1824  }
1825  } catch (...) {
1826  reset();
1828  throw;
1829  }
1830  }
1831 
1832  FOLLY_NOINLINE void reserveImpl(
1833  std::size_t capacity,
1834  std::size_t origChunkCount,
1835  std::size_t origMaxSizeWithoutRehash) {
1836  FOLLY_SAFE_DCHECK(capacity >= size(), "");
1837 
1838  // compute new size
1839  std::size_t const kInitialCapacity = 2;
1840  std::size_t const kHalfChunkCapacity =
1841  (Chunk::kDesiredCapacity / 2) & ~std::size_t{1};
1842  std::size_t newMaxSizeWithoutRehash;
1843  std::size_t newChunkCount;
1844  if (capacity <= kHalfChunkCapacity) {
1845  newChunkCount = 1;
1846  newMaxSizeWithoutRehash =
1847  (capacity < kInitialCapacity) ? kInitialCapacity : kHalfChunkCapacity;
1848  } else {
1849  newChunkCount = nextPowTwo((capacity - 1) / Chunk::kDesiredCapacity + 1);
1850  newMaxSizeWithoutRehash = newChunkCount * Chunk::kDesiredCapacity;
1851 
1852  constexpr std::size_t kMaxChunksWithoutCapacityOverflow =
1853  (std::numeric_limits<std::size_t>::max)() / Chunk::kDesiredCapacity;
1854 
1855  if (newChunkCount > kMaxChunksWithoutCapacityOverflow ||
1856  newMaxSizeWithoutRehash > max_size()) {
1857  throw_exception<std::bad_alloc>();
1858  }
1859  }
1860 
1861  if (origMaxSizeWithoutRehash != newMaxSizeWithoutRehash) {
1862  rehashImpl(
1863  origChunkCount,
1864  origMaxSizeWithoutRehash,
1865  newChunkCount,
1866  newMaxSizeWithoutRehash);
1867  }
1868  }
1869 
1870  void rehashImpl(
1871  std::size_t origChunkCount,
1872  std::size_t origMaxSizeWithoutRehash,
1873  std::size_t newChunkCount,
1874  std::size_t newMaxSizeWithoutRehash) {
1875  auto origChunks = chunks_;
1876 
1877  BytePtr rawAllocation;
1878  auto undoState = this->beforeRehash(
1879  size(),
1880  origMaxSizeWithoutRehash,
1881  newMaxSizeWithoutRehash,
1882  chunkAllocSize(newChunkCount, newMaxSizeWithoutRehash),
1883  rawAllocation);
1884  chunks_ =
1885  initializeChunks(rawAllocation, newChunkCount, newMaxSizeWithoutRehash);
1886 
1888  newChunkCount < std::numeric_limits<InternalSizeType>::max(), "");
1889  chunkMask_ = static_cast<InternalSizeType>(newChunkCount - 1);
1890 
1891  bool success = false;
1892  SCOPE_EXIT {
1893  // this SCOPE_EXIT reverts chunks_ and chunkMask_ if necessary
1894  BytePtr finishedRawAllocation = nullptr;
1895  std::size_t finishedAllocSize = 0;
1896  if (LIKELY(success)) {
1897  if (origMaxSizeWithoutRehash > 0) {
1898  finishedRawAllocation = std::pointer_traits<BytePtr>::pointer_to(
1899  *static_cast<uint8_t*>(static_cast<void*>(&*origChunks)));
1900  finishedAllocSize =
1901  chunkAllocSize(origChunkCount, origMaxSizeWithoutRehash);
1902  }
1903  } else {
1904  finishedRawAllocation = rawAllocation;
1905  finishedAllocSize =
1906  chunkAllocSize(newChunkCount, newMaxSizeWithoutRehash);
1907  chunks_ = origChunks;
1909  origChunkCount < std::numeric_limits<InternalSizeType>::max(), "");
1910  chunkMask_ = static_cast<InternalSizeType>(origChunkCount - 1);
1912  }
1913 
1914  this->afterRehash(
1915  std::move(undoState),
1916  success,
1917  size(),
1918  origMaxSizeWithoutRehash,
1919  newMaxSizeWithoutRehash,
1920  finishedRawAllocation,
1921  finishedAllocSize);
1922  };
1923 
1924  if (size() == 0) {
1925  // nothing to do
1926  } else if (origChunkCount == 1 && newChunkCount == 1) {
1927  // no mask, no chunk scan, no hash computation, no probing
1928  auto srcChunk = origChunks;
1929  auto dstChunk = chunks_;
1930  std::size_t srcI = 0;
1931  std::size_t dstI = 0;
1932  while (dstI < size()) {
1933  if (LIKELY(srcChunk->occupied(srcI))) {
1934  dstChunk->setTag(dstI, srcChunk->tag(srcI));
1935  this->moveItemDuringRehash(
1936  dstChunk->itemAddr(dstI), srcChunk->item(srcI));
1937  ++dstI;
1938  }
1939  ++srcI;
1940  }
1941  if (kEnableItemIteration) {
1942  sizeAndPackedBegin_.packedBegin() = ItemIter{dstChunk, dstI - 1}.pack();
1943  }
1944  } else {
1945  // 1 byte per chunk means < 1 bit per value temporary overhead
1946  std::array<uint8_t, 256> stackBuf;
1947  uint8_t* fullness;
1948  if (newChunkCount <= stackBuf.size()) {
1949  fullness = stackBuf.data();
1950  } else {
1951  ByteAlloc a{this->alloc()};
1952  // may throw
1953  fullness =
1954  &*std::allocator_traits<ByteAlloc>::allocate(a, newChunkCount);
1955  }
1956  std::memset(fullness, '\0', newChunkCount);
1957  SCOPE_EXIT {
1958  if (newChunkCount > stackBuf.size()) {
1959  ByteAlloc a{this->alloc()};
1960  std::allocator_traits<ByteAlloc>::deallocate(
1961  a,
1962  std::pointer_traits<typename std::allocator_traits<
1963  ByteAlloc>::pointer>::pointer_to(*fullness),
1964  newChunkCount);
1965  }
1966  };
1967 
1968  auto srcChunk = origChunks + origChunkCount - 1;
1969  std::size_t remaining = size();
1970  while (remaining > 0) {
1971  auto iter = srcChunk->occupiedIter();
1972  if (prefetchBeforeRehash()) {
1973  for (auto piter = iter; piter.hasNext();) {
1974  this->prefetchValue(srcChunk->item(piter.next()));
1975  }
1976  }
1977  while (iter.hasNext()) {
1978  --remaining;
1979  auto srcI = iter.next();
1980  Item& srcItem = srcChunk->item(srcI);
1981  auto hp = splitHash(
1982  this->computeItemHash(const_cast<Item const&>(srcItem)));
1983  FOLLY_SAFE_DCHECK(hp.second == srcChunk->tag(srcI), "");
1984 
1985  auto dstIter = allocateTag(fullness, hp);
1986  this->moveItemDuringRehash(dstIter.itemAddr(), srcItem);
1987  }
1988  --srcChunk;
1989  }
1990 
1991  if (kEnableItemIteration) {
1992  // this code replaces size invocations of adjustSizeAndBeginAfterInsert
1993  std::size_t i = chunkMask_;
1994  while (fullness[i] == 0) {
1995  --i;
1996  }
1997  sizeAndPackedBegin_.packedBegin() =
1998  ItemIter{chunks_ + i, std::size_t{fullness[i]} - 1}.pack();
1999  }
2000  }
2001 
2002  success = true;
2003  }
2004 
2005  void asanOnReserve(std::size_t capacity) {
2006  if (kIsSanitizeAddress && capacity > size()) {
2007  asanPendingSafeInserts += capacity - size();
2008  }
2009  }
2010 
2011  bool asanShouldAddExtraRehash() {
2012  if (!kIsSanitizeAddress) {
2013  return false;
2014  } else if (asanPendingSafeInserts > 0) {
2016  return false;
2017  } else if (size() <= 1) {
2018  return size() > 0;
2019  } else {
2020  constexpr std::size_t kBigPrime = 4294967291U;
2021  auto s = (asanRehashState += kBigPrime);
2022  return (s % size()) == 0;
2023  }
2024  }
2025 
2026  void asanExtraRehash() {
2027  auto cc = chunkMask_ + 1;
2028  auto bc = bucket_count();
2029  rehashImpl(cc, bc, cc, bc);
2030  }
2031 
2032  void asanOnInsert() {
2033  // When running under ASAN, we add a spurious rehash with 1/size()
2034  // probability before every insert. This means that finding reference
2035  // stability problems for F14Value and F14Vector is much more likely.
2036  // The most common pattern that causes this is
2037  //
2038  // auto& ref = map[k1]; map[k2] = foo(ref);
2039  //
2040  // One way to fix this is to call map.reserve(N) before such a
2041  // sequence, where N is the number of keys that might be inserted
2042  // within the section that retains references.
2043  if (asanShouldAddExtraRehash()) {
2044  asanExtraRehash();
2045  }
2046  }
2047 
2048  public:
2049  // user has no control over max_load_factor
2050 
2051  void rehash(std::size_t capacity) {
2052  reserve(capacity);
2053  }
2054 
2055  void reserve(std::size_t capacity) {
2056  // We want to support the pattern
2057  // map.reserve(2); auto& r1 = map[k1]; auto& r2 = map[k2];
2058  asanOnReserve(capacity);
2059  reserveImpl(
2060  std::max<std::size_t>(capacity, size()),
2061  chunkMask_ + 1,
2062  bucket_count());
2063  }
2064 
2065  // Returns true iff a rehash was performed
2066  void reserveForInsert(size_t incoming = 1) {
2067  auto capacity = size() + incoming;
2068  auto bc = bucket_count();
2069  if (capacity - 1 >= bc) {
2070  reserveImpl(capacity, chunkMask_ + 1, bc);
2071  }
2072  }
2073 
2074  // Returns pos,true if construct, pos,false if found. key is only used
2075  // during the search; all constructor args for an inserted value come
2076  // from args... key won't be accessed after args are touched.
2077  template <typename K, typename... Args>
2078  std::pair<ItemIter, bool> tryEmplaceValue(K const& key, Args&&... args) {
2079  const auto hp = splitHash(this->computeKeyHash(key));
2080 
2081  if (size() > 0) {
2082  auto existing = findImpl(hp, key);
2083  if (!existing.atEnd()) {
2084  return std::make_pair(existing, false);
2085  }
2086  }
2087 
2088  asanOnInsert();
2089 
2090  reserveForInsert();
2091 
2092  std::size_t index = hp.first;
2093  ChunkPtr chunk = chunks_ + (index & chunkMask_);
2094  auto firstEmpty = chunk->firstEmpty();
2095 
2096  if (!firstEmpty.hasIndex()) {
2097  std::size_t delta = probeDelta(hp);
2098  do {
2099  chunk->incrOutboundOverflowCount();
2100  index += delta;
2101  chunk = chunks_ + (index & chunkMask_);
2102  firstEmpty = chunk->firstEmpty();
2103  } while (!firstEmpty.hasIndex());
2104  chunk->adjustHostedOverflowCount(Chunk::kIncrHostedOverflowCount);
2105  }
2106  std::size_t itemIndex = firstEmpty.index();
2107  FOLLY_SAFE_DCHECK(!chunk->occupied(itemIndex), "");
2108 
2109  chunk->setTag(itemIndex, hp.second);
2110  ItemIter iter{chunk, itemIndex};
2111 
2112  // insertAtBlank will clear the tag if the constructor throws
2113  insertAtBlank(iter, hp, std::forward<Args>(args)...);
2114  return std::make_pair(iter, true);
2115  }
2116 
2117  private:
2118  template <bool Reset>
2119  void clearImpl() noexcept {
2120  if (chunks_ == Chunk::emptyInstance()) {
2121  FOLLY_SAFE_DCHECK(empty() && bucket_count() == 0, "");
2122  return;
2123  }
2124 
2125  // turn clear into reset if the table is >= 16 chunks so that
2126  // we don't get too low a load factor
2127  bool willReset = Reset || chunkMask_ + 1 >= 16;
2128 
2129  auto origSize = size();
2130  auto origCapacity = bucket_count();
2131  if (willReset) {
2132  this->beforeReset(origSize, origCapacity);
2133  } else {
2134  this->beforeClear(origSize, origCapacity);
2135  }
2136 
2137  if (!empty()) {
2138  if (destroyItemOnClear()) {
2139  for (std::size_t ci = 0; ci <= chunkMask_; ++ci) {
2140  ChunkPtr chunk = chunks_ + ci;
2141  auto iter = chunk->occupiedIter();
2142  if (prefetchBeforeDestroy()) {
2143  for (auto piter = iter; piter.hasNext();) {
2144  this->prefetchValue(chunk->item(piter.next()));
2145  }
2146  }
2147  while (iter.hasNext()) {
2148  this->destroyItem(chunk->item(iter.next()));
2149  }
2150  }
2151  }
2152  if (!willReset) {
2153  // It's okay to do this in a separate loop because we only do it
2154  // when the chunk count is small. That avoids a branch when we
2155  // are promoting a clear to a reset for a large table.
2156  auto c0c = chunks_[0].chunk0Capacity();
2157  for (std::size_t ci = 0; ci <= chunkMask_; ++ci) {
2158  chunks_[ci].clear();
2159  }
2160  chunks_[0].markEof(c0c);
2161  }
2162  if (kEnableItemIteration) {
2163  sizeAndPackedBegin_.packedBegin() = ItemIter{}.pack();
2164  }
2165  sizeAndPackedBegin_.size_ = 0;
2166  }
2167 
2168  if (willReset) {
2169  BytePtr rawAllocation = std::pointer_traits<BytePtr>::pointer_to(
2170  *static_cast<uint8_t*>(static_cast<void*>(&*chunks_)));
2171  std::size_t rawSize = chunkAllocSize(chunkMask_ + 1, bucket_count());
2172 
2173  chunks_ = Chunk::emptyInstance();
2174  chunkMask_ = 0;
2175 
2176  this->afterReset(origSize, origCapacity, rawAllocation, rawSize);
2177  } else {
2178  this->afterClear(origSize, origCapacity);
2179  }
2180  }
2181 
2182  void eraseImpl(ItemIter pos, HashPair hp) {
2183  this->destroyItem(pos.item());
2184  adjustSizeAndBeginBeforeErase(pos);
2185  eraseBlank(pos, hp);
2186  }
2187 
2188  public:
2189  // The item needs to still be hashable during this call. If you want
2190  // to intercept the value before it is destroyed (to extract it, for
2191  // example), use eraseIterInto(pos, beforeDestroy).
2192  void eraseIter(ItemIter pos) {
2193  eraseIterInto(pos, [](value_type&&) {});
2194  }
2195 
2196  // The item needs to still be hashable during this call. If you want
2197  // to intercept the value before it is destroyed (to extract it, for
2198  // example), do so in the beforeDestroy callback.
2199  template <typename BeforeDestroy>
2200  void eraseIterInto(ItemIter pos, BeforeDestroy&& beforeDestroy) {
2201  HashPair hp{};
2202  if (pos.chunk()->hostedOverflowCount() != 0) {
2203  hp = splitHash(this->computeItemHash(pos.citem()));
2204  }
2205  beforeDestroy(this->valueAtItemForExtract(pos.item()));
2206  eraseImpl(pos, hp);
2207  }
2208 
2209  template <typename K>
2210  std::size_t eraseKey(K const& key) {
2211  return eraseKeyInto(key, [](value_type&&) {});
2212  }
2213 
2214  template <typename K, typename BeforeDestroy>
2215  std::size_t eraseKeyInto(K const& key, BeforeDestroy&& beforeDestroy) {
2216  if (UNLIKELY(size() == 0)) {
2217  return 0;
2218  }
2219  auto hp = splitHash(this->computeKeyHash(key));
2220  auto iter = findImpl(hp, key);
2221  if (!iter.atEnd()) {
2222  beforeDestroy(this->valueAtItemForExtract(iter.item()));
2223  eraseImpl(iter, hp);
2224  return 1;
2225  } else {
2226  return 0;
2227  }
2228  }
2229 
2230  void clear() noexcept {
2231  if (kIsSanitizeAddress) {
2232  // force recycling of heap memory
2233  auto bc = bucket_count();
2234  reset();
2235  try {
2236  reserveImpl(bc, 0, 0);
2237  } catch (std::bad_alloc const&) {
2238  // ASAN mode only, keep going
2239  }
2240  } else {
2241  clearImpl<false>();
2242  }
2243  }
2244 
2245  // Like clear(), but always frees all dynamic storage allocated
2246  // by the table.
2247  void reset() noexcept {
2248  clearImpl<true>();
2249  }
2250 
2251  // Get memory footprint, not including sizeof(*this).
2252  std::size_t getAllocatedMemorySize() const {
2253  std::size_t sum = 0;
2254  visitAllocationClasses(
2255  [&sum](std::size_t bytes, std::size_t n) { sum += bytes * n; });
2256  return sum;
2257  }
2258 
2259  // Enumerates classes of allocated memory blocks currently owned
2260  // by this table, calling visitor(allocationSize, allocationCount).
2261  // This can be used to get a more accurate indication of memory footprint
2262  // than getAllocatedMemorySize() if you have some way of computing the
2263  // internal fragmentation of the allocator, such as JEMalloc's nallocx.
2264  // The visitor might be called twice with the same allocationSize. The
2265  // visitor's computation should produce the same result for visitor(8,
2266  // 2) as for two calls to visitor(8, 1), for example. The visitor may
2267  // be called with a zero allocationCount.
2268  template <typename V>
2269  void visitAllocationClasses(V&& visitor) const {
2270  auto bc = bucket_count();
2271  this->visitPolicyAllocationClasses(
2272  (bc == 0 ? 0 : chunkAllocSize(chunkMask_ + 1, bc)),
2273  size(),
2274  bc,
2275  visitor);
2276  }
2277 
2278  // visitor should take an Item const&
2279  template <typename V>
2280  void visitItems(V&& visitor) const {
2281  if (empty()) {
2282  return;
2283  }
2284  std::size_t maxChunkIndex = lastOccupiedChunk() - chunks_;
2285  auto chunk = &chunks_[0];
2286  for (std::size_t i = 0; i <= maxChunkIndex; ++i, ++chunk) {
2287  auto iter = chunk->occupiedIter();
2288  if (prefetchBeforeCopy()) {
2289  for (auto piter = iter; piter.hasNext();) {
2290  this->prefetchValue(chunk->citem(piter.next()));
2291  }
2292  }
2293  while (iter.hasNext()) {
2294  visitor(chunk->citem(iter.next()));
2295  }
2296  }
2297  }
2298 
2299  // visitor should take two Item const*
2300  template <typename V>
2301  void visitContiguousItemRanges(V&& visitor) const {
2302  if (empty()) {
2303  return;
2304  }
2305  std::size_t maxChunkIndex = lastOccupiedChunk() - chunks_;
2306  auto chunk = &chunks_[0];
2307  for (std::size_t i = 0; i <= maxChunkIndex; ++i, ++chunk) {
2308  for (auto iter = chunk->occupiedRangeIter(); iter.hasNext();) {
2309  auto be = iter.next();
2311  chunk->occupied(be.first) && chunk->occupied(be.second - 1), "");
2312  Item const* b = chunk->itemAddr(be.first);
2313  visitor(b, b + (be.second - be.first));
2314  }
2315  }
2316  }
2317 
2318  private:
2319  static std::size_t& histoAt(
2320  std::vector<std::size_t>& histo,
2321  std::size_t index) {
2322  if (histo.size() <= index) {
2323  histo.resize(index + 1);
2324  }
2325  return histo.at(index);
2326  }
2327 
2328  public:
2329  // Expensive
2330  F14TableStats computeStats() const {
2331  F14TableStats stats;
2332 
2333  if (kIsDebug && kEnableItemIteration) {
2334  // validate iteration
2335  std::size_t n = 0;
2336  ItemIter prev;
2337  for (auto iter = begin(); iter != end(); iter.advance()) {
2338  FOLLY_SAFE_DCHECK(n == 0 || iter.pack() < prev.pack(), "");
2339  ++n;
2340  prev = iter;
2341  }
2342  FOLLY_SAFE_DCHECK(n == size(), "");
2343  }
2344 
2346  (chunks_ == Chunk::emptyInstance()) == (bucket_count() == 0), "");
2347 
2348  std::size_t n1 = 0;
2349  std::size_t n2 = 0;
2350  auto cc = bucket_count() == 0 ? 0 : chunkMask_ + 1;
2351  for (std::size_t ci = 0; ci < cc; ++ci) {
2352  ChunkPtr chunk = chunks_ + ci;
2353  FOLLY_SAFE_DCHECK(chunk->eof() == (ci == 0), "");
2354 
2355  auto iter = chunk->occupiedIter();
2356 
2357  std::size_t chunkOccupied = 0;
2358  for (auto piter = iter; piter.hasNext(); piter.next()) {
2359  ++chunkOccupied;
2360  }
2361  n1 += chunkOccupied;
2362 
2363  histoAt(stats.chunkOccupancyHisto, chunkOccupied)++;
2364  histoAt(
2365  stats.chunkOutboundOverflowHisto, chunk->outboundOverflowCount())++;
2366  histoAt(stats.chunkHostedOverflowHisto, chunk->hostedOverflowCount())++;
2367 
2368  while (iter.hasNext()) {
2369  auto ii = iter.next();
2370  ++n2;
2371 
2372  {
2373  auto& item = chunk->citem(ii);
2374  auto hp = splitHash(this->computeItemHash(item));
2375  FOLLY_SAFE_DCHECK(chunk->tag(ii) == hp.second, "");
2376 
2377  std::size_t dist = 1;
2378  std::size_t index = hp.first;
2379  std::size_t delta = probeDelta(hp);
2380  while ((index & chunkMask_) != ci) {
2381  index += delta;
2382  ++dist;
2383  }
2384 
2385  histoAt(stats.keyProbeLengthHisto, dist)++;
2386  }
2387 
2388  // misses could have any tag, so we do the dumb but accurate
2389  // thing and just try them all
2390  for (std::size_t ti = 0; ti < 256; ++ti) {
2391  uint8_t tag = static_cast<uint8_t>(ti == 0 ? 1 : 0);
2392  HashPair hp{ci, tag};
2393 
2394  std::size_t dist = 1;
2395  std::size_t index = hp.first;
2396  std::size_t delta = probeDelta(hp);
2397  for (std::size_t tries = 0; tries <= chunkMask_ &&
2398  chunks_[index & chunkMask_].outboundOverflowCount() != 0;
2399  ++tries) {
2400  index += delta;
2401  ++dist;
2402  }
2403 
2404  histoAt(stats.missProbeLengthHisto, dist)++;
2405  }
2406  }
2407  }
2408 
2409  FOLLY_SAFE_DCHECK(n1 == size(), "");
2410  FOLLY_SAFE_DCHECK(n2 == size(), "");
2411 
2412 #if FOLLY_HAS_RTTI
2413  stats.policy = typeid(Policy).name();
2414 #endif
2415  stats.size = size();
2416  stats.valueSize = sizeof(value_type);
2417  stats.bucketCount = bucket_count();
2418  stats.chunkCount = cc;
2419 
2420  stats.totalBytes = sizeof(*this) + getAllocatedMemorySize();
2421  stats.overheadBytes = stats.totalBytes - size() * sizeof(value_type);
2422 
2423  return stats;
2424  }
2425 };
2426 } // namespace detail
2427 } // namespace f14
2428 
2429 #endif // FOLLY_F14_VECTOR_INTRINSICS_AVAILABLE
2430 
2431 } // namespace folly
const string needle
constexpr unsigned int popcount(T const v)
Definition: Bits.h:130
constexpr std::size_t constexpr_find_first_set(T t)
Definition: InvokeTest.cpp:58
void * ptr
auto chunks
std::vector< std::size_t > keyProbeLengthHisto
Definition: F14Table.h:91
typename remove_cvref< T >::type remove_cvref_t
Definition: Traits.h:183
std::atomic< int64_t > sum(0)
#define FOLLY_F14_TLS_IF_ASAN
Definition: F14Table.h:54
constexpr auto kIsDebug
Definition: Portability.h:264
#define FOLLY_ALWAYS_INLINE
Definition: CPortability.h:151
char b
LogLevel max
Definition: LogLevel.cpp:31
constexpr T nextPowTwo(T const v)
Definition: Bits.h:149
std::size_t bucketCount
Definition: F14Table.h:86
constexpr T constexpr_min(T a)
Definition: ConstexprMath.h:79
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
#define LIKELY(x)
Definition: Likely.h:47
STL namespace.
auto begin(TestAdlIterable &instance)
Definition: ForeachTest.cpp:56
constexpr bool kIsSanitizeAddress
Definition: Portability.h:118
#define SCOPE_EXIT
Definition: ScopeGuard.h:274
folly::std T
internal::ArgsMatcher< InnerMatcher > Args(const InnerMatcher &matcher)
The non test part of the code is expected to have failures gtest_output_test_ cc
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
std::size_t valueSize
Definition: F14Table.h:85
requires E e noexcept(noexcept(s.error(std::move(e))))
static auto computeHelper(T const *m) -> decltype(m->computeStats())
Definition: F14Table.h:98
bool_constant< true > true_type
Definition: gtest-port.h:2210
FOLLY_PUSH_WARNING RHS rhs
Definition: Traits.h:649
constexpr T constexpr_max(T a)
Definition: ConstexprMath.h:68
static F14TableStats compute(T const &m)
Definition: F14Table.h:108
FOLLY_F14_TLS_IF_ASAN std::size_t asanPendingSafeInserts
Definition: F14Table.cpp:34
bool operator!=(const Unexpected< Error > &lhs, const Unexpected< Error > &rhs)
Definition: Expected.h:766
const char * name
Definition: http_parser.c:437
#define FOLLY_NOINLINE
Definition: CPortability.h:142
std::vector< std::size_t > missProbeLengthHisto
Definition: F14Table.h:92
FOLLY_ALWAYS_INLINE void assume_unreachable()
Definition: Assume.h:59
constexpr auto empty(C const &c) -> decltype(c.empty())
Definition: Access.h:55
std::vector< std::size_t > chunkOccupancyHisto
Definition: F14Table.h:88
bool Value(const T &value, M matcher)
static constexpr F14IntrinsicsMode getF14IntrinsicsMode()
auto end(TestAdlIterable &instance)
Definition: ForeachTest.cpp:62
static map< string, int > m
constexpr unsigned int findLastSet(T const v)
Definition: Bits.h:105
std::uniform_int_distribution< milliseconds::rep > dist
std::enable_if< std::is_integral< Src >::value &&IsSomeString< Tgt >::value &&sizeof(Src)< 4 >::typetoAppend(Src value, Tgt *result){typedef typename std::conditional< std::is_signed< Src >::value, int64_t, uint64_t >::type Intermediate;toAppend< Tgt >static_cast< Intermediate >value), result);}template< class Src >typename std::enable_if< std::is_integral< Src >::value &&sizeof(Src)< 4 &&!std::is_same< Src, char >::value, size_t >::typeestimateSpaceNeeded(Src value){typedef typename std::conditional< std::is_signed< Src >::value, int64_t, uint64_t >::type Intermediate;return estimateSpaceNeeded(static_cast< Intermediate >value));}template< class Tgt, class Src >typename std::enable_if< std::is_enum< Src >::value &&IsSomeString< Tgt >::value >::typetoAppend(Src value, Tgt *result){toAppend(static_cast< typename std::underlying_type< Src >::type >value), result);}template< class Src >typename std::enable_if< std::is_enum< Src >::value, size_t >::typeestimateSpaceNeeded(Src value){return estimateSpaceNeeded(static_cast< typename std::underlying_type< Src >::type >value));}namespace detail{constexpr int kConvMaxDecimalInShortestLow=-6;constexpr int kConvMaxDecimalInShortestHigh=21;}template< class Tgt, class Src >typename std::enable_if< std::is_floating_point< Src >::value &&IsSomeString< Tgt >::value >::typetoAppend(Src value, Tgt *result, double_conversion::DoubleToStringConverter::DtoaMode mode, unsigned int numDigits){using namespace double_conversion;DoubleToStringConverter conv(DoubleToStringConverter::NO_FLAGS,"Infinity","NaN", 'E', detail::kConvMaxDecimalInShortestLow, detail::kConvMaxDecimalInShortestHigh, 6, 1);char buffer[256];StringBuilder builder(buffer, sizeof(buffer));switch(mode){case DoubleToStringConverter::SHORTEST:conv.ToShortest(value,&builder);break;case DoubleToStringConverter::SHORTEST_SINGLE:conv.ToShortestSingle(static_cast< float >value),&builder);break;case DoubleToStringConverter::FIXED:conv.ToFixed(value, int(numDigits),&builder);break;default:CHECK(mode==DoubleToStringConverter::PRECISION);conv.ToPrecision(value, int(numDigits),&builder);break;}const size_t length=size_t(builder.position());builder.Finalize();result->append(buffer, length);}template< class Tgt, class Src >typename std::enable_if< std::is_floating_point< Src >::value &&IsSomeString< Tgt >::value >::typetoAppend(Src value, Tgt *result){toAppend(value, result, double_conversion::DoubleToStringConverter::SHORTEST, 0);}template< class Src >typename std::enable_if< std::is_floating_point< Src >::value, size_t >::typeestimateSpaceNeeded(Src value){constexpr int kMaxMantissaSpace=double_conversion::DoubleToStringConverter::kBase10MaximalLength+1;constexpr int kMaxExponentSpace=2+3;static const int kMaxPositiveSpace=std::max({kMaxMantissaSpace+kMaxExponentSpace, kMaxMantissaSpace-detail::kConvMaxDecimalInShortestLow, detail::kConvMaxDecimalInShortestHigh,});return size_t(kMaxPositiveSpace+(value< 0?1:0));}template< class Src >struct HasLengthEstimator:std::false_type{};template< class Src >constexpr typename std::enable_if< !std::is_fundamental< Src >::value &&!IsSomeString< Src >::value &&!std::is_convertible< Src, const char * >::value &&!std::is_convertible< Src, StringPiece >::value &&!std::is_enum< Src >::value &&!HasLengthEstimator< Src >::value, size_t >::typeestimateSpaceNeeded(const Src &){return sizeof(Src)+1;}namespace detail{template< class Tgt >typename std::enable_if< IsSomeString< Tgt >::value, size_t >::typeestimateSpaceToReserve(size_t sofar, Tgt *){return sofar;}template< class T, class...Ts >size_t estimateSpaceToReserve(size_t sofar, const T &v, const Ts &...vs){return estimateSpaceToReserve(sofar+estimateSpaceNeeded(v), vs...);}template< class...Ts >void reserveInTarget(const Ts &...vs){getLastElement(vs...) -> reserve(estimateSpaceToReserve(0, vs...))
Definition: Conv.h:792
char a
std::is_trivially_copyable< T > is_trivially_copyable
Definition: Traits.h:313
static const char *const value
Definition: Conv.cpp:50
std::vector< std::size_t > chunkHostedOverflowHisto
Definition: F14Table.h:90
std::size_t overheadBytes
Definition: F14Table.h:94
#define FOLLY_SAFE_CHECK(expr, msg)
Definition: SafeAssert.h:35
FOLLY_F14_TLS_IF_ASAN std::size_t asanRehashState
Definition: F14Table.cpp:35
std::size_t chunkCount
Definition: F14Table.h:87
void swap(exception_wrapper &a, exception_wrapper &b) noexcept
bool operator==(const Unexpected< Error > &lhs, const Unexpected< Error > &rhs)
Definition: Expected.h:758
std::vector< std::size_t > chunkOutboundOverflowHisto
Definition: F14Table.h:89
static set< string > s
static F14TableStats computeHelper(...)
Definition: F14Table.h:102
const
Definition: upload.py:398
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
bool_constant< false > false_type
Definition: gtest-port.h:2209
Definition: InvokeTest.cpp:65
FOLLY_NODISCARD T * launder(T *in) noexcept
Definition: Launder.h:48
std::size_t size
Definition: F14Table.h:84
char const * policy
Definition: F14Table.h:83
void swap(SwapTrackingAlloc< T > &, SwapTrackingAlloc< T > &)
Definition: F14TestUtil.h:414
#define UNLIKELY(x)
Definition: Likely.h:48
FOLLY_ALWAYS_INLINE void assume(bool cond)
Definition: Assume.h:41
char c
#define FOLLY_SAFE_DCHECK(expr, msg)
Definition: SafeAssert.h:42
bool check(const dynamic &schema, const dynamic &value, bool check=true)
std::enable_if< IsLessThanComparable< Value >::value, bool >::type operator<(const Expected< Value, Error > &lhs, const Expected< Value, Error > &rhs)
Definition: Expected.h:1321
unsigned char * ptr_
Definition: Random.cpp:106
std::size_t totalBytes
Definition: F14Table.h:93
def next(obj)
Definition: ast.py:58