|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #ifndef ds_InlineMap_h |
|
8 #define ds_InlineMap_h |
|
9 |
|
10 #include "jsalloc.h" |
|
11 |
|
12 #include "js/HashTable.h" |
|
13 |
|
14 namespace js { |
|
15 |
|
16 /* |
|
17 * A type can only be used as an InlineMap key if zero is an invalid key value |
|
18 * (and thus may be used as a tombstone value by InlineMap). |
|
19 */ |
|
20 template <typename T> struct ZeroIsReserved { static const bool result = false; }; |
|
21 template <typename T> struct ZeroIsReserved<T *> { static const bool result = true; }; |
|
22 |
|
23 template <typename K, typename V, size_t InlineElems> |
|
24 class InlineMap |
|
25 { |
|
26 public: |
|
27 typedef HashMap<K, V, DefaultHasher<K>, SystemAllocPolicy> WordMap; |
|
28 |
|
29 struct InlineElem |
|
30 { |
|
31 K key; |
|
32 V value; |
|
33 }; |
|
34 |
|
35 private: |
|
36 typedef typename WordMap::Ptr WordMapPtr; |
|
37 typedef typename WordMap::AddPtr WordMapAddPtr; |
|
38 typedef typename WordMap::Range WordMapRange; |
|
39 |
|
40 size_t inlNext; |
|
41 size_t inlCount; |
|
42 InlineElem inl[InlineElems]; |
|
43 WordMap map; |
|
44 |
|
45 void checkStaticInvariants() { |
|
46 JS_STATIC_ASSERT(ZeroIsReserved<K>::result); |
|
47 } |
|
48 |
|
49 bool usingMap() const { |
|
50 return inlNext > InlineElems; |
|
51 } |
|
52 |
|
53 bool switchToMap() { |
|
54 JS_ASSERT(inlNext == InlineElems); |
|
55 |
|
56 if (map.initialized()) { |
|
57 map.clear(); |
|
58 } else { |
|
59 if (!map.init(count())) |
|
60 return false; |
|
61 JS_ASSERT(map.initialized()); |
|
62 } |
|
63 |
|
64 for (InlineElem *it = inl, *end = inl + inlNext; it != end; ++it) { |
|
65 if (it->key && !map.putNew(it->key, it->value)) |
|
66 return false; |
|
67 } |
|
68 |
|
69 inlNext = InlineElems + 1; |
|
70 JS_ASSERT(map.count() == inlCount); |
|
71 JS_ASSERT(usingMap()); |
|
72 return true; |
|
73 } |
|
74 |
|
75 MOZ_NEVER_INLINE |
|
76 bool switchAndAdd(const K &key, const V &value) { |
|
77 if (!switchToMap()) |
|
78 return false; |
|
79 |
|
80 return map.putNew(key, value); |
|
81 } |
|
82 |
|
83 public: |
|
84 explicit InlineMap() |
|
85 : inlNext(0), inlCount(0) { |
|
86 checkStaticInvariants(); /* Force the template to instantiate the static invariants. */ |
|
87 } |
|
88 |
|
89 class Entry |
|
90 { |
|
91 friend class InlineMap; |
|
92 const K &key_; |
|
93 V &value_; |
|
94 |
|
95 Entry(const K &key, V &value) : key_(key), value_(value) {} |
|
96 |
|
97 public: |
|
98 const K &key() { return key_; } |
|
99 V &value() { return value_; } |
|
100 }; /* class Entry */ |
|
101 |
|
102 class Ptr |
|
103 { |
|
104 friend class InlineMap; |
|
105 |
|
106 WordMapPtr mapPtr; |
|
107 InlineElem *inlPtr; |
|
108 bool isInlinePtr; |
|
109 |
|
110 typedef Ptr ******* ConvertibleToBool; |
|
111 |
|
112 explicit Ptr(WordMapPtr p) : mapPtr(p), isInlinePtr(false) {} |
|
113 explicit Ptr(InlineElem *ie) : inlPtr(ie), isInlinePtr(true) {} |
|
114 void operator==(const Ptr &other); |
|
115 |
|
116 public: |
|
117 /* Leaves Ptr uninitialized. */ |
|
118 Ptr() { |
|
119 #ifdef DEBUG |
|
120 inlPtr = (InlineElem *) 0xbad; |
|
121 isInlinePtr = true; |
|
122 #endif |
|
123 } |
|
124 |
|
125 /* Default copy constructor works for this structure. */ |
|
126 |
|
127 bool found() const { |
|
128 return isInlinePtr ? bool(inlPtr) : mapPtr.found(); |
|
129 } |
|
130 |
|
131 operator ConvertibleToBool() const { |
|
132 return ConvertibleToBool(found()); |
|
133 } |
|
134 |
|
135 K &key() { |
|
136 JS_ASSERT(found()); |
|
137 return isInlinePtr ? inlPtr->key : mapPtr->key(); |
|
138 } |
|
139 |
|
140 V &value() { |
|
141 JS_ASSERT(found()); |
|
142 return isInlinePtr ? inlPtr->value : mapPtr->value(); |
|
143 } |
|
144 }; /* class Ptr */ |
|
145 |
|
146 class AddPtr |
|
147 { |
|
148 friend class InlineMap; |
|
149 |
|
150 WordMapAddPtr mapAddPtr; |
|
151 InlineElem *inlAddPtr; |
|
152 bool isInlinePtr; |
|
153 /* Indicates whether inlAddPtr is a found result or an add pointer. */ |
|
154 bool inlPtrFound; |
|
155 |
|
156 AddPtr(InlineElem *ptr, bool found) |
|
157 : inlAddPtr(ptr), isInlinePtr(true), inlPtrFound(found) |
|
158 {} |
|
159 |
|
160 AddPtr(const WordMapAddPtr &p) : mapAddPtr(p), isInlinePtr(false) {} |
|
161 |
|
162 void operator==(const AddPtr &other); |
|
163 |
|
164 typedef AddPtr ******* ConvertibleToBool; |
|
165 |
|
166 public: |
|
167 AddPtr() {} |
|
168 |
|
169 bool found() const { |
|
170 return isInlinePtr ? inlPtrFound : mapAddPtr.found(); |
|
171 } |
|
172 |
|
173 operator ConvertibleToBool() const { |
|
174 return found() ? ConvertibleToBool(1) : ConvertibleToBool(0); |
|
175 } |
|
176 |
|
177 V &value() { |
|
178 JS_ASSERT(found()); |
|
179 if (isInlinePtr) |
|
180 return inlAddPtr->value; |
|
181 return mapAddPtr->value(); |
|
182 } |
|
183 }; /* class AddPtr */ |
|
184 |
|
185 size_t count() { |
|
186 return usingMap() ? map.count() : inlCount; |
|
187 } |
|
188 |
|
189 bool empty() const { |
|
190 return usingMap() ? map.empty() : !inlCount; |
|
191 } |
|
192 |
|
193 void clear() { |
|
194 inlNext = 0; |
|
195 inlCount = 0; |
|
196 } |
|
197 |
|
198 bool isMap() const { |
|
199 return usingMap(); |
|
200 } |
|
201 |
|
202 const WordMap &asMap() const { |
|
203 JS_ASSERT(isMap()); |
|
204 return map; |
|
205 } |
|
206 |
|
207 const InlineElem *asInline() const { |
|
208 JS_ASSERT(!isMap()); |
|
209 return inl; |
|
210 } |
|
211 |
|
212 const InlineElem *inlineEnd() const { |
|
213 JS_ASSERT(!isMap()); |
|
214 return inl + inlNext; |
|
215 } |
|
216 |
|
217 MOZ_ALWAYS_INLINE |
|
218 Ptr lookup(const K &key) { |
|
219 if (usingMap()) |
|
220 return Ptr(map.lookup(key)); |
|
221 |
|
222 for (InlineElem *it = inl, *end = inl + inlNext; it != end; ++it) { |
|
223 if (it->key == key) |
|
224 return Ptr(it); |
|
225 } |
|
226 |
|
227 return Ptr(nullptr); |
|
228 } |
|
229 |
|
230 MOZ_ALWAYS_INLINE |
|
231 AddPtr lookupForAdd(const K &key) { |
|
232 if (usingMap()) |
|
233 return AddPtr(map.lookupForAdd(key)); |
|
234 |
|
235 for (InlineElem *it = inl, *end = inl + inlNext; it != end; ++it) { |
|
236 if (it->key == key) |
|
237 return AddPtr(it, true); |
|
238 } |
|
239 |
|
240 /* |
|
241 * The add pointer that's returned here may indicate the limit entry of |
|
242 * the linear space, in which case the |add| operation will initialize |
|
243 * the map if necessary and add the entry there. |
|
244 */ |
|
245 return AddPtr(inl + inlNext, false); |
|
246 } |
|
247 |
|
248 MOZ_ALWAYS_INLINE |
|
249 bool add(AddPtr &p, const K &key, const V &value) { |
|
250 JS_ASSERT(!p); |
|
251 |
|
252 if (p.isInlinePtr) { |
|
253 InlineElem *addPtr = p.inlAddPtr; |
|
254 JS_ASSERT(addPtr == inl + inlNext); |
|
255 |
|
256 /* Switching to map mode before we add this pointer. */ |
|
257 if (addPtr == inl + InlineElems) |
|
258 return switchAndAdd(key, value); |
|
259 |
|
260 JS_ASSERT(!p.found()); |
|
261 JS_ASSERT(uintptr_t(inl + inlNext) == uintptr_t(p.inlAddPtr)); |
|
262 p.inlAddPtr->key = key; |
|
263 p.inlAddPtr->value = value; |
|
264 ++inlCount; |
|
265 ++inlNext; |
|
266 return true; |
|
267 } |
|
268 |
|
269 return map.add(p.mapAddPtr, key, value); |
|
270 } |
|
271 |
|
272 MOZ_ALWAYS_INLINE |
|
273 bool put(const K &key, const V &value) { |
|
274 AddPtr p = lookupForAdd(key); |
|
275 if (p) { |
|
276 p.value() = value; |
|
277 return true; |
|
278 } |
|
279 return add(p, key, value); |
|
280 } |
|
281 |
|
282 void remove(Ptr p) { |
|
283 JS_ASSERT(p); |
|
284 if (p.isInlinePtr) { |
|
285 JS_ASSERT(inlCount > 0); |
|
286 JS_ASSERT(p.inlPtr->key != nullptr); |
|
287 p.inlPtr->key = nullptr; |
|
288 --inlCount; |
|
289 return; |
|
290 } |
|
291 JS_ASSERT(map.initialized() && usingMap()); |
|
292 map.remove(p.mapPtr); |
|
293 } |
|
294 |
|
295 void remove(const K &key) { |
|
296 if (Ptr p = lookup(key)) |
|
297 remove(p); |
|
298 } |
|
299 |
|
300 class Range |
|
301 { |
|
302 friend class InlineMap; |
|
303 |
|
304 WordMapRange mapRange; |
|
305 InlineElem *cur; |
|
306 InlineElem *end; |
|
307 bool isInline; |
|
308 |
|
309 explicit Range(WordMapRange r) |
|
310 : cur(nullptr), end(nullptr), /* Avoid GCC 4.3.3 over-warning. */ |
|
311 isInline(false) { |
|
312 mapRange = r; |
|
313 JS_ASSERT(!isInlineRange()); |
|
314 } |
|
315 |
|
316 Range(const InlineElem *begin, const InlineElem *end_) |
|
317 : cur(const_cast<InlineElem *>(begin)), |
|
318 end(const_cast<InlineElem *>(end_)), |
|
319 isInline(true) { |
|
320 advancePastNulls(cur); |
|
321 JS_ASSERT(isInlineRange()); |
|
322 } |
|
323 |
|
324 bool checkInlineRangeInvariants() const { |
|
325 JS_ASSERT(uintptr_t(cur) <= uintptr_t(end)); |
|
326 JS_ASSERT_IF(cur != end, cur->key != nullptr); |
|
327 return true; |
|
328 } |
|
329 |
|
330 bool isInlineRange() const { |
|
331 JS_ASSERT_IF(isInline, checkInlineRangeInvariants()); |
|
332 return isInline; |
|
333 } |
|
334 |
|
335 void advancePastNulls(InlineElem *begin) { |
|
336 InlineElem *newCur = begin; |
|
337 while (newCur < end && nullptr == newCur->key) |
|
338 ++newCur; |
|
339 JS_ASSERT(uintptr_t(newCur) <= uintptr_t(end)); |
|
340 cur = newCur; |
|
341 } |
|
342 |
|
343 void bumpCurPtr() { |
|
344 JS_ASSERT(isInlineRange()); |
|
345 advancePastNulls(cur + 1); |
|
346 } |
|
347 |
|
348 void operator==(const Range &other); |
|
349 |
|
350 public: |
|
351 bool empty() const { |
|
352 return isInlineRange() ? cur == end : mapRange.empty(); |
|
353 } |
|
354 |
|
355 Entry front() { |
|
356 JS_ASSERT(!empty()); |
|
357 if (isInlineRange()) |
|
358 return Entry(cur->key, cur->value); |
|
359 return Entry(mapRange.front().key(), mapRange.front().value()); |
|
360 } |
|
361 |
|
362 void popFront() { |
|
363 JS_ASSERT(!empty()); |
|
364 if (isInlineRange()) |
|
365 bumpCurPtr(); |
|
366 else |
|
367 mapRange.popFront(); |
|
368 } |
|
369 }; /* class Range */ |
|
370 |
|
371 Range all() const { |
|
372 return usingMap() ? Range(map.all()) : Range(inl, inl + inlNext); |
|
373 } |
|
374 }; /* class InlineMap */ |
|
375 |
|
376 } /* namespace js */ |
|
377 |
|
378 #endif /* ds_InlineMap_h */ |