mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2025-02-17 02:18:14 +00:00
587 lines
17 KiB
C++
587 lines
17 KiB
C++
/*
|
|
* 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
|