2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2014 Apple Inc. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 #ifndef PropertyMapHashTable_h
22 #define PropertyMapHashTable_h
24 #include "JSExportMacros.h"
25 #include "PropertyOffset.h"
26 #include "Structure.h"
27 #include "WriteBarrier.h"
28 #include <wtf/CryptographicallyRandomNumber.h>
29 #include <wtf/HashTable.h>
30 #include <wtf/MathExtras.h>
31 #include <wtf/Vector.h>
32 #include <wtf/text/AtomicStringImpl.h>
35 #define DUMP_PROPERTYMAP_STATS 0
36 #define DUMP_PROPERTYMAP_COLLISIONS 0
38 #define PROPERTY_MAP_DELETED_ENTRY_KEY ((UniquedStringImpl*)1)
42 #if DUMP_PROPERTYMAP_STATS
44 struct PropertyMapHashTableStats
{
45 std::atomic
<unsigned> numFinds
;
46 std::atomic
<unsigned> numCollisions
;
47 std::atomic
<unsigned> numLookups
;
48 std::atomic
<unsigned> numLookupProbing
;
49 std::atomic
<unsigned> numAdds
;
50 std::atomic
<unsigned> numRemoves
;
51 std::atomic
<unsigned> numRehashes
;
52 std::atomic
<unsigned> numReinserts
;
55 JS_EXPORTDATA
extern PropertyMapHashTableStats
* propertyMapHashTableStats
;
59 inline bool isPowerOf2(unsigned v
)
61 return hasOneBitSet(v
);
64 inline unsigned nextPowerOf2(unsigned v
)
66 // Taken from http://www.cs.utk.edu/~vose/c-stuff/bithacks.html
67 // Devised by Sean Anderson, Sepember 14, 2001
80 class PropertyTable final
: public JSCell
{
82 // This is the implementation for 'iterator' and 'const_iterator',
83 // used for iterating over the table in insertion order.
85 class ordered_iterator
{
87 ordered_iterator
<T
>& operator++()
89 m_valuePtr
= skipDeletedEntries(m_valuePtr
+ 1);
93 bool operator==(const ordered_iterator
<T
>& other
)
95 return m_valuePtr
== other
.m_valuePtr
;
98 bool operator!=(const ordered_iterator
<T
>& other
)
100 return m_valuePtr
!= other
.m_valuePtr
;
113 ordered_iterator(T
* valuePtr
)
114 : m_valuePtr(valuePtr
)
124 static const unsigned StructureFlags
= Base::StructureFlags
| StructureIsImmortal
;
126 static const bool needsDestruction
= true;
127 static void destroy(JSCell
*);
131 static Structure
* createStructure(VM
& vm
, JSGlobalObject
* globalObject
, JSValue prototype
)
133 return Structure::create(vm
, globalObject
, prototype
, TypeInfo(CellType
, StructureFlags
), info());
136 typedef UniquedStringImpl
* KeyType
;
137 typedef PropertyMapEntry ValueType
;
139 // The in order iterator provides overloaded * and -> to access the Value at the current position.
140 typedef ordered_iterator
<ValueType
> iterator
;
141 typedef ordered_iterator
<const ValueType
> const_iterator
;
143 // The find_iterator is a pair of a pointer to a Value* an the entry in the index.
144 // If 'find' does not find an entry then iter.first will be 0, and iter.second will
145 // give the point in m_index where an entry should be inserted.
146 typedef std::pair
<ValueType
*, unsigned> find_iterator
;
148 // Constructor is passed an initial capacity, a PropertyTable to copy, or both.
149 static PropertyTable
* create(VM
&, unsigned initialCapacity
);
150 static PropertyTable
* clone(VM
&, const PropertyTable
&);
151 static PropertyTable
* clone(VM
&, unsigned initialCapacity
, const PropertyTable
&);
154 // Ordered iteration methods.
157 const_iterator
begin() const;
158 const_iterator
end() const;
160 // Find a value in the table.
161 find_iterator
find(const KeyType
&);
162 ValueType
* get(const KeyType
&);
163 // Add a value to the table
164 enum EffectOnPropertyOffset
{ PropertyOffsetMayChange
, PropertyOffsetMustNotChange
};
165 std::pair
<find_iterator
, bool> add(const ValueType
& entry
, PropertyOffset
&, EffectOnPropertyOffset
);
166 // Remove a value from the table.
167 void remove(const find_iterator
& iter
);
168 void remove(const KeyType
& key
);
170 // Returns the number of values in the hashtable.
171 unsigned size() const;
173 // Checks if there are any values in the hashtable.
174 bool isEmpty() const;
176 // Number of slots in the property storage array in use, included deletedOffsets.
177 unsigned propertyStorageSize() const;
179 // Used to maintain a list of unused entries in the property storage.
180 void clearDeletedOffsets();
181 bool hasDeletedOffset();
182 PropertyOffset
getDeletedOffset();
183 void addDeletedOffset(PropertyOffset
);
185 PropertyOffset
nextOffset(PropertyOffset inlineCapacity
);
187 // Copy this PropertyTable, ensuring the copy has at least the capacity provided.
188 PropertyTable
* copy(VM
&, unsigned newCapacity
);
191 size_t sizeInMemory();
192 void checkConsistency();
196 PropertyTable(VM
&, unsigned initialCapacity
);
197 PropertyTable(VM
&, const PropertyTable
&);
198 PropertyTable(VM
&, unsigned initialCapacity
, const PropertyTable
&);
200 PropertyTable(const PropertyTable
&);
201 // Used to insert a value known not to be in the table, and where we know capacity to be available.
202 void reinsert(const ValueType
& entry
);
204 // Rehash the table. Used to grow, or to recover deleted slots.
205 void rehash(unsigned newCapacity
);
207 // The capacity of the table of values is half of the size of the index.
208 unsigned tableCapacity() const;
210 // We keep an extra deleted slot after the array to make iteration work,
211 // and to use for deleted values. Index values into the array are 1-based,
212 // so this is tableCapacity() + 1.
213 // For example, if m_tableSize is 16, then tableCapacity() is 8 - but the
214 // values array is actually 9 long (the 9th used for the deleted value/
215 // iteration guard). The 8 valid entries are numbered 1..8, so the
216 // deleted index is 9 (0 being reserved for empty).
217 unsigned deletedEntryIndex() const;
219 // Used in iterator creation/progression.
221 static T
* skipDeletedEntries(T
* valuePtr
);
223 // The table of values lies after the hash index.
225 const ValueType
* table() const;
227 // total number of used entries in the values array - by either valid entries, or deleted ones.
228 unsigned usedCount() const;
230 // The size in bytes of data needed for by the table.
233 // Calculates the appropriate table size (rounds up to a power of two).
234 static unsigned sizeForCapacity(unsigned capacity
);
236 // Check if capacity is available.
239 unsigned m_indexSize
;
240 unsigned m_indexMask
;
243 unsigned m_deletedCount
;
244 std::unique_ptr
<Vector
<PropertyOffset
>> m_deletedOffsets
;
246 static const unsigned MinimumTableSize
= 16;
247 static const unsigned EmptyEntryIndex
= 0;
250 inline PropertyTable::iterator
PropertyTable::begin()
252 return iterator(skipDeletedEntries(table()));
255 inline PropertyTable::iterator
PropertyTable::end()
257 return iterator(table() + usedCount());
260 inline PropertyTable::const_iterator
PropertyTable::begin() const
262 return const_iterator(skipDeletedEntries(table()));
265 inline PropertyTable::const_iterator
PropertyTable::end() const
267 return const_iterator(table() + usedCount());
270 inline PropertyTable::find_iterator
PropertyTable::find(const KeyType
& key
)
273 ASSERT(key
->isAtomic() || key
->isSymbol());
274 unsigned hash
= IdentifierRepHash::hash(key
);
277 #if DUMP_PROPERTYMAP_STATS
278 ++propertyMapHashTableStats
->numFinds
;
282 unsigned entryIndex
= m_index
[hash
& m_indexMask
];
283 if (entryIndex
== EmptyEntryIndex
)
284 return std::make_pair((ValueType
*)0, hash
& m_indexMask
);
285 if (key
== table()[entryIndex
- 1].key
)
286 return std::make_pair(&table()[entryIndex
- 1], hash
& m_indexMask
);
289 step
= WTF::doubleHash(IdentifierRepHash::hash(key
)) | 1;
291 #if DUMP_PROPERTYMAP_STATS
292 ++propertyMapHashTableStats
->numCollisions
;
295 #if DUMP_PROPERTYMAP_COLLISIONS
296 dataLog("PropertyTable collision for ", key
, " (", hash
, ") with step ", step
, "\n");
297 dataLog("Collided with ", table()[entryIndex
- 1].key
, "(", IdentifierRepHash::hash(table()[entryIndex
- 1].key
), ")\n");
304 inline PropertyTable::ValueType
* PropertyTable::get(const KeyType
& key
)
307 ASSERT(key
->isAtomic() || key
->isSymbol());
312 unsigned hash
= IdentifierRepHash::hash(key
);
315 #if DUMP_PROPERTYMAP_STATS
316 ++propertyMapHashTableStats
->numLookups
;
320 unsigned entryIndex
= m_index
[hash
& m_indexMask
];
321 if (entryIndex
== EmptyEntryIndex
)
323 if (key
== table()[entryIndex
- 1].key
)
324 return &table()[entryIndex
- 1];
326 #if DUMP_PROPERTYMAP_STATS
327 ++propertyMapHashTableStats
->numLookupProbing
;
331 step
= WTF::doubleHash(IdentifierRepHash::hash(key
)) | 1;
336 inline std::pair
<PropertyTable::find_iterator
, bool> PropertyTable::add(const ValueType
& entry
, PropertyOffset
& offset
, EffectOnPropertyOffset offsetEffect
)
338 // Look for a value with a matching key already in the array.
339 find_iterator iter
= find(entry
.key
);
341 RELEASE_ASSERT(iter
.first
->offset
<= offset
);
342 return std::make_pair(iter
, false);
345 #if DUMP_PROPERTYMAP_STATS
346 ++propertyMapHashTableStats
->numAdds
;
352 // ensure capacity is available.
354 rehash(m_keyCount
+ 1);
355 iter
= find(entry
.key
);
359 // Allocate a slot in the hashtable, and set the index to reference this.
360 unsigned entryIndex
= usedCount() + 1;
361 m_index
[iter
.second
] = entryIndex
;
362 iter
.first
= &table()[entryIndex
- 1];
367 if (offsetEffect
== PropertyOffsetMayChange
)
368 offset
= std::max(offset
, entry
.offset
);
370 RELEASE_ASSERT(offset
>= entry
.offset
);
372 return std::make_pair(iter
, true);
375 inline void PropertyTable::remove(const find_iterator
& iter
)
377 // Removing a key that doesn't exist does nothing!
381 #if DUMP_PROPERTYMAP_STATS
382 ++propertyMapHashTableStats
->numRemoves
;
385 // Replace this one element with the deleted sentinel. Also clear out
386 // the entry so we can iterate all the entries as needed.
387 m_index
[iter
.second
] = deletedEntryIndex();
388 iter
.first
->key
->deref();
389 iter
.first
->key
= PROPERTY_MAP_DELETED_ENTRY_KEY
;
391 ASSERT(m_keyCount
>= 1);
395 if (m_deletedCount
* 4 >= m_indexSize
)
399 inline void PropertyTable::remove(const KeyType
& key
)
404 // returns the number of values in the hashtable.
405 inline unsigned PropertyTable::size() const
410 inline bool PropertyTable::isEmpty() const
415 inline unsigned PropertyTable::propertyStorageSize() const
417 return size() + (m_deletedOffsets
? m_deletedOffsets
->size() : 0);
420 inline void PropertyTable::clearDeletedOffsets()
422 m_deletedOffsets
= nullptr;
425 inline bool PropertyTable::hasDeletedOffset()
427 return m_deletedOffsets
&& !m_deletedOffsets
->isEmpty();
430 inline PropertyOffset
PropertyTable::getDeletedOffset()
432 PropertyOffset offset
= m_deletedOffsets
->last();
433 m_deletedOffsets
->removeLast();
437 inline void PropertyTable::addDeletedOffset(PropertyOffset offset
)
439 if (!m_deletedOffsets
)
440 m_deletedOffsets
= std::make_unique
<Vector
<PropertyOffset
>>();
441 m_deletedOffsets
->append(offset
);
444 inline PropertyOffset
PropertyTable::nextOffset(PropertyOffset inlineCapacity
)
446 if (hasDeletedOffset())
447 return getDeletedOffset();
449 return offsetForPropertyNumber(size(), inlineCapacity
);
452 inline PropertyTable
* PropertyTable::copy(VM
& vm
, unsigned newCapacity
)
454 ASSERT(newCapacity
>= m_keyCount
);
456 // Fast case; if the new table will be the same m_indexSize as this one, we can memcpy it,
457 // save rehashing all keys.
458 if (sizeForCapacity(newCapacity
) == m_indexSize
)
459 return PropertyTable::clone(vm
, *this);
460 return PropertyTable::clone(vm
, newCapacity
, *this);
464 inline size_t PropertyTable::sizeInMemory()
466 size_t result
= sizeof(PropertyTable
) + dataSize();
467 if (m_deletedOffsets
)
468 result
+= (m_deletedOffsets
->capacity() * sizeof(PropertyOffset
));
473 inline void PropertyTable::reinsert(const ValueType
& entry
)
475 #if DUMP_PROPERTYMAP_STATS
476 ++propertyMapHashTableStats
->numReinserts
;
479 // Used to insert a value known not to be in the table, and where
480 // we know capacity to be available.
482 find_iterator iter
= find(entry
.key
);
485 unsigned entryIndex
= usedCount() + 1;
486 m_index
[iter
.second
] = entryIndex
;
487 table()[entryIndex
- 1] = entry
;
492 inline void PropertyTable::rehash(unsigned newCapacity
)
494 #if DUMP_PROPERTYMAP_STATS
495 ++propertyMapHashTableStats
->numRehashes
;
498 unsigned* oldEntryIndices
= m_index
;
499 iterator iter
= this->begin();
500 iterator end
= this->end();
502 m_indexSize
= sizeForCapacity(newCapacity
);
503 m_indexMask
= m_indexSize
- 1;
506 m_index
= static_cast<unsigned*>(fastZeroedMalloc(dataSize()));
508 for (; iter
!= end
; ++iter
) {
513 fastFree(oldEntryIndices
);
516 inline unsigned PropertyTable::tableCapacity() const { return m_indexSize
>> 1; }
518 inline unsigned PropertyTable::deletedEntryIndex() const { return tableCapacity() + 1; }
521 inline T
* PropertyTable::skipDeletedEntries(T
* valuePtr
)
523 while (valuePtr
->key
== PROPERTY_MAP_DELETED_ENTRY_KEY
)
528 inline PropertyTable::ValueType
* PropertyTable::table()
530 // The table of values lies after the hash index.
531 return reinterpret_cast<ValueType
*>(m_index
+ m_indexSize
);
534 inline const PropertyTable::ValueType
* PropertyTable::table() const
536 // The table of values lies after the hash index.
537 return reinterpret_cast<const ValueType
*>(m_index
+ m_indexSize
);
540 inline unsigned PropertyTable::usedCount() const
542 // Total number of used entries in the values array - by either valid entries, or deleted ones.
543 return m_keyCount
+ m_deletedCount
;
546 inline size_t PropertyTable::dataSize()
548 // The size in bytes of data needed for by the table.
549 return m_indexSize
* sizeof(unsigned) + ((tableCapacity()) + 1) * sizeof(ValueType
);
552 inline unsigned PropertyTable::sizeForCapacity(unsigned capacity
)
554 if (capacity
< MinimumTableSize
/ 2)
555 return MinimumTableSize
;
556 return nextPowerOf2(capacity
+ 1) * 2;
559 inline bool PropertyTable::canInsert()
561 return usedCount() < tableCapacity();
566 #endif // PropertyMapHashTable_h