/src/hermes/include/hermes/ADT/ScopedHashTable.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #ifndef HERMES_ADT_SCOPEDHASHTABLE_H |
9 | | #define HERMES_ADT_SCOPEDHASHTABLE_H |
10 | | |
11 | | #include "llvh/ADT/DenseMap.h" |
12 | | #include "llvh/Support/Allocator.h" |
13 | | |
14 | | // hermes::ScopedHashTable is a drop-in replacement for llvh::ScopedHashTable, |
15 | | // but will allow us to export the current values in scope. |
16 | | |
17 | | namespace hermes { |
18 | | template <typename K, typename V> |
19 | | class ScopedHashTableNode { |
20 | | public: |
21 | | K key_; |
22 | | V value_; |
23 | | |
24 | | ScopedHashTableNode<K, V> *nextShadowed_{nullptr}; |
25 | | ScopedHashTableNode<K, V> *nextInScope_{nullptr}; |
26 | | uint32_t depth_; |
27 | | ScopedHashTableNode(uint32_t depth, const K &key, const V &value) |
28 | 32.9k | : key_(key), value_(value), depth_(depth) {} |
29 | | }; |
30 | | |
31 | | template <typename K, typename V> |
32 | | class ScopedHashTable; |
33 | | |
34 | | template <typename K, typename V> |
35 | | class ScopedHashTableScope { |
36 | | private: |
37 | | uint32_t depth_; |
38 | | /// Start of the linked list of nodes in this scope. Owned by ScopedHashTable. |
39 | | ScopedHashTableNode<K, V> *head_{nullptr}; |
40 | | /// The scope we're shadowing. Owned by the user. |
41 | | ScopedHashTableScope<K, V> *previous_; |
42 | | /// The table we're creating a scope for. |
43 | | ScopedHashTable<K, V> &base_; |
44 | | |
45 | | public: |
46 | | ScopedHashTableScope(ScopedHashTable<K, V> &base); |
47 | | ~ScopedHashTableScope(); |
48 | | |
49 | | ScopedHashTableScope(const ScopedHashTableScope &) = delete; |
50 | | ScopedHashTableScope(ScopedHashTableScope &&) = delete; |
51 | | ScopedHashTableScope &operator=(const ScopedHashTableScope &) = delete; |
52 | | ScopedHashTableScope &operator=(ScopedHashTableScope &&) = delete; |
53 | | |
54 | | friend class ScopedHashTable<K, V>; |
55 | | }; |
56 | | |
57 | | template <typename K, typename V> |
58 | | class ScopedHashTable { |
59 | | private: |
60 | | /// Maps from keys to most current definition. All Nodes are owned by this. |
61 | | /// They are allocated by insertIntoScope and deleted by clearCurrentScope |
62 | | llvh::DenseMap<K, ScopedHashTableNode<K, V> *> map_; |
63 | | // The current scope. Owned by the user. |
64 | | ScopedHashTableScope<K, V> *scope_{nullptr}; |
65 | | |
66 | | /// Unlinks the innermost Node for a key and returns it. |
67 | 32.9k | ScopedHashTableNode<K, V> *pop(const K &key) { |
68 | 32.9k | ScopedHashTableNode<K, V> *&entry = map_[key]; |
69 | 32.9k | assert(entry && "Asked to pop an empty scope value"); |
70 | 32.9k | assert( |
71 | 32.9k | entry->depth_ == scope_->depth_ && |
72 | 32.9k | "Asked to pop value not from innermost scope"); |
73 | 32.9k | auto *ret = entry; |
74 | 32.9k | if (entry->nextShadowed_) { |
75 | 21.3k | entry = entry->nextShadowed_; |
76 | 21.3k | } else { |
77 | 11.5k | map_.erase(key); |
78 | 11.5k | } |
79 | 32.9k | return ret; |
80 | 32.9k | } |
81 | | |
82 | | /// Unlinks and deallocates all keys in the current scope. |
83 | 23.2k | void clearCurrentScope() { |
84 | 23.2k | assert(scope_ && "No current scope to clear"); |
85 | | |
86 | 23.2k | auto *current = scope_->head_; |
87 | 56.1k | while (current) { |
88 | 32.9k | assert(current->depth_ == scope_->depth_ && "Bad scope link"); |
89 | 32.9k | auto *popped = pop(current->key_); |
90 | 32.9k | assert(current == popped && "Unexpected innermost value for key"); |
91 | 32.9k | current = current->nextInScope_; |
92 | | // All nodes deallocated here. |
93 | 32.9k | delete popped; |
94 | 32.9k | } |
95 | 23.2k | scope_->head_ = nullptr; |
96 | 23.2k | } |
97 | | |
98 | | /// Create a new node and insert it into the current scope. |
99 | | void insertNewNode( |
100 | | ScopedHashTableScope<K, V> *const scope, |
101 | | const K &key, |
102 | | const V &value, |
103 | 32.9k | ScopedHashTableNode<K, V> *&entry) { |
104 | | // All Nodes allocated here. |
105 | 32.9k | auto *update = new ScopedHashTableNode<K, V>(scope->depth_, key, value); |
106 | 32.9k | assert( |
107 | 32.9k | (!entry || entry->depth_ <= scope->depth_) && |
108 | 32.9k | "Can't insert values under existing names"); |
109 | 32.9k | update->nextShadowed_ = entry; |
110 | 32.9k | update->nextInScope_ = scope->head_; |
111 | 32.9k | scope->head_ = update; |
112 | 32.9k | entry = update; |
113 | 32.9k | } |
114 | | |
115 | | public: |
116 | 147 | ScopedHashTable() : map_() {} |
117 | 147 | ~ScopedHashTable() { |
118 | 147 | assert(!scope_ && "Scopes remain when destructing ScopedHashTable"); |
119 | 147 | assert(!map_.size() && "Elements remaining in map without scope!"); |
120 | 147 | } |
121 | | |
122 | | /// Inserts a new key into the given scope. A key may not be inserted |
123 | | /// such that it would be shadowed by another scope currently in effect. |
124 | | void insertIntoScope( |
125 | | ScopedHashTableScope<K, V> *const scope, |
126 | | const K &key, |
127 | 32.9k | const V &value) { |
128 | 32.9k | assert(scope && "No currently defined scope"); |
129 | 32.9k | insertNewNode(scope, key, value, map_[key]); |
130 | 32.9k | } |
131 | | |
132 | | /// Inserts a value into the current scope. |
133 | 23.0k | void insert(const K &key, const V &value) { |
134 | 23.0k | insertIntoScope(scope_, key, value); |
135 | 23.0k | } |
136 | | |
137 | | /// If the key exists in the current scope, update its value to the specified |
138 | | /// one. Otherwise, create a new entry in current scope. |
139 | 0 | void setInCurrentScope(const K &key, const V &value) { |
140 | 0 | assert(scope_ && "No currently defined scope"); |
141 | 0 | ScopedHashTableNode<K, V> *&entry = map_[key]; |
142 | 0 | if (entry && entry->depth_ == scope_->depth_) { |
143 | | // If the key exists in the current scope, update the value. |
144 | 0 | entry->value_ = value; |
145 | 0 | } else { |
146 | | // Otherwise, create a new node in the current scope. |
147 | 0 | insertNewNode(scope_, key, value, entry); |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | /// Returns 1 if the value is defined, 0 if it's not. |
152 | 15 | uint32_t count(const K &key) const { |
153 | 15 | return map_.count(key); |
154 | 15 | } |
155 | | |
156 | | /// Gets the innermost value for a key, or default value if none. |
157 | 431k | V lookup(const K &key) const { |
158 | 431k | auto result = map_.find(key); |
159 | 431k | if (result == map_.end()) |
160 | 17.3k | return V(); |
161 | | |
162 | 414k | return result->second->value_; |
163 | 431k | } |
164 | | |
165 | | // Gets all values currently in scope. |
166 | | std::unique_ptr<llvh::DenseMap<K, V>> flatten() const { |
167 | | std::unique_ptr<llvh::DenseMap<K, V>> result{ |
168 | | new llvh::DenseMap<K, V>(map_.size())}; |
169 | | for (auto &pair : map_) { |
170 | | assert(pair.second && "Node is null"); |
171 | | (*result)[pair.first] = pair.second->value_; |
172 | | } |
173 | | return result; |
174 | | } |
175 | | |
176 | | // Gets keys in each scope. This may correspond to a \p ScopeChain. |
177 | | // Shadowed keys are ignored. 0 is innermost. |
178 | | std::unique_ptr<std::vector<std::vector<K>>> getKeysByScope() const { |
179 | | assert(scope_ && "Missing scope"); |
180 | | |
181 | | int size = scope_->depth_ + 1; |
182 | | std::unique_ptr<std::vector<std::vector<K>>> result; |
183 | | result.reset(new std::vector<std::vector<K>>()); |
184 | | result->resize(size); |
185 | | |
186 | | for (auto &pair : map_) { |
187 | | auto *node = pair.second; |
188 | | assert(node && "Node is null"); |
189 | | assert(node->depth_ <= scope_->depth_ && "Node at bad depth"); |
190 | | result->at(size - node->depth_ - 1).push_back(pair.first); |
191 | | } |
192 | | return result; |
193 | | } |
194 | | |
195 | | friend class ScopedHashTableScope<K, V>; |
196 | | }; |
197 | | |
198 | | template <typename K, typename V> |
199 | | ScopedHashTableScope<K, V>::ScopedHashTableScope(ScopedHashTable<K, V> &base) |
200 | 23.2k | : base_(base) { |
201 | 23.2k | previous_ = base.scope_; |
202 | 23.2k | depth_ = !previous_ ? 0 : previous_->depth_ + 1; |
203 | 23.2k | base.scope_ = this; |
204 | 23.2k | } |
205 | | |
206 | | template <typename K, typename V> |
207 | 23.2k | ScopedHashTableScope<K, V>::~ScopedHashTableScope() { |
208 | 23.2k | assert(this == base_.scope_ && "Deallocating scope that's not current!"); |
209 | 23.2k | base_.clearCurrentScope(); |
210 | 23.2k | base_.scope_ = previous_; |
211 | 23.2k | } |
212 | | |
213 | | } // namespace hermes |
214 | | |
215 | | #endif // HERMES_ADT_SCOPEDHASHTABLE_H |