v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
small-map.h
Go to the documentation of this file.
1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Copyright 2023 the V8 project authors. All rights reserved.
6// This file is a clone of "base/containers/small_map.h" in chromium.
7// Keep in sync, especially when fixing bugs.
8
9#ifndef V8_BASE_SMALL_MAP_H_
10#define V8_BASE_SMALL_MAP_H_
11
12#include "src/base/macros.h"
13
14namespace v8::base {
15
16// SmallMap is a container with a std::map-like interface. It starts out backed
17// by an unsorted array but switches to some other container type if it grows
18// beyond this fixed size.
19//
20// PROS
21//
22// - Good memory locality and low overhead for smaller maps.
23// - Handles large maps without the degenerate performance of an array.
24//
25// CONS
26//
27// - Larger code size than the alternatives.
28//
29// IMPORTANT NOTES
30//
31// - Iterators are invalidated across mutations.
32//
33// DETAILS
34//
35// SmallMap will pick up the comparator from the underlying map type. In
36// std::map only a "less" operator is defined, which requires us to do two
37// comparisons per element when doing the brute-force search in the simple
38// array. std::unordered_map has a key_equal function which will be used.
39//
40// We define default overrides for the common map types to avoid this
41// double-compare, but you should be aware of this if you use your own operator<
42// for your map and supply your own version of == to the SmallMap. You can use
43// regular operator== by just doing:
44//
45// SmallMap<std::map<MyKey, MyValue>, 4, std::equal_to<MyKey>>
46//
47//
48// USAGE
49// -----
50//
51// NormalMap: The map type to fall back to. This also defines the key and value
52// types for the SmallMap.
53// kArraySize: The size of the initial array of results. This will be allocated
54// with the SmallMap object rather than separately on the heap.
55// Once the map grows beyond this size, the map type will be used
56// instead.
57// EqualKey: A functor which tests two keys for equality. If the wrapped map
58// type has a "key_equal" member (unordered_map does), then that will
59// be used by default. If the wrapped map type has a strict weak
60// ordering "key_compare" (std::map does), that will be used to
61// implement equality by default.
62// MapInit: A functor that takes a NormalMap* and uses it to initialize the map.
63// This functor will be called at most once per SmallMap, when the map
64// exceeds the threshold of kArraySize and we are about to copy values
65// from the array to the map. The functor *must* initialize the
66// NormalMap* argument with placement new, since after it runs we
67// assume that the NormalMap has been initialized.
68//
69// Example:
70// SmallMap<std::map<string, int>> days;
71// days["sunday" ] = 0;
72// days["monday" ] = 1;
73// days["tuesday" ] = 2;
74// days["wednesday"] = 3;
75// days["thursday" ] = 4;
76// days["friday" ] = 5;
77// days["saturday" ] = 6;
78
79namespace internal {
80
81template <typename NormalMap>
83 public:
84 void operator()(NormalMap* map) const { new (map) NormalMap(); }
85};
86
87// has_key_equal<M>::value is true iff there exists a type M::key_equal. This is
88// used to dispatch to one of the select_equal_key<> metafunctions below.
89template <typename M>
91 typedef char sml; // "small" is sometimes #defined so we use an abbreviation.
92 typedef struct {
93 char dummy[2];
94 } big;
95 // Two functions, one accepts types that have a key_equal member, and one that
96 // accepts anything. They each return a value of a different size, so we can
97 // determine at compile-time which function would have been called.
98 template <typename U>
99 static big test(typename U::key_equal*);
100 template <typename>
101 static sml test(...);
102 // Determines if M::key_equal exists by looking at the size of the return
103 // type of the compiler-chosen test() function.
104 static const bool value = (sizeof(test<M>(0)) == sizeof(big));
105};
106template <typename M>
107const bool has_key_equal<M>::value;
108
109// Base template used for map types that do NOT have an M::key_equal member,
110// e.g., std::map<>. These maps have a strict weak ordering comparator rather
111// than an equality functor, so equality will be implemented in terms of that
112// comparator.
113//
114// There's a partial specialization of this template below for map types that do
115// have an M::key_equal member.
116template <typename M, bool has_key_equal_value>
118 struct equal_key {
119 bool operator()(const typename M::key_type& left,
120 const typename M::key_type& right) {
121 // Implements equality in terms of a strict weak ordering comparator.
122 typename M::key_compare comp;
123 return !comp(left, right) && !comp(right, left);
124 }
125 };
126};
127
128// Partial template specialization handles case where M::key_equal exists, e.g.,
129// unordered_map<>.
130template <typename M>
131struct select_equal_key<M, true> {
132 typedef typename M::key_equal equal_key;
133};
134
135} // namespace internal
136
137template <typename NormalMap, size_t kArraySize = 4,
138 typename EqualKey = typename internal::select_equal_key<
139 NormalMap, internal::has_key_equal<NormalMap>::value>::equal_key,
141class SmallMap {
142 static constexpr size_t kUsingFullMapSentinel =
143 std::numeric_limits<size_t>::max();
144
145 static_assert(kArraySize > 0, "Initial size must be greater than 0");
146 static_assert(kArraySize != kUsingFullMapSentinel,
147 "Initial size out of range");
148
149 public:
150 typedef typename NormalMap::key_type key_type;
151 typedef typename NormalMap::mapped_type data_type;
152 typedef typename NormalMap::mapped_type mapped_type;
153 typedef typename NormalMap::value_type value_type;
154 typedef EqualKey key_equal;
155
156 SmallMap() : size_(0), functor_(MapInit()) {}
157
158 explicit SmallMap(const MapInit& functor) : size_(0), functor_(functor) {}
159
160 // Allow copy-constructor and assignment, since STL allows them too.
162 // size_ and functor_ are initted in InitFrom()
163 InitFrom(src);
164 }
165
166 void operator=(const SmallMap& src) V8_NOEXCEPT {
167 if (&src == this) return;
168
169 // This is not optimal. If src and dest are both using the small array, we
170 // could skip the teardown and reconstruct. One problem to be resolved is
171 // that the value_type itself is pair<const K, V>, and const K is not
172 // assignable.
173 Destroy();
174 InitFrom(src);
175 }
176
178
179 class const_iterator;
180
181 class iterator {
182 public:
183 typedef typename NormalMap::iterator::iterator_category iterator_category;
184 typedef typename NormalMap::iterator::value_type value_type;
185 typedef typename NormalMap::iterator::difference_type difference_type;
186 typedef typename NormalMap::iterator::pointer pointer;
187 typedef typename NormalMap::iterator::reference reference;
188
190
192 if (array_iter_ != nullptr) {
193 ++array_iter_;
194 } else {
195 ++map_iter_;
196 }
197 return *this;
198 }
199
200 V8_INLINE iterator operator++(int /*unused*/) {
201 iterator result(*this);
202 ++(*this);
203 return result;
204 }
205
207 if (array_iter_ != nullptr) {
208 --array_iter_;
209 } else {
210 --map_iter_;
211 }
212 return *this;
213 }
214
215 V8_INLINE iterator operator--(int /*unused*/) {
216 iterator result(*this);
217 --(*this);
218 return result;
219 }
220
222 return array_iter_ ? array_iter_ : map_iter_.operator->();
223 }
224
226 return array_iter_ ? *array_iter_ : *map_iter_;
227 }
228
229 V8_INLINE bool operator==(const iterator& other) const {
230 if (array_iter_ != nullptr) {
231 return array_iter_ == other.array_iter_;
232 } else {
233 return other.array_iter_ == nullptr && map_iter_ == other.map_iter_;
234 }
235 }
236
237 V8_INLINE bool operator!=(const iterator& other) const {
238 return !(*this == other);
239 }
240
241 private:
242 friend class SmallMap;
243 friend class const_iterator;
244 V8_INLINE explicit iterator(value_type* init) : array_iter_(init) {}
245 V8_INLINE explicit iterator(const typename NormalMap::iterator& init)
246 : array_iter_(nullptr), map_iter_(init) {}
247
249 typename NormalMap::iterator map_iter_;
250 };
251
253 public:
254 typedef
255 typename NormalMap::const_iterator::iterator_category iterator_category;
256 typedef typename NormalMap::const_iterator::value_type value_type;
257 typedef typename NormalMap::const_iterator::difference_type difference_type;
258 typedef typename NormalMap::const_iterator::pointer pointer;
259 typedef typename NormalMap::const_iterator::reference reference;
260
262
263 // Non-explicit constructor lets us convert regular iterators to const
264 // iterators.
267
269 if (array_iter_ != nullptr) {
270 ++array_iter_;
271 } else {
272 ++map_iter_;
273 }
274 return *this;
275 }
276
278 const_iterator result(*this);
279 ++(*this);
280 return result;
281 }
282
284 if (array_iter_ != nullptr) {
285 --array_iter_;
286 } else {
287 --map_iter_;
288 }
289 return *this;
290 }
291
293 const_iterator result(*this);
294 --(*this);
295 return result;
296 }
297
299 return array_iter_ ? array_iter_ : map_iter_.operator->();
300 }
301
303 return array_iter_ ? *array_iter_ : *map_iter_;
304 }
305
306 V8_INLINE bool operator==(const const_iterator& other) const {
307 if (array_iter_ != nullptr) {
308 return array_iter_ == other.array_iter_;
309 }
310 return other.array_iter_ == nullptr && map_iter_ == other.map_iter_;
311 }
312
313 V8_INLINE bool operator!=(const const_iterator& other) const {
314 return !(*this == other);
315 }
316
317 private:
318 friend class SmallMap;
319 V8_INLINE explicit const_iterator(const value_type* init)
320 : array_iter_(init) {}
322 const typename NormalMap::const_iterator& init)
323 : array_iter_(nullptr), map_iter_(init) {}
324
326 typename NormalMap::const_iterator map_iter_;
327 };
328
331
332 if (UsingFullMap()) {
333 return iterator(map()->find(key));
334 }
335
336 for (size_t i = 0; i < size_; ++i) {
337 if (compare(array_[i].first, key)) {
338 return iterator(array_ + i);
339 }
340 }
341 return iterator(array_ + size_);
342 }
343
346
347 if (UsingFullMap()) {
348 return const_iterator(map()->find(key));
349 }
350
351 for (size_t i = 0; i < size_; ++i) {
352 if (compare(array_[i].first, key)) {
353 return const_iterator(array_ + i);
354 }
355 }
356 return const_iterator(array_ + size_);
357 }
358
359 // Invalidates iterators.
362
363 if (UsingFullMap()) {
364 return map_[key];
365 }
366
367 // Search backwards to favor recently-added elements.
368 for (size_t i = size_; i > 0; --i) {
369 const size_t index = i - 1;
370 if (compare(array_[index].first, key)) {
371 return array_[index].second;
372 }
373 }
374
375 if (V8_UNLIKELY(size_ == kArraySize)) {
377 return map_[key];
378 }
379
380 DCHECK(size_ < kArraySize);
381 new (&array_[size_]) value_type(key, data_type());
382 return array_[size_++].second;
383 }
384
385 // Invalidates iterators.
386 std::pair<iterator, bool> insert(const value_type& x) {
388
389 if (UsingFullMap()) {
390 std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
391 return std::make_pair(iterator(ret.first), ret.second);
392 }
393
394 for (size_t i = 0; i < size_; ++i) {
395 if (compare(array_[i].first, x.first)) {
396 return std::make_pair(iterator(array_ + i), false);
397 }
398 }
399
400 if (V8_UNLIKELY(size_ == kArraySize)) {
401 ConvertToRealMap(); // Invalidates all iterators!
402 std::pair<typename NormalMap::iterator, bool> ret = map_.insert(x);
403 return std::make_pair(iterator(ret.first), ret.second);
404 }
405
406 DCHECK(size_ < kArraySize);
407 new (&array_[size_]) value_type(x);
408 return std::make_pair(iterator(array_ + size_++), true);
409 }
410
411 // Invalidates iterators.
412 template <class InputIterator>
413 void insert(InputIterator f, InputIterator l) {
414 while (f != l) {
415 insert(*f);
416 ++f;
417 }
418 }
419
420 // Invalidates iterators.
421 template <typename... Args>
422 std::pair<iterator, bool> emplace(Args&&... args) {
424
425 if (UsingFullMap()) {
426 std::pair<typename NormalMap::iterator, bool> ret =
427 map_.emplace(std::forward<Args>(args)...);
428 return std::make_pair(iterator(ret.first), ret.second);
429 }
430
431 value_type x(std::forward<Args>(args)...);
432 for (size_t i = 0; i < size_; ++i) {
433 if (compare(array_[i].first, x.first)) {
434 return std::make_pair(iterator(array_ + i), false);
435 }
436 }
437
438 if (V8_UNLIKELY(size_ == kArraySize)) {
439 ConvertToRealMap(); // Invalidates all iterators!
440 std::pair<typename NormalMap::iterator, bool> ret =
441 map_.emplace(std::move(x));
442 return std::make_pair(iterator(ret.first), ret.second);
443 }
444
445 DCHECK(size_ < kArraySize);
446 new (&array_[size_]) value_type(std::move(x));
447 return std::make_pair(iterator(array_ + size_++), true);
448 }
449
450 // Invalidates iterators.
451 template <typename... Args>
452 std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
454
455 if (UsingFullMap()) {
456 std::pair<typename NormalMap::iterator, bool> ret =
457 map_.try_emplace(key, std::forward<Args>(args)...);
458 return std::make_pair(iterator(ret.first), ret.second);
459 }
460
461 for (size_t i = 0; i < size_; ++i) {
462 if (compare(array_[i].first, key)) {
463 return std::make_pair(iterator(array_ + i), false);
464 }
465 }
466
467 if (V8_UNLIKELY(size_ == kArraySize)) {
468 ConvertToRealMap(); // Invalidates all iterators!
469 std::pair<typename NormalMap::iterator, bool> ret =
470 map_.try_emplace(key, std::forward<Args>(args)...);
471 return std::make_pair(iterator(ret.first), ret.second);
472 }
473
474 DCHECK(size_ < kArraySize);
475 new (&array_[size_]) value_type(key, std::forward<Args>(args)...);
476 return std::make_pair(iterator(array_ + size_++), true);
477 }
478
480 return UsingFullMap() ? iterator(map_.begin()) : iterator(array_);
481 }
482
484 return UsingFullMap() ? const_iterator(map_.begin())
486 }
487
489 return UsingFullMap() ? iterator(map_.end()) : iterator(array_ + size_);
490 }
491
493 return UsingFullMap() ? const_iterator(map_.end())
495 }
496
497 void clear() {
498 if (UsingFullMap()) {
499 map_.~NormalMap();
500 } else {
501 for (size_t i = 0; i < size_; ++i) {
502 array_[i].~value_type();
503 }
504 }
505 size_ = 0;
506 }
507
508 // Invalidates iterators. Returns iterator following the last removed element.
510 if (UsingFullMap()) {
511 return iterator(map_.erase(position.map_iter_));
512 }
513
514 size_t i = static_cast<size_t>(position.array_iter_ - array_);
515 // TODO(crbug.com/817982): When we have a checked iterator, this CHECK might
516 // not be necessary.
517 CHECK_LE(i, size_);
518 array_[i].~value_type();
519 --size_;
520 if (i != size_) {
521 new (&array_[i]) value_type(std::move(array_[size_]));
522 array_[size_].~value_type();
523 return iterator(array_ + i);
524 }
525 return end();
526 }
527
528 size_t erase(const key_type& key) {
529 iterator iter = find(key);
530 if (iter == end()) {
531 return 0;
532 }
533 erase(iter);
534 return 1;
535 }
536
537 size_t count(const key_type& key) const {
538 return (find(key) == end()) ? 0 : 1;
539 }
540
541 size_t size() const { return UsingFullMap() ? map_.size() : size_; }
542
543 bool empty() const { return UsingFullMap() ? map_.empty() : size_ == 0; }
544
545 // Returns true if we have fallen back to using the underlying map
546 // representation.
547 bool UsingFullMap() const { return size_ == kUsingFullMapSentinel; }
548
549 V8_INLINE NormalMap* map() {
551 return &map_;
552 }
553
554 V8_INLINE const NormalMap* map() const {
556 return &map_;
557 }
558
559 private:
560 // When `size_ == kUsingFullMapSentinel`, we have switched storage strategies
561 // from `array_[kArraySize] to `NormalMap map_`. See ConvertToRealMap and
562 // UsingFullMap.
563 size_t size_;
564
565 MapInit functor_;
566
567 // We want to call constructors and destructors manually, but we don't want
568 // to allocate and deallocate the memory used for them separately. Since
569 // array_ and map_ are mutually exclusive, we'll put them in a union.
570 union {
571 value_type array_[kArraySize];
572 NormalMap map_;
573 };
574
576 // Storage for the elements in the temporary array. This is intentionally
577 // declared as a union to avoid having to default-construct |kArraySize|
578 // elements, only to move construct over them in the initial loop.
579 union Storage {
580 Storage() {}
581 ~Storage() {}
582 value_type array[kArraySize];
583 } temp;
584
585 // Move the current elements into a temporary array.
586 for (size_t i = 0; i < kArraySize; ++i) {
587 new (&temp.array[i]) value_type(std::move(array_[i]));
588 array_[i].~value_type();
589 }
590
591 // Initialize the map.
593 functor_(&map_);
594
595 // Insert elements into it.
596 for (size_t i = 0; i < kArraySize; ++i) {
597 map_.insert(std::move(temp.array[i]));
598 temp.array[i].~value_type();
599 }
600 }
601
602 // Helpers for constructors and destructors.
603 void InitFrom(const SmallMap& src) {
604 functor_ = src.functor_;
605 size_ = src.size_;
606 if (src.UsingFullMap()) {
607 functor_(&map_);
608 map_ = src.map_;
609 } else {
610 for (size_t i = 0; i < size_; ++i) {
611 new (&array_[i]) value_type(src.array_[i]);
612 }
613 }
614 }
615
616 void Destroy() {
617 if (UsingFullMap()) {
618 map_.~NormalMap();
619 } else {
620 for (size_t i = 0; i < size_; ++i) {
621 array_[i].~value_type();
622 }
623 }
624 }
625};
626
627} // namespace v8::base
628
629#endif // V8_BASE_SMALL_MAP_H_
NormalMap::const_iterator::reference reference
Definition small-map.h:259
V8_INLINE bool operator!=(const const_iterator &other) const
Definition small-map.h:313
V8_INLINE const value_type * operator->() const
Definition small-map.h:298
V8_INLINE bool operator==(const const_iterator &other) const
Definition small-map.h:306
NormalMap::const_iterator::pointer pointer
Definition small-map.h:258
V8_INLINE const value_type & operator*() const
Definition small-map.h:302
NormalMap::const_iterator::iterator_category iterator_category
Definition small-map.h:255
V8_INLINE const_iterator(const iterator &other)
Definition small-map.h:265
V8_INLINE const_iterator operator++(int)
Definition small-map.h:277
V8_INLINE const_iterator & operator++()
Definition small-map.h:268
V8_INLINE const_iterator(const value_type *init)
Definition small-map.h:319
NormalMap::const_iterator::value_type value_type
Definition small-map.h:256
NormalMap::const_iterator::difference_type difference_type
Definition small-map.h:257
NormalMap::const_iterator map_iter_
Definition small-map.h:326
V8_INLINE const_iterator operator--(int)
Definition small-map.h:292
V8_INLINE const_iterator(const typename NormalMap::const_iterator &init)
Definition small-map.h:321
V8_INLINE const_iterator & operator--()
Definition small-map.h:283
V8_INLINE iterator(value_type *init)
Definition small-map.h:244
V8_INLINE iterator operator++(int)
Definition small-map.h:200
V8_INLINE iterator & operator--()
Definition small-map.h:206
V8_INLINE iterator & operator++()
Definition small-map.h:191
NormalMap::iterator::difference_type difference_type
Definition small-map.h:185
V8_INLINE bool operator==(const iterator &other) const
Definition small-map.h:229
V8_INLINE iterator(const typename NormalMap::iterator &init)
Definition small-map.h:245
V8_INLINE value_type * operator->() const
Definition small-map.h:221
NormalMap::iterator::value_type value_type
Definition small-map.h:184
V8_INLINE value_type & operator*() const
Definition small-map.h:225
NormalMap::iterator::pointer pointer
Definition small-map.h:186
NormalMap::iterator::reference reference
Definition small-map.h:187
NormalMap::iterator::iterator_category iterator_category
Definition small-map.h:183
V8_INLINE iterator operator--(int)
Definition small-map.h:215
NormalMap::iterator map_iter_
Definition small-map.h:249
V8_INLINE bool operator!=(const iterator &other) const
Definition small-map.h:237
iterator begin()
Definition small-map.h:479
value_type array_[kArraySize]
Definition small-map.h:571
void insert(InputIterator f, InputIterator l)
Definition small-map.h:413
void InitFrom(const SmallMap &src)
Definition small-map.h:603
const_iterator find(const key_type &key) const
Definition small-map.h:344
SmallMap(const SmallMap &src) V8_NOEXCEPT
Definition small-map.h:161
SmallMap(const MapInit &functor)
Definition small-map.h:158
V8_NOINLINE V8_PRESERVE_MOST void ConvertToRealMap()
Definition small-map.h:575
data_type & operator[](const key_type &key)
Definition small-map.h:360
size_t erase(const key_type &key)
Definition small-map.h:528
NormalMap::value_type value_type
Definition small-map.h:153
bool UsingFullMap() const
Definition small-map.h:547
std::pair< iterator, bool > emplace(Args &&... args)
Definition small-map.h:422
V8_INLINE NormalMap * map()
Definition small-map.h:549
iterator erase(const iterator &position)
Definition small-map.h:509
const_iterator begin() const
Definition small-map.h:483
const_iterator end() const
Definition small-map.h:492
size_t count(const key_type &key) const
Definition small-map.h:537
void operator=(const SmallMap &src) V8_NOEXCEPT
Definition small-map.h:166
NormalMap::key_type key_type
Definition small-map.h:150
size_t size() const
Definition small-map.h:541
NormalMap::mapped_type mapped_type
Definition small-map.h:152
static constexpr size_t kUsingFullMapSentinel
Definition small-map.h:142
bool empty() const
Definition small-map.h:543
std::pair< iterator, bool > try_emplace(const key_type &key, Args &&... args)
Definition small-map.h:452
V8_INLINE const NormalMap * map() const
Definition small-map.h:554
NormalMap::mapped_type data_type
Definition small-map.h:151
iterator find(const key_type &key)
Definition small-map.h:329
std::pair< iterator, bool > insert(const value_type &x)
Definition small-map.h:386
void operator()(NormalMap *map) const
Definition small-map.h:84
OptionalOpIndex index
std::map< const std::string, const std::string > map
ZoneVector< RpoNumber > & result
int x
int position
Definition liveedit.cc:290
V8_BASE_EXPORT int const char va_list args
Definition strings.h:23
uint32_t test
uint32_t compare
#define V8_NOEXCEPT
#define CHECK(condition)
Definition logging.h:124
#define CHECK_LE(lhs, rhs)
#define DCHECK(condition)
Definition logging.h:482
static big test(typename U::key_equal *)
bool operator()(const typename M::key_type &left, const typename M::key_type &right)
Definition small-map.h:119
#define V8_INLINE
Definition v8config.h:500
#define V8_UNLIKELY(condition)
Definition v8config.h:660
#define V8_NOINLINE
Definition v8config.h:586
#define V8_PRESERVE_MOST
Definition v8config.h:598
std::unique_ptr< ValueMirror > key