blob: 4d519c053dffdc535046569cc5dfdb419392263a [file] [log] [blame]
/*
* Copyright (C) 2004-2019 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*);
static void visitChildren(JSCell*, SlotVisitor&);
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