v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
persistent-map.h
Go to the documentation of this file.
1// Copyright 2017 the V8 project 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#ifndef V8_COMPILER_PERSISTENT_MAP_H_
6#define V8_COMPILER_PERSISTENT_MAP_H_
7
8#include <array>
9#include <tuple>
10
11#include "src/base/hashing.h"
13
14namespace v8 {
15namespace internal {
16namespace compiler {
17
18// A fast and possibly incomplete equality check. If it returns false, the
19// values are certainly not equal, otherwise we do not know. The template is
20// intended to be specialized for types with expensive equality checks.
21template <class T>
23 bool operator()(const T& a, const T& b) { return a != b; }
24};
25
26// PersistentMap is a persistent map datastructure based on hash trees (a binary
27// tree using the bits of a hash value as addresses). The map is a conceptually
28// infinite: All keys are initially mapped to a default value, values are
29// deleted by overwriting them with the default value. The iterators produce
30// exactly the keys that are not the default value. The hash values should have
31// high variance in their high bits, so dense integers are a bad choice.
32// Complexity:
33// - Copy and assignment: O(1)
34// - access: O(log n)
35// - update: O(log n) time and space
36// - iteration: amortized O(1) per step
37// - Zip: O(n)
38// - equality check: O(n)
39// TODO(turbofan): Cache map transitions to avoid re-allocation of the same map.
40// TODO(turbofan): Implement an O(1) equality check based on hash consing or
41// something similar.
42template <class Key, class Value, class Hasher = base::hash<Key>>
44 public:
45 using key_type = Key;
47 using value_type = std::pair<Key, Value>;
48
49 private:
50 static constexpr size_t kHashBits = 32;
51 enum Bit : int { kLeft = 0, kRight = 1 };
52
53 // Access hash bits starting from the high bits and compare them according to
54 // their unsigned value. This way, the order in the hash tree is compatible
55 // with numeric hash comparisons.
56 class HashValue;
57
58 struct KeyValue : std::pair<Key, Value> {
59 const Key& key() const { return this->first; }
60 const Value& value() const { return this->second; }
61 using std::pair<Key, Value>::pair;
62 };
63
64 struct FocusedTree;
65
66 friend struct may_be_unequal<PersistentMap<Key, Value, Hasher>>;
67
68 public:
69 // Depth of the last added element. This is a cheap estimate for the size of
70 // the hash tree.
71 size_t last_depth() const {
72 if (tree_) {
73 return tree_->length;
74 } else {
75 return 0;
76 }
77 }
78
79 const Value& Get(const Key& key) const {
80 HashValue key_hash = HashValue(Hasher()(key));
81 const FocusedTree* tree = FindHash(key_hash);
82 return GetFocusedValue(tree, key);
83 }
84
85 // Add or overwrite an existing key-value pair.
86 void Set(Key key, Value value);
87 // Modify an entry in-place, avoiding repeated search.
88 // `F` is a functional that expects a `Value*` parameter to modify it.
89 template <class F>
90 void Modify(Key key, F f);
91
92 bool operator==(const PersistentMap& other) const {
93 if (tree_ == other.tree_) return true;
94 if (def_value_ != other.def_value_) return false;
95 for (std::tuple<Key, Value, Value> triple : Zip(other)) {
96 if (std::get<1>(triple) != std::get<2>(triple)) return false;
97 }
98 return true;
99 }
100
101 bool operator!=(const PersistentMap& other) const {
102 return !(*this == other);
103 }
104
105 // The iterator produces key-value pairs in the lexicographical order of
106 // hash value and key. It produces exactly the key-value pairs where the value
107 // is not the default value.
108 class iterator;
109
110 iterator begin() const {
111 if (!tree_) return end();
113 }
115
116 // Iterator to traverse two maps in lockstep, producing matching value pairs
117 // for each key where at least one value is different from the respective
118 // default.
119 class double_iterator;
120
121 // An iterable to iterate over the two maps in lockstep.
128
129 ZipIterable Zip(const PersistentMap& other) const { return {*this, other}; }
130
131 explicit PersistentMap(Zone* zone, Value def_value = Value())
132 : PersistentMap(nullptr, zone, def_value) {}
133
134 private:
135 // Find the {FocusedTree} that contains a key-value pair with key hash {hash}.
136 const FocusedTree* FindHash(HashValue hash) const;
137
138 // Find the {FocusedTree} that contains a key-value pair with key hash {hash}.
139 // Output the path to this {FocusedTree} and its length. If no such
140 // {FocusedTree} exists, return {nullptr} and output the path to the last node
141 // with a matching hash prefix. Note that {length} is the length of the found
142 // path and may be less than the length of the found {FocusedTree}.
143 const FocusedTree* FindHash(HashValue hash,
144 std::array<const FocusedTree*, kHashBits>* path,
145 int* length) const;
146
147 // Load value from the leaf node on the focused path of {tree}.
148 const Value& GetFocusedValue(const FocusedTree* tree, const Key& key) const;
149
150 // Return the {FocusedTree} representing the left (bit==kLeft) or right
151 // (bit==kRight) child of the node on the path of {tree} at tree level
152 // {level}.
153 static const FocusedTree* GetChild(const FocusedTree* tree, int level,
154 Bit bit);
155
156 // Find the leftmost path in the tree, starting at the node at tree level
157 // {level} on the path of {start}. Output the level of the leaf to {level} and
158 // the path to {path}.
159 static const FocusedTree* FindLeftmost(
160 const FocusedTree* start, int* level,
161 std::array<const FocusedTree*, kHashBits>* path);
162
163 PersistentMap(const FocusedTree* tree, Zone* zone, Value def_value)
164 : tree_(tree), def_value_(def_value), zone_(zone) {}
165
169};
170
171template <class Key, class Value, class Hasher>
172struct may_be_unequal<PersistentMap<Key, Value, Hasher>> {
175 return a.tree_ != b.tree_;
176 }
177};
178
179// This structure represents a hash tree with one focused path to a specific
180// leaf. For the focused leaf, it stores key, value and key hash. The path is
181// defined by the hash bits of the focused leaf. In a traditional tree
182// datastructure, the nodes of a path form a linked list with the values being
183// the pointers outside of this path. Instead of storing all of these nodes,
184// we store an array of the pointers pointing outside of the path. This is
185// similar to the stack used when doing DFS traversal of a tree. The hash of
186// the leaf is used to know if the pointers point to the left or the
187// right of the path. As there is no explicit representation of a tree node,
188// this structure also represents all the nodes on its path. The intended node
189// depends on the tree depth, which is always clear from the referencing
190// context. So the pointer to a {FocusedTree} stored in the
191// {PersistentMap.tree_} always references the root, while a pointer from a
192// focused node of another {FocusedTree} always references to one tree level
193// lower than before.
194template <class Key, class Value, class Hasher>
195struct PersistentMap<Key, Value, Hasher>::FocusedTree {
197 // The depth of the focused path, that is, the number of pointers stored in
198 // this structure.
199 int8_t length;
201 // Out-of-line storage for hash collisions.
203 using more_iterator = typename ZoneMap<Key, Value>::const_iterator;
204 // {path_array} has to be the last member: To store an array inline, we
205 // over-allocate memory for this structure and access memory beyond
206 // {path_array}. This corresponds to a flexible array member as defined in
207 // C99.
208 const FocusedTree* path_array[1];
209 const FocusedTree*& path(int i) {
210 DCHECK(i < length);
211 return reinterpret_cast<const FocusedTree**>(
212 reinterpret_cast<uint8_t*>(this) +
213 offsetof(FocusedTree, path_array))[i];
214 }
215 const FocusedTree* path(int i) const {
216 DCHECK(i < length);
217 return reinterpret_cast<const FocusedTree* const*>(
218 reinterpret_cast<const uint8_t*>(this) +
219 offsetof(FocusedTree, path_array))[i];
220 }
221};
222
223template <class Key, class Value, class Hasher>
224class PersistentMap<Key, Value, Hasher>::HashValue {
225 public:
226 explicit HashValue(size_t hash) : bits_(static_cast<uint32_t>(hash)) {}
227
228 Bit operator[](int pos) const {
230 return bits_ & (static_cast<decltype(bits_)>(1) << (kHashBits - pos - 1))
231 ? kRight
232 : kLeft;
233 }
234
235 bool operator<(HashValue other) const { return bits_ < other.bits_; }
236 bool operator==(HashValue other) const { return bits_ == other.bits_; }
237 bool operator!=(HashValue other) const { return bits_ != other.bits_; }
239 return HashValue(bits_ ^ other.bits_);
240 }
241
242 private:
243 static_assert(sizeof(uint32_t) * 8 == kHashBits, "wrong type for bits_");
244 uint32_t bits_;
245};
246
247template <class Key, class Value, class Hasher>
248class PersistentMap<Key, Value, Hasher>::iterator {
249 public:
250 const value_type operator*() const {
251 if (current_->more) {
252 return *more_iter_;
253 } else {
254 return current_->key_value;
255 }
256 }
257
259 do {
260 if (!current_) {
261 // Iterator is past the end.
262 return *this;
263 }
264 if (current_->more) {
265 DCHECK(more_iter_ != current_->more->end());
266 ++more_iter_;
267 if (more_iter_ != current_->more->end()) return *this;
268 }
269 if (level_ == 0) {
270 *this = end(def_value_);
271 return *this;
272 }
273 --level_;
274 while (current_->key_hash[level_] == kRight || path_[level_] == nullptr) {
275 if (level_ == 0) {
276 *this = end(def_value_);
277 return *this;
278 }
279 --level_;
280 }
281 const FocusedTree* first_right_alternative = path_[level_];
282 level_++;
283 current_ = FindLeftmost(first_right_alternative, &level_, &path_);
284 if (current_->more) {
285 more_iter_ = current_->more->begin();
286 }
287 } while (!((**this).second != def_value()));
288 return *this;
289 }
290
291 bool operator==(const iterator& other) const {
292 if (is_end()) return other.is_end();
293 if (other.is_end()) return false;
294 if (current_->key_hash != other.current_->key_hash) {
295 return false;
296 } else {
297 return (**this).first == (*other).first;
298 }
299 }
300 bool operator!=(const iterator& other) const { return !(*this == other); }
301
302 bool operator<(const iterator& other) const {
303 if (is_end()) return false;
304 if (other.is_end()) return true;
305 if (current_->key_hash == other.current_->key_hash) {
306 return (**this).first < (*other).first;
307 } else {
308 return current_->key_hash < other.current_->key_hash;
309 }
310 }
311
312 bool is_end() const { return current_ == nullptr; }
313
314 const Value& def_value() { return def_value_; }
315
316 static iterator begin(const FocusedTree* tree, Value def_value) {
317 iterator i(def_value);
318 i.current_ = FindLeftmost(tree, &i.level_, &i.path_);
319 if (i.current_->more) {
320 i.more_iter_ = i.current_->more->begin();
321 }
322 // Skip entries with default value. PersistentMap iterators must never point
323 // to a default value.
324 while (!i.is_end() && !((*i).second != def_value)) ++i;
325 return i;
326 }
327
328 static iterator end(Value def_value) { return iterator(def_value); }
329
330 private:
334 std::array<const FocusedTree*, kHashBits> path_;
336
337 explicit iterator(Value def_value)
338 : level_(0), current_(nullptr), def_value_(def_value) {}
339};
340
341template <class Key, class Value, class Hasher>
342class PersistentMap<Key, Value, Hasher>::double_iterator {
343 public:
344 std::tuple<Key, Value, Value> operator*() {
345 if (first_current_) {
346 auto pair = *first_;
347 return std::make_tuple(
348 pair.first, pair.second,
349 second_current_ ? (*second_).second : second_.def_value());
350 } else {
351 DCHECK(second_current_);
352 auto pair = *second_;
353 return std::make_tuple(pair.first, first_.def_value(), pair.second);
354 }
355 }
356
358#ifdef DEBUG
359 iterator old_first = first_;
360 iterator old_second = second_;
361#endif
362 if (first_current_) {
363 ++first_;
364 DCHECK(old_first < first_);
365 }
366 if (second_current_) {
367 ++second_;
368 DCHECK(old_second < second_);
369 }
370 return *this = double_iterator(first_, second_);
371 }
372
374 : first_(first), second_(second) {
375 if (first_ == second_) {
376 first_current_ = second_current_ = true;
377 } else if (first_ < second_) {
378 first_current_ = true;
379 second_current_ = false;
380 } else {
381 DCHECK(second_ < first_);
382 first_current_ = false;
383 second_current_ = true;
384 }
385 }
386
387 bool operator!=(const double_iterator& other) {
388 return first_ != other.first_ || second_ != other.second_;
389 }
390
391 bool is_end() const { return first_.is_end() && second_.is_end(); }
392
393 private:
398};
399
400template <class Key, class Value, class Hasher>
402 Modify(key, [&](Value* value) { *value = std::move(new_value); });
403}
404
405template <class Key, class Value, class Hasher>
406template <class F>
408 static_assert(std::is_void_v<decltype(f(std::declval<Value*>()))>);
409 HashValue key_hash = HashValue(Hasher()(key));
410 std::array<const FocusedTree*, kHashBits> path;
411 int length = 0;
412 const FocusedTree* old = FindHash(key_hash, &path, &length);
413 ZoneMap<Key, Value>* more = nullptr;
414 const Value& old_value = GetFocusedValue(old, key);
415 Value new_value = old_value;
416 f(&new_value);
417 if (!may_be_unequal<Value>()(old_value, new_value)) return;
418 if (old && !(old->more == nullptr && old->key_value.key() == key)) {
419 more = zone_->New<ZoneMap<Key, Value>>(zone_);
420 if (old->more) {
421 *more = *old->more;
422 } else {
423 more->erase(old->key_value.key());
424 more->emplace(old->key_value.key(), old->key_value.value());
425 }
426 more->erase(key);
427 more->emplace(key, new_value);
428 }
429 size_t size = sizeof(FocusedTree) +
430 std::max(0, length - 1) * sizeof(const FocusedTree*);
431 FocusedTree* tree = new (zone_->Allocate<FocusedTree>(size))
432 FocusedTree{KeyValue(std::move(key), std::move(new_value)),
433 static_cast<int8_t>(length),
434 key_hash,
435 more,
436 {}};
437 for (int i = 0; i < length; ++i) {
438 tree->path(i) = path[i];
439 }
440 *this = PersistentMap(tree, zone_, def_value_);
441}
442
443template <class Key, class Value, class Hasher>
446 const FocusedTree* tree = tree_;
447 int level = 0;
448 while (tree && hash != tree->key_hash) {
449 while ((hash ^ tree->key_hash)[level] == 0) {
450 ++level;
451 }
452 tree = level < tree->length ? tree->path(level) : nullptr;
453 ++level;
454 }
455 return tree;
456}
457
458template <class Key, class Value, class Hasher>
461 HashValue hash, std::array<const FocusedTree*, kHashBits>* path,
462 int* length) const {
463 const FocusedTree* tree = tree_;
464 int level = 0;
465 while (tree && hash != tree->key_hash) {
466 int map_length = tree->length;
467 while ((hash ^ tree->key_hash)[level] == 0) {
468 (*path)[level] = level < map_length ? tree->path(level) : nullptr;
469 ++level;
470 }
471 (*path)[level] = tree;
472 tree = level < tree->length ? tree->path(level) : nullptr;
473 ++level;
474 }
475 if (tree) {
476 while (level < tree->length) {
477 (*path)[level] = tree->path(level);
478 ++level;
479 }
480 }
481 *length = level;
482 return tree;
483}
484
485template <class Key, class Value, class Hasher>
487 const FocusedTree* tree, const Key& key) const {
488 if (!tree) {
489 return def_value_;
490 }
491 if (tree->more) {
492 auto it = tree->more->find(key);
493 if (it == tree->more->end())
494 return def_value_;
495 else
496 return it->second;
497 } else {
498 if (key == tree->key_value.key()) {
499 return tree->key_value.value();
500 } else {
501 return def_value_;
502 }
503 }
504}
505
506template <class Key, class Value, class Hasher>
509 Bit bit) {
510 if (tree->key_hash[level] == bit) {
511 return tree;
512 } else if (level < tree->length) {
513 return tree->path(level);
514 } else {
515 return nullptr;
516 }
517}
518
519template <class Key, class Value, class Hasher>
522 const FocusedTree* start, int* level,
523 std::array<const FocusedTree*, kHashBits>* path) {
524 const FocusedTree* current = start;
525 while (*level < current->length) {
526 if (const FocusedTree* left_child = GetChild(current, *level, kLeft)) {
527 (*path)[*level] = GetChild(current, *level, kRight);
528 current = left_child;
529 ++*level;
530 } else if (const FocusedTree* right_child =
531 GetChild(current, *level, kRight)) {
532 (*path)[*level] = GetChild(current, *level, kLeft);
533 current = right_child;
534 ++*level;
535 } else {
536 UNREACHABLE();
537 }
538 }
539 return current;
540}
541
542template <class Key, class Value, class Hasher>
543std::ostream& operator<<(std::ostream& os,
545 os << "{";
546 bool first = true;
547 for (auto pair : map) {
548 if (!first) os << ", ";
549 first = false;
550 os << pair.first << ": " << pair.second;
551 }
552 return os << "}";
553}
554
555} // namespace compiler
556} // namespace internal
557} // namespace v8
558
559#endif // V8_COMPILER_PERSISTENT_MAP_H_
SourcePosition pos
HashValue operator^(HashValue other) const
bool operator<(const iterator &other) const
static iterator begin(const FocusedTree *tree, Value def_value)
bool operator!=(const iterator &other) const
std::array< const FocusedTree *, kHashBits > path_
bool operator==(const iterator &other) const
const Value & Get(const Key &key) const
static const FocusedTree * GetChild(const FocusedTree *tree, int level, Bit bit)
const Value & GetFocusedValue(const FocusedTree *tree, const Key &key) const
PersistentMap(Zone *zone, Value def_value=Value())
static const FocusedTree * FindLeftmost(const FocusedTree *start, int *level, std::array< const FocusedTree *, kHashBits > *path)
PersistentMap(const FocusedTree *tree, Zone *zone, Value def_value)
const FocusedTree * FindHash(HashValue hash) const
const FocusedTree * FindHash(HashValue hash, std::array< const FocusedTree *, kHashBits > *path, int *length) const
ZipIterable Zip(const PersistentMap &other) const
bool operator==(const PersistentMap &other) const
bool operator!=(const PersistentMap &other) const
Zone * zone_
int start
LineAndColumn current
double second
std::ostream & operator<<(std::ostream &os, AccessMode access_mode)
base::uc32 current_
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
typename ZoneMap< Key, Value >::const_iterator more_iterator
bool operator()(const PersistentMap< Key, Value, Hasher > &a, const PersistentMap< Key, Value, Hasher > &b)
bool operator()(const T &a, const T &b)