|  | /* | 
|  | *  Copyright (C) 2004-2021 Apple Inc. All rights reserved. | 
|  | * | 
|  | *  This library is free software; you can redistribute it and/or | 
|  | *  modify it under the terms of the GNU Library General Public | 
|  | *  License as published by the Free Software Foundation; either | 
|  | *  version 2 of the License, or (at your option) any later version. | 
|  | * | 
|  | *  This library is distributed in the hope that it will be useful, | 
|  | *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | *  Library General Public License for more details. | 
|  | * | 
|  | *  You should have received a copy of the GNU Library General Public License | 
|  | *  along with this library; see the file COPYING.LIB.  If not, write to | 
|  | *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 
|  | *  Boston, MA 02110-1301, USA. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #pragma once | 
|  |  | 
|  | #include "JSExportMacros.h" | 
|  | #include "PropertyOffset.h" | 
|  | #include "Structure.h" | 
|  | #include "WriteBarrier.h" | 
|  | #include <wtf/HashTable.h> | 
|  | #include <wtf/MathExtras.h> | 
|  | #include <wtf/Vector.h> | 
|  | #include <wtf/text/AtomStringImpl.h> | 
|  |  | 
|  |  | 
|  | #define DUMP_PROPERTYMAP_STATS 0 | 
|  | #define DUMP_PROPERTYMAP_COLLISIONS 0 | 
|  |  | 
|  | #define PROPERTY_MAP_DELETED_ENTRY_KEY ((UniquedStringImpl*)1) | 
|  |  | 
|  | namespace JSC { | 
|  |  | 
|  | DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(PropertyTable); | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  |  | 
|  | struct PropertyMapHashTableStats { | 
|  | std::atomic<unsigned> numFinds; | 
|  | std::atomic<unsigned> numCollisions; | 
|  | std::atomic<unsigned> numLookups; | 
|  | std::atomic<unsigned> numLookupProbing; | 
|  | std::atomic<unsigned> numAdds; | 
|  | std::atomic<unsigned> numRemoves; | 
|  | std::atomic<unsigned> numRehashes; | 
|  | std::atomic<unsigned> numReinserts; | 
|  | }; | 
|  |  | 
|  | JS_EXPORT_PRIVATE extern PropertyMapHashTableStats* propertyMapHashTableStats; | 
|  |  | 
|  | #endif | 
|  |  | 
|  | inline bool isPowerOf2(unsigned v) | 
|  | { | 
|  | return hasOneBitSet(v); | 
|  | } | 
|  |  | 
|  | inline unsigned nextPowerOf2(unsigned v) | 
|  | { | 
|  | // Taken from http://www.cs.utk.edu/~vose/c-stuff/bithacks.html | 
|  | // Devised by Sean Anderson, Sepember 14, 2001 | 
|  |  | 
|  | v--; | 
|  | v |= v >> 1; | 
|  | v |= v >> 2; | 
|  | v |= v >> 4; | 
|  | v |= v >> 8; | 
|  | v |= v >> 16; | 
|  | v++; | 
|  |  | 
|  | return v; | 
|  | } | 
|  |  | 
|  | class PropertyTable final : public JSCell { | 
|  |  | 
|  | // This is the implementation for 'iterator' and 'const_iterator', | 
|  | // used for iterating over the table in insertion order. | 
|  | template<typename T> | 
|  | class ordered_iterator { | 
|  | public: | 
|  | ordered_iterator<T>& operator++() | 
|  | { | 
|  | m_valuePtr = skipDeletedEntries(m_valuePtr + 1, m_endValuePtr); | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | bool operator==(const ordered_iterator<T>& other) | 
|  | { | 
|  | return m_valuePtr == other.m_valuePtr; | 
|  | } | 
|  |  | 
|  | bool operator!=(const ordered_iterator<T>& other) | 
|  | { | 
|  | return m_valuePtr != other.m_valuePtr; | 
|  | } | 
|  |  | 
|  | T& operator*() | 
|  | { | 
|  | return *m_valuePtr; | 
|  | } | 
|  |  | 
|  | T* operator->() | 
|  | { | 
|  | return m_valuePtr; | 
|  | } | 
|  |  | 
|  | ordered_iterator(T* valuePtr, T* endValuePtr) | 
|  | : m_valuePtr(valuePtr) | 
|  | , m_endValuePtr(endValuePtr) | 
|  | { | 
|  | } | 
|  |  | 
|  | private: | 
|  | T* m_valuePtr; | 
|  | T* m_endValuePtr; | 
|  | }; | 
|  |  | 
|  | public: | 
|  | typedef JSCell Base; | 
|  | static constexpr unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal; | 
|  |  | 
|  | template<typename CellType, SubspaceAccess> | 
|  | static IsoSubspace* subspaceFor(VM& vm) | 
|  | { | 
|  | return &vm.propertyTableSpace; | 
|  | } | 
|  |  | 
|  | static constexpr bool needsDestruction = true; | 
|  | static void destroy(JSCell*); | 
|  | DECLARE_VISIT_CHILDREN; | 
|  |  | 
|  | DECLARE_EXPORT_INFO; | 
|  |  | 
|  | static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) | 
|  | { | 
|  | return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); | 
|  | } | 
|  |  | 
|  | typedef UniquedStringImpl* KeyType; | 
|  | typedef PropertyMapEntry ValueType; | 
|  |  | 
|  | // The in order iterator provides overloaded * and -> to access the Value at the current position. | 
|  | typedef ordered_iterator<ValueType> iterator; | 
|  | typedef ordered_iterator<const ValueType> const_iterator; | 
|  |  | 
|  | // The find_iterator is a pair of a pointer to a Value* an the entry in the index. | 
|  | // If 'find' does not find an entry then iter.first will be 0, and iter.second will | 
|  | // give the point in m_index where an entry should be inserted. | 
|  | typedef std::pair<ValueType*, unsigned> find_iterator; | 
|  |  | 
|  | // Constructor is passed an initial capacity, a PropertyTable to copy, or both. | 
|  | static PropertyTable* create(VM&, unsigned initialCapacity); | 
|  | static PropertyTable* clone(VM&, const PropertyTable&); | 
|  | static PropertyTable* clone(VM&, unsigned initialCapacity, const PropertyTable&); | 
|  | ~PropertyTable(); | 
|  |  | 
|  | // Ordered iteration methods. | 
|  | iterator begin(); | 
|  | iterator end(); | 
|  | const_iterator begin() const; | 
|  | const_iterator end() const; | 
|  |  | 
|  | // Find a value in the table. | 
|  | find_iterator find(const KeyType&); | 
|  | ValueType* get(const KeyType&); | 
|  | // Add a value to the table | 
|  | std::pair<find_iterator, bool> WARN_UNUSED_RETURN add(VM&, const ValueType& entry); | 
|  | // Remove a value from the table. | 
|  | void remove(VM&, const find_iterator& iter); | 
|  | void remove(VM&, const KeyType& key); | 
|  |  | 
|  | // Returns the number of values in the hashtable. | 
|  | unsigned size() const; | 
|  |  | 
|  | // Checks if there are any values in the hashtable. | 
|  | bool isEmpty() const; | 
|  |  | 
|  | // Number of slots in the property storage array in use, included deletedOffsets. | 
|  | unsigned propertyStorageSize() const; | 
|  |  | 
|  | // Used to maintain a list of unused entries in the property storage. | 
|  | void clearDeletedOffsets(); | 
|  | bool hasDeletedOffset(); | 
|  | PropertyOffset getDeletedOffset(); | 
|  | void addDeletedOffset(PropertyOffset); | 
|  |  | 
|  | PropertyOffset nextOffset(PropertyOffset inlineCapacity); | 
|  |  | 
|  | // Copy this PropertyTable, ensuring the copy has at least the capacity provided. | 
|  | PropertyTable* copy(VM&, unsigned newCapacity); | 
|  |  | 
|  | #ifndef NDEBUG | 
|  | size_t sizeInMemory(); | 
|  | void checkConsistency(); | 
|  | #endif | 
|  |  | 
|  | static ptrdiff_t offsetOfIndexSize() { return OBJECT_OFFSETOF(PropertyTable, m_indexSize); } | 
|  | static ptrdiff_t offsetOfIndexMask() { return OBJECT_OFFSETOF(PropertyTable, m_indexMask); } | 
|  | static ptrdiff_t offsetOfIndex() { return OBJECT_OFFSETOF(PropertyTable, m_index); } | 
|  |  | 
|  | static constexpr unsigned EmptyEntryIndex = 0; | 
|  |  | 
|  | private: | 
|  | PropertyTable(VM&, unsigned initialCapacity); | 
|  | PropertyTable(VM&, const PropertyTable&); | 
|  | PropertyTable(VM&, unsigned initialCapacity, const PropertyTable&); | 
|  |  | 
|  | PropertyTable(const PropertyTable&); | 
|  |  | 
|  | void finishCreation(VM&); | 
|  |  | 
|  | // Used to insert a value known not to be in the table, and where we know capacity to be available. | 
|  | void reinsert(const ValueType& entry); | 
|  |  | 
|  | // Rehash the table.  Used to grow, or to recover deleted slots. | 
|  | void rehash(VM&, unsigned newCapacity); | 
|  |  | 
|  | // The capacity of the table of values is half of the size of the index. | 
|  | unsigned tableCapacity() const; | 
|  |  | 
|  | // We keep an extra deleted slot after the array to make iteration work, | 
|  | // and to use for deleted values. Index values into the array are 1-based, | 
|  | // so this is tableCapacity() + 1. | 
|  | // For example, if m_tableSize is 16, then tableCapacity() is 8 - but the | 
|  | // values array is actually 9 long (the 9th used for the deleted value/ | 
|  | // iteration guard).  The 8 valid entries are numbered 1..8, so the | 
|  | // deleted index is 9 (0 being reserved for empty). | 
|  | unsigned deletedEntryIndex() const; | 
|  |  | 
|  | // Used in iterator creation/progression. | 
|  | template<typename T> | 
|  | static T* skipDeletedEntries(T* valuePtr, T* endValuePtr); | 
|  |  | 
|  | // The table of values lies after the hash index. | 
|  | ValueType* table(); | 
|  | const ValueType* table() const; | 
|  |  | 
|  | ValueType* tableEnd() { return table() + usedCount(); } | 
|  | const ValueType* tableEnd() const { return table() + usedCount(); } | 
|  |  | 
|  | // total number of  used entries in the values array - by either valid entries, or deleted ones. | 
|  | unsigned usedCount() const; | 
|  |  | 
|  | // The size in bytes of data needed for by the table. | 
|  | size_t dataSize(); | 
|  |  | 
|  | // Calculates the appropriate table size (rounds up to a power of two). | 
|  | static unsigned sizeForCapacity(unsigned capacity); | 
|  |  | 
|  | // Check if capacity is available. | 
|  | bool canInsert(); | 
|  |  | 
|  | unsigned m_indexSize; | 
|  | unsigned m_indexMask; | 
|  | unsigned* m_index; | 
|  | unsigned m_keyCount; | 
|  | unsigned m_deletedCount; | 
|  | std::unique_ptr<Vector<PropertyOffset>> m_deletedOffsets; | 
|  |  | 
|  | static constexpr unsigned MinimumTableSize = 16; | 
|  | }; | 
|  |  | 
|  | inline PropertyTable::iterator PropertyTable::begin() | 
|  | { | 
|  | auto* tableEnd = this->tableEnd(); | 
|  | return iterator(skipDeletedEntries(table(), tableEnd), tableEnd); | 
|  | } | 
|  |  | 
|  | inline PropertyTable::iterator PropertyTable::end() | 
|  | { | 
|  | auto* tableEnd = this->tableEnd(); | 
|  | return iterator(tableEnd, tableEnd); | 
|  | } | 
|  |  | 
|  | inline PropertyTable::const_iterator PropertyTable::begin() const | 
|  | { | 
|  | auto* tableEnd = this->tableEnd(); | 
|  | return const_iterator(skipDeletedEntries(table(), tableEnd), tableEnd); | 
|  | } | 
|  |  | 
|  | inline PropertyTable::const_iterator PropertyTable::end() const | 
|  | { | 
|  | auto* tableEnd = this->tableEnd(); | 
|  | return const_iterator(tableEnd, tableEnd); | 
|  | } | 
|  |  | 
|  | inline PropertyTable::find_iterator PropertyTable::find(const KeyType& key) | 
|  | { | 
|  | ASSERT(key); | 
|  | ASSERT(key->isAtom() || key->isSymbol()); | 
|  | unsigned hash = IdentifierRepHash::hash(key); | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numFinds; | 
|  | #endif | 
|  |  | 
|  | while (true) { | 
|  | unsigned entryIndex = m_index[hash & m_indexMask]; | 
|  | if (entryIndex == EmptyEntryIndex) | 
|  | return std::make_pair((ValueType*)nullptr, hash & m_indexMask); | 
|  | if (key == table()[entryIndex - 1].key) | 
|  | return std::make_pair(&table()[entryIndex - 1], hash & m_indexMask); | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numCollisions; | 
|  | #endif | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_COLLISIONS | 
|  | dataLog("PropertyTable collision for ", key, " (", hash, ")\n"); | 
|  | dataLog("Collided with ", table()[entryIndex - 1].key, "(", IdentifierRepHash::hash(table()[entryIndex - 1].key), ")\n"); | 
|  | #endif | 
|  |  | 
|  | hash++; | 
|  | } | 
|  | } | 
|  |  | 
|  | inline PropertyTable::ValueType* PropertyTable::get(const KeyType& key) | 
|  | { | 
|  | ASSERT(key); | 
|  | ASSERT(key->isAtom() || key->isSymbol()); | 
|  | ASSERT(key != PROPERTY_MAP_DELETED_ENTRY_KEY); | 
|  |  | 
|  | if (!m_keyCount) | 
|  | return nullptr; | 
|  |  | 
|  | unsigned hash = IdentifierRepHash::hash(key); | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numLookups; | 
|  | #endif | 
|  |  | 
|  | while (true) { | 
|  | unsigned entryIndex = m_index[hash & m_indexMask]; | 
|  | if (entryIndex == EmptyEntryIndex) | 
|  | return nullptr; | 
|  | if (key == table()[entryIndex - 1].key) { | 
|  | ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(table()[entryIndex - 1].offset)); | 
|  | return &table()[entryIndex - 1]; | 
|  | } | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numLookupProbing; | 
|  | #endif | 
|  |  | 
|  | hash++; | 
|  | } | 
|  | } | 
|  |  | 
|  | inline std::pair<PropertyTable::find_iterator, bool> WARN_UNUSED_RETURN PropertyTable::add(VM& vm, const ValueType& entry) | 
|  | { | 
|  | ASSERT(!m_deletedOffsets || !m_deletedOffsets->contains(entry.offset)); | 
|  |  | 
|  | // Look for a value with a matching key already in the array. | 
|  | find_iterator iter = find(entry.key); | 
|  | if (iter.first) | 
|  | return std::make_pair(iter, false); | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numAdds; | 
|  | #endif | 
|  |  | 
|  | // Ref the key | 
|  | entry.key->ref(); | 
|  |  | 
|  | // ensure capacity is available. | 
|  | if (!canInsert()) { | 
|  | rehash(vm, m_keyCount + 1); | 
|  | iter = find(entry.key); | 
|  | ASSERT(!iter.first); | 
|  | } | 
|  |  | 
|  | // Allocate a slot in the hashtable, and set the index to reference this. | 
|  | unsigned entryIndex = usedCount() + 1; | 
|  | m_index[iter.second] = entryIndex; | 
|  | iter.first = &table()[entryIndex - 1]; | 
|  | *iter.first = entry; | 
|  |  | 
|  | ++m_keyCount; | 
|  |  | 
|  | return std::make_pair(iter, true); | 
|  | } | 
|  |  | 
|  | inline void PropertyTable::remove(VM& vm, const find_iterator& iter) | 
|  | { | 
|  | // Removing a key that doesn't exist does nothing! | 
|  | if (!iter.first) | 
|  | return; | 
|  |  | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numRemoves; | 
|  | #endif | 
|  |  | 
|  | // Replace this one element with the deleted sentinel. Also clear out | 
|  | // the entry so we can iterate all the entries as needed. | 
|  | m_index[iter.second] = deletedEntryIndex(); | 
|  | iter.first->key->deref(); | 
|  | iter.first->key = PROPERTY_MAP_DELETED_ENTRY_KEY; | 
|  |  | 
|  | ASSERT(m_keyCount >= 1); | 
|  | --m_keyCount; | 
|  | ++m_deletedCount; | 
|  |  | 
|  | if (m_deletedCount * 4 >= m_indexSize) | 
|  | rehash(vm, m_keyCount); | 
|  | } | 
|  |  | 
|  | inline void PropertyTable::remove(VM& vm, const KeyType& key) | 
|  | { | 
|  | remove(vm, find(key)); | 
|  | } | 
|  |  | 
|  | // returns the number of values in the hashtable. | 
|  | inline unsigned PropertyTable::size() const | 
|  | { | 
|  | return m_keyCount; | 
|  | } | 
|  |  | 
|  | inline bool PropertyTable::isEmpty() const | 
|  | { | 
|  | return !m_keyCount; | 
|  | } | 
|  |  | 
|  | inline unsigned PropertyTable::propertyStorageSize() const | 
|  | { | 
|  | return size() + (m_deletedOffsets ? m_deletedOffsets->size() : 0); | 
|  | } | 
|  |  | 
|  | inline void PropertyTable::clearDeletedOffsets() | 
|  | { | 
|  | m_deletedOffsets = nullptr; | 
|  | } | 
|  |  | 
|  | inline bool PropertyTable::hasDeletedOffset() | 
|  | { | 
|  | return m_deletedOffsets && !m_deletedOffsets->isEmpty(); | 
|  | } | 
|  |  | 
|  | inline PropertyOffset PropertyTable::getDeletedOffset() | 
|  | { | 
|  | PropertyOffset offset = m_deletedOffsets->last(); | 
|  | m_deletedOffsets->removeLast(); | 
|  | return offset; | 
|  | } | 
|  |  | 
|  | inline void PropertyTable::addDeletedOffset(PropertyOffset offset) | 
|  | { | 
|  | if (!m_deletedOffsets) | 
|  | m_deletedOffsets = makeUnique<Vector<PropertyOffset>>(); | 
|  | ASSERT(!m_deletedOffsets->contains(offset)); | 
|  | m_deletedOffsets->append(offset); | 
|  | } | 
|  |  | 
|  | inline PropertyOffset PropertyTable::nextOffset(PropertyOffset inlineCapacity) | 
|  | { | 
|  | if (hasDeletedOffset()) | 
|  | return getDeletedOffset(); | 
|  |  | 
|  | return offsetForPropertyNumber(size(), inlineCapacity); | 
|  | } | 
|  |  | 
|  | inline PropertyTable* PropertyTable::copy(VM& vm, unsigned newCapacity) | 
|  | { | 
|  | ASSERT(newCapacity >= m_keyCount); | 
|  |  | 
|  | // Fast case; if the new table will be the same m_indexSize as this one, we can memcpy it, | 
|  | // save rehashing all keys. | 
|  | if (sizeForCapacity(newCapacity) == m_indexSize) | 
|  | return PropertyTable::clone(vm, *this); | 
|  | return PropertyTable::clone(vm, newCapacity, *this); | 
|  | } | 
|  |  | 
|  | #ifndef NDEBUG | 
|  | inline size_t PropertyTable::sizeInMemory() | 
|  | { | 
|  | size_t result = sizeof(PropertyTable) + dataSize(); | 
|  | if (m_deletedOffsets) | 
|  | result += (m_deletedOffsets->capacity() * sizeof(PropertyOffset)); | 
|  | return result; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | inline void PropertyTable::reinsert(const ValueType& entry) | 
|  | { | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numReinserts; | 
|  | #endif | 
|  |  | 
|  | // Used to insert a value known not to be in the table, and where | 
|  | // we know capacity to be available. | 
|  | ASSERT(canInsert()); | 
|  | find_iterator iter = find(entry.key); | 
|  | ASSERT(!iter.first); | 
|  |  | 
|  | unsigned entryIndex = usedCount() + 1; | 
|  | m_index[iter.second] = entryIndex; | 
|  | table()[entryIndex - 1] = entry; | 
|  |  | 
|  | ++m_keyCount; | 
|  | } | 
|  |  | 
|  | inline void PropertyTable::rehash(VM& vm, unsigned newCapacity) | 
|  | { | 
|  | #if DUMP_PROPERTYMAP_STATS | 
|  | ++propertyMapHashTableStats->numRehashes; | 
|  | #endif | 
|  |  | 
|  | size_t oldDataSize = dataSize(); | 
|  | unsigned* oldEntryIndices = m_index; | 
|  | iterator iter = this->begin(); | 
|  | iterator end = this->end(); | 
|  |  | 
|  | m_indexSize = sizeForCapacity(newCapacity); | 
|  | m_indexMask = m_indexSize - 1; | 
|  | m_keyCount = 0; | 
|  | m_deletedCount = 0; | 
|  |  | 
|  | m_index = static_cast<unsigned*>(PropertyTableMalloc::zeroedMalloc(dataSize())); | 
|  |  | 
|  | for (; iter != end; ++iter) { | 
|  | ASSERT(canInsert()); | 
|  | reinsert(*iter); | 
|  | } | 
|  |  | 
|  | PropertyTableMalloc::free(oldEntryIndices); | 
|  |  | 
|  | if (oldDataSize < dataSize()) | 
|  | vm.heap.reportExtraMemoryAllocated(dataSize() - oldDataSize); | 
|  | } | 
|  |  | 
|  | inline unsigned PropertyTable::tableCapacity() const { return m_indexSize >> 1; } | 
|  |  | 
|  | inline unsigned PropertyTable::deletedEntryIndex() const { return tableCapacity() + 1; } | 
|  |  | 
|  | template<typename T> | 
|  | inline T* PropertyTable::skipDeletedEntries(T* valuePtr, T* endValuePtr) | 
|  | { | 
|  | while (valuePtr < endValuePtr && valuePtr->key == PROPERTY_MAP_DELETED_ENTRY_KEY) | 
|  | ++valuePtr; | 
|  | return valuePtr; | 
|  | } | 
|  |  | 
|  | inline PropertyTable::ValueType* PropertyTable::table() | 
|  | { | 
|  | // The table of values lies after the hash index. | 
|  | return reinterpret_cast<ValueType*>(m_index + m_indexSize); | 
|  | } | 
|  |  | 
|  | inline const PropertyTable::ValueType* PropertyTable::table() const | 
|  | { | 
|  | // The table of values lies after the hash index. | 
|  | return reinterpret_cast<const ValueType*>(m_index + m_indexSize); | 
|  | } | 
|  |  | 
|  | inline unsigned PropertyTable::usedCount() const | 
|  | { | 
|  | // Total number of  used entries in the values array - by either valid entries, or deleted ones. | 
|  | return m_keyCount + m_deletedCount; | 
|  | } | 
|  |  | 
|  | inline size_t PropertyTable::dataSize() | 
|  | { | 
|  | // The size in bytes of data needed for by the table. | 
|  | // Ensure that this function can be called concurrently. | 
|  | unsigned indexSize = m_indexSize; | 
|  | return indexSize * sizeof(unsigned) + ((indexSize >> 1) + 1) * sizeof(ValueType); | 
|  | } | 
|  |  | 
|  | inline unsigned PropertyTable::sizeForCapacity(unsigned capacity) | 
|  | { | 
|  | if (capacity < MinimumTableSize / 2) | 
|  | return MinimumTableSize; | 
|  | return nextPowerOf2(capacity + 1) * 2; | 
|  | } | 
|  |  | 
|  | inline bool PropertyTable::canInsert() | 
|  | { | 
|  | return usedCount() < tableCapacity(); | 
|  | } | 
|  |  | 
|  | } // namespace JSC |