/* | |
* Copyright (C) 2008, 2009 Apple Inc. All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | |
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | |
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#include "config.h" | |
#include "Structure.h" | |
#include "Identifier.h" | |
#include "JSObject.h" | |
#include "JSPropertyNameIterator.h" | |
#include "Lookup.h" | |
#include "PropertyNameArray.h" | |
#include "StructureChain.h" | |
#include <wtf/RefCountedLeakCounter.h> | |
#include <wtf/RefPtr.h> | |
#if ENABLE(JSC_MULTIPLE_THREADS) | |
#include <wtf/Threading.h> | |
#endif | |
#define DUMP_STRUCTURE_ID_STATISTICS 0 | |
#ifndef NDEBUG | |
#define DO_PROPERTYMAP_CONSTENCY_CHECK 0 | |
#else | |
#define DO_PROPERTYMAP_CONSTENCY_CHECK 0 | |
#endif | |
using namespace std; | |
using namespace WTF; | |
namespace JSC { | |
// Choose a number for the following so that most property maps are smaller, | |
// but it's not going to blow out the stack to allocate this number of pointers. | |
static const int smallMapThreshold = 1024; | |
// The point at which the function call overhead of the qsort implementation | |
// becomes small compared to the inefficiency of insertion sort. | |
static const unsigned tinyMapThreshold = 20; | |
static const unsigned newTableSize = 16; | |
#ifndef NDEBUG | |
static WTF::RefCountedLeakCounter structureCounter("Structure"); | |
#if ENABLE(JSC_MULTIPLE_THREADS) | |
static Mutex& ignoreSetMutex = *(new Mutex); | |
#endif | |
static bool shouldIgnoreLeaks; | |
static HashSet<Structure*>& ignoreSet = *(new HashSet<Structure*>); | |
#endif | |
#if DUMP_STRUCTURE_ID_STATISTICS | |
static HashSet<Structure*>& liveStructureSet = *(new HashSet<Structure*>); | |
#endif | |
static int comparePropertyMapEntryIndices(const void* a, const void* b); | |
inline void Structure::setTransitionTable(TransitionTable* table) | |
{ | |
ASSERT(m_isUsingSingleSlot); | |
#ifndef NDEBUG | |
setSingleTransition(0); | |
#endif | |
m_isUsingSingleSlot = false; | |
m_transitions.m_table = table; | |
// This implicitly clears the flag that indicates we're using a single transition | |
ASSERT(!m_isUsingSingleSlot); | |
} | |
// The contains and get methods accept imprecise matches, so if an unspecialised transition exists | |
// for the given key they will consider that transition to be a match. If a specialised transition | |
// exists and it matches the provided specificValue, get will return the specific transition. | |
inline bool Structure::transitionTableContains(const StructureTransitionTableHash::Key& key, JSCell* specificValue) | |
{ | |
if (m_isUsingSingleSlot) { | |
Structure* existingTransition = singleTransition(); | |
return existingTransition && existingTransition->m_nameInPrevious.get() == key.first | |
&& existingTransition->m_attributesInPrevious == key.second | |
&& (existingTransition->m_specificValueInPrevious == specificValue || existingTransition->m_specificValueInPrevious == 0); | |
} | |
TransitionTable::iterator find = transitionTable()->find(key); | |
if (find == transitionTable()->end()) | |
return false; | |
return find->second.first || find->second.second->transitionedFor(specificValue); | |
} | |
inline Structure* Structure::transitionTableGet(const StructureTransitionTableHash::Key& key, JSCell* specificValue) const | |
{ | |
if (m_isUsingSingleSlot) { | |
Structure* existingTransition = singleTransition(); | |
if (existingTransition && existingTransition->m_nameInPrevious.get() == key.first | |
&& existingTransition->m_attributesInPrevious == key.second | |
&& (existingTransition->m_specificValueInPrevious == specificValue || existingTransition->m_specificValueInPrevious == 0)) | |
return existingTransition; | |
return 0; | |
} | |
Transition transition = transitionTable()->get(key); | |
if (transition.second && transition.second->transitionedFor(specificValue)) | |
return transition.second; | |
return transition.first; | |
} | |
inline bool Structure::transitionTableHasTransition(const StructureTransitionTableHash::Key& key) const | |
{ | |
if (m_isUsingSingleSlot) { | |
Structure* transition = singleTransition(); | |
return transition && transition->m_nameInPrevious == key.first | |
&& transition->m_attributesInPrevious == key.second; | |
} | |
return transitionTable()->contains(key); | |
} | |
inline void Structure::transitionTableRemove(const StructureTransitionTableHash::Key& key, JSCell* specificValue) | |
{ | |
if (m_isUsingSingleSlot) { | |
ASSERT(transitionTableContains(key, specificValue)); | |
setSingleTransition(0); | |
return; | |
} | |
TransitionTable::iterator find = transitionTable()->find(key); | |
if (!specificValue) | |
find->second.first = 0; | |
else | |
find->second.second = 0; | |
if (!find->second.first && !find->second.second) | |
transitionTable()->remove(find); | |
} | |
inline void Structure::transitionTableAdd(const StructureTransitionTableHash::Key& key, Structure* structure, JSCell* specificValue) | |
{ | |
if (m_isUsingSingleSlot) { | |
if (!singleTransition()) { | |
setSingleTransition(structure); | |
return; | |
} | |
Structure* existingTransition = singleTransition(); | |
TransitionTable* transitionTable = new TransitionTable; | |
setTransitionTable(transitionTable); | |
if (existingTransition) | |
transitionTableAdd(std::make_pair(existingTransition->m_nameInPrevious.get(), existingTransition->m_attributesInPrevious), existingTransition, existingTransition->m_specificValueInPrevious); | |
} | |
if (!specificValue) { | |
TransitionTable::iterator find = transitionTable()->find(key); | |
if (find == transitionTable()->end()) | |
transitionTable()->add(key, Transition(structure, static_cast<Structure*>(0))); | |
else | |
find->second.first = structure; | |
} else { | |
// If we're adding a transition to a specific value, then there cannot be | |
// an existing transition | |
ASSERT(!transitionTable()->contains(key)); | |
transitionTable()->add(key, Transition(static_cast<Structure*>(0), structure)); | |
} | |
} | |
void Structure::dumpStatistics() | |
{ | |
#if DUMP_STRUCTURE_ID_STATISTICS | |
unsigned numberLeaf = 0; | |
unsigned numberUsingSingleSlot = 0; | |
unsigned numberSingletons = 0; | |
unsigned numberWithPropertyMaps = 0; | |
unsigned totalPropertyMapsSize = 0; | |
HashSet<Structure*>::const_iterator end = liveStructureSet.end(); | |
for (HashSet<Structure*>::const_iterator it = liveStructureSet.begin(); it != end; ++it) { | |
Structure* structure = *it; | |
if (structure->m_usingSingleTransitionSlot) { | |
if (!structure->m_transitions.singleTransition) | |
++numberLeaf; | |
else | |
++numberUsingSingleSlot; | |
if (!structure->m_previous && !structure->m_transitions.singleTransition) | |
++numberSingletons; | |
} | |
if (structure->m_propertyTable) { | |
++numberWithPropertyMaps; | |
totalPropertyMapsSize += PropertyMapHashTable::allocationSize(structure->m_propertyTable->size); | |
if (structure->m_propertyTable->deletedOffsets) | |
totalPropertyMapsSize += (structure->m_propertyTable->deletedOffsets->capacity() * sizeof(unsigned)); | |
} | |
} | |
printf("Number of live Structures: %d\n", liveStructureSet.size()); | |
printf("Number of Structures using the single item optimization for transition map: %d\n", numberUsingSingleSlot); | |
printf("Number of Structures that are leaf nodes: %d\n", numberLeaf); | |
printf("Number of Structures that singletons: %d\n", numberSingletons); | |
printf("Number of Structures with PropertyMaps: %d\n", numberWithPropertyMaps); | |
printf("Size of a single Structures: %d\n", static_cast<unsigned>(sizeof(Structure))); | |
printf("Size of sum of all property maps: %d\n", totalPropertyMapsSize); | |
printf("Size of average of all property maps: %f\n", static_cast<double>(totalPropertyMapsSize) / static_cast<double>(liveStructureSet.size())); | |
#else | |
printf("Dumping Structure statistics is not enabled.\n"); | |
#endif | |
} | |
Structure::Structure(JSValue prototype, const TypeInfo& typeInfo, unsigned anonymousSlotCount) | |
: m_typeInfo(typeInfo) | |
, m_prototype(prototype) | |
, m_specificValueInPrevious(0) | |
, m_propertyTable(0) | |
, m_propertyStorageCapacity(JSObject::inlineStorageCapacity) | |
, m_offset(noOffset) | |
, m_dictionaryKind(NoneDictionaryKind) | |
, m_isPinnedPropertyTable(false) | |
, m_hasGetterSetterProperties(false) | |
, m_attributesInPrevious(0) | |
, m_specificFunctionThrashCount(0) | |
, m_anonymousSlotCount(anonymousSlotCount) | |
, m_isUsingSingleSlot(true) | |
{ | |
m_transitions.m_singleTransition = 0; | |
ASSERT(m_prototype); | |
ASSERT(m_prototype.isObject() || m_prototype.isNull()); | |
#ifndef NDEBUG | |
#if ENABLE(JSC_MULTIPLE_THREADS) | |
MutexLocker protect(ignoreSetMutex); | |
#endif | |
if (shouldIgnoreLeaks) | |
ignoreSet.add(this); | |
else | |
structureCounter.increment(); | |
#endif | |
#if DUMP_STRUCTURE_ID_STATISTICS | |
liveStructureSet.add(this); | |
#endif | |
} | |
Structure::~Structure() | |
{ | |
if (m_previous) { | |
ASSERT(m_nameInPrevious); | |
m_previous->transitionTableRemove(make_pair(m_nameInPrevious.get(), m_attributesInPrevious), m_specificValueInPrevious); | |
} | |
ASSERT(!m_enumerationCache.hasDeadObject()); | |
if (m_propertyTable) { | |
unsigned entryCount = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; | |
for (unsigned i = 1; i <= entryCount; i++) { | |
if (UString::Rep* key = m_propertyTable->entries()[i].key) | |
key->deref(); | |
} | |
delete m_propertyTable->deletedOffsets; | |
fastFree(m_propertyTable); | |
} | |
if (!m_isUsingSingleSlot) | |
delete transitionTable(); | |
#ifndef NDEBUG | |
#if ENABLE(JSC_MULTIPLE_THREADS) | |
MutexLocker protect(ignoreSetMutex); | |
#endif | |
HashSet<Structure*>::iterator it = ignoreSet.find(this); | |
if (it != ignoreSet.end()) | |
ignoreSet.remove(it); | |
else | |
structureCounter.decrement(); | |
#endif | |
#if DUMP_STRUCTURE_ID_STATISTICS | |
liveStructureSet.remove(this); | |
#endif | |
} | |
void Structure::startIgnoringLeaks() | |
{ | |
#ifndef NDEBUG | |
shouldIgnoreLeaks = true; | |
#endif | |
} | |
void Structure::stopIgnoringLeaks() | |
{ | |
#ifndef NDEBUG | |
shouldIgnoreLeaks = false; | |
#endif | |
} | |
static bool isPowerOf2(unsigned v) | |
{ | |
// Taken from http://www.cs.utk.edu/~vose/c-stuff/bithacks.html | |
return !(v & (v - 1)) && v; | |
} | |
static 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; | |
} | |
static unsigned sizeForKeyCount(size_t keyCount) | |
{ | |
if (keyCount == notFound) | |
return newTableSize; | |
if (keyCount < 8) | |
return newTableSize; | |
if (isPowerOf2(keyCount)) | |
return keyCount * 4; | |
return nextPowerOf2(keyCount) * 2; | |
} | |
void Structure::materializePropertyMap() | |
{ | |
ASSERT(!m_propertyTable); | |
Vector<Structure*, 8> structures; | |
structures.append(this); | |
Structure* structure = this; | |
// Search for the last Structure with a property table. | |
while ((structure = structure->previousID())) { | |
if (structure->m_isPinnedPropertyTable) { | |
ASSERT(structure->m_propertyTable); | |
ASSERT(!structure->m_previous); | |
m_propertyTable = structure->copyPropertyTable(); | |
break; | |
} | |
structures.append(structure); | |
} | |
if (!m_propertyTable) | |
createPropertyMapHashTable(sizeForKeyCount(m_offset + 1)); | |
else { | |
if (sizeForKeyCount(m_offset + 1) > m_propertyTable->size) | |
rehashPropertyMapHashTable(sizeForKeyCount(m_offset + 1)); // This could be made more efficient by combining with the copy above. | |
} | |
for (ptrdiff_t i = structures.size() - 2; i >= 0; --i) { | |
structure = structures[i]; | |
structure->m_nameInPrevious->ref(); | |
PropertyMapEntry entry(structure->m_nameInPrevious.get(), m_anonymousSlotCount + structure->m_offset, structure->m_attributesInPrevious, structure->m_specificValueInPrevious, ++m_propertyTable->lastIndexUsed); | |
insertIntoPropertyMapHashTable(entry); | |
} | |
} | |
void Structure::growPropertyStorageCapacity() | |
{ | |
if (m_propertyStorageCapacity == JSObject::inlineStorageCapacity) | |
m_propertyStorageCapacity = JSObject::nonInlineBaseStorageCapacity; | |
else | |
m_propertyStorageCapacity *= 2; | |
} | |
void Structure::despecifyDictionaryFunction(const Identifier& propertyName) | |
{ | |
const UString::Rep* rep = propertyName._ustring.rep(); | |
materializePropertyMapIfNecessary(); | |
ASSERT(isDictionary()); | |
ASSERT(m_propertyTable); | |
unsigned i = rep->existingHash(); | |
#if DUMP_PROPERTYMAP_STATS | |
++numProbes; | |
#endif | |
unsigned entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
ASSERT(entryIndex != emptyEntryIndex); | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) { | |
m_propertyTable->entries()[entryIndex - 1].specificValue = 0; | |
return; | |
} | |
#if DUMP_PROPERTYMAP_STATS | |
++numCollisions; | |
#endif | |
unsigned k = 1 | doubleHash(rep->existingHash()); | |
while (1) { | |
i += k; | |
#if DUMP_PROPERTYMAP_STATS | |
++numRehashes; | |
#endif | |
entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
ASSERT(entryIndex != emptyEntryIndex); | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) { | |
m_propertyTable->entries()[entryIndex - 1].specificValue = 0; | |
return; | |
} | |
} | |
} | |
PassRefPtr<Structure> Structure::addPropertyTransitionToExistingStructure(Structure* structure, const Identifier& propertyName, unsigned attributes, JSCell* specificValue, size_t& offset) | |
{ | |
ASSERT(!structure->isDictionary()); | |
ASSERT(structure->typeInfo().type() == ObjectType); | |
if (Structure* existingTransition = structure->transitionTableGet(make_pair(propertyName.ustring().rep(), attributes), specificValue)) { | |
ASSERT(existingTransition->m_offset != noOffset); | |
offset = existingTransition->m_offset + existingTransition->m_anonymousSlotCount; | |
ASSERT(offset >= structure->m_anonymousSlotCount); | |
ASSERT(structure->m_anonymousSlotCount == existingTransition->m_anonymousSlotCount); | |
return existingTransition; | |
} | |
return 0; | |
} | |
PassRefPtr<Structure> Structure::addPropertyTransition(Structure* structure, const Identifier& propertyName, unsigned attributes, JSCell* specificValue, size_t& offset) | |
{ | |
ASSERT(!structure->isDictionary()); | |
ASSERT(structure->typeInfo().type() == ObjectType); | |
ASSERT(!Structure::addPropertyTransitionToExistingStructure(structure, propertyName, attributes, specificValue, offset)); | |
if (structure->m_specificFunctionThrashCount == maxSpecificFunctionThrashCount) | |
specificValue = 0; | |
if (structure->transitionCount() > s_maxTransitionLength) { | |
RefPtr<Structure> transition = toCacheableDictionaryTransition(structure); | |
ASSERT(structure != transition); | |
offset = transition->put(propertyName, attributes, specificValue); | |
ASSERT(offset >= structure->m_anonymousSlotCount); | |
ASSERT(structure->m_anonymousSlotCount == transition->m_anonymousSlotCount); | |
if (transition->propertyStorageSize() > transition->propertyStorageCapacity()) | |
transition->growPropertyStorageCapacity(); | |
return transition.release(); | |
} | |
RefPtr<Structure> transition = create(structure->m_prototype, structure->typeInfo(), structure->anonymousSlotCount()); | |
transition->m_cachedPrototypeChain = structure->m_cachedPrototypeChain; | |
transition->m_previous = structure; | |
transition->m_nameInPrevious = propertyName.ustring().rep(); | |
transition->m_attributesInPrevious = attributes; | |
transition->m_specificValueInPrevious = specificValue; | |
transition->m_propertyStorageCapacity = structure->m_propertyStorageCapacity; | |
transition->m_hasGetterSetterProperties = structure->m_hasGetterSetterProperties; | |
transition->m_hasNonEnumerableProperties = structure->m_hasNonEnumerableProperties; | |
transition->m_specificFunctionThrashCount = structure->m_specificFunctionThrashCount; | |
if (structure->m_propertyTable) { | |
if (structure->m_isPinnedPropertyTable) | |
transition->m_propertyTable = structure->copyPropertyTable(); | |
else { | |
transition->m_propertyTable = structure->m_propertyTable; | |
structure->m_propertyTable = 0; | |
} | |
} else { | |
if (structure->m_previous) | |
transition->materializePropertyMap(); | |
else | |
transition->createPropertyMapHashTable(); | |
} | |
offset = transition->put(propertyName, attributes, specificValue); | |
ASSERT(offset >= structure->m_anonymousSlotCount); | |
ASSERT(structure->m_anonymousSlotCount == transition->m_anonymousSlotCount); | |
if (transition->propertyStorageSize() > transition->propertyStorageCapacity()) | |
transition->growPropertyStorageCapacity(); | |
transition->m_offset = offset - structure->m_anonymousSlotCount; | |
ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); | |
structure->transitionTableAdd(make_pair(propertyName.ustring().rep(), attributes), transition.get(), specificValue); | |
return transition.release(); | |
} | |
PassRefPtr<Structure> Structure::removePropertyTransition(Structure* structure, const Identifier& propertyName, size_t& offset) | |
{ | |
ASSERT(!structure->isUncacheableDictionary()); | |
RefPtr<Structure> transition = toUncacheableDictionaryTransition(structure); | |
offset = transition->remove(propertyName); | |
ASSERT(offset >= structure->m_anonymousSlotCount); | |
ASSERT(structure->m_anonymousSlotCount == transition->m_anonymousSlotCount); | |
return transition.release(); | |
} | |
PassRefPtr<Structure> Structure::changePrototypeTransition(Structure* structure, JSValue prototype) | |
{ | |
RefPtr<Structure> transition = create(prototype, structure->typeInfo(), structure->anonymousSlotCount()); | |
transition->m_propertyStorageCapacity = structure->m_propertyStorageCapacity; | |
transition->m_hasGetterSetterProperties = structure->m_hasGetterSetterProperties; | |
transition->m_hasNonEnumerableProperties = structure->m_hasNonEnumerableProperties; | |
transition->m_specificFunctionThrashCount = structure->m_specificFunctionThrashCount; | |
// Don't set m_offset, as one can not transition to this. | |
structure->materializePropertyMapIfNecessary(); | |
transition->m_propertyTable = structure->copyPropertyTable(); | |
transition->m_isPinnedPropertyTable = true; | |
ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); | |
return transition.release(); | |
} | |
PassRefPtr<Structure> Structure::despecifyFunctionTransition(Structure* structure, const Identifier& replaceFunction) | |
{ | |
ASSERT(structure->m_specificFunctionThrashCount < maxSpecificFunctionThrashCount); | |
RefPtr<Structure> transition = create(structure->storedPrototype(), structure->typeInfo(), structure->anonymousSlotCount()); | |
transition->m_propertyStorageCapacity = structure->m_propertyStorageCapacity; | |
transition->m_hasGetterSetterProperties = structure->m_hasGetterSetterProperties; | |
transition->m_hasNonEnumerableProperties = structure->m_hasNonEnumerableProperties; | |
transition->m_specificFunctionThrashCount = structure->m_specificFunctionThrashCount + 1; | |
// Don't set m_offset, as one can not transition to this. | |
structure->materializePropertyMapIfNecessary(); | |
transition->m_propertyTable = structure->copyPropertyTable(); | |
transition->m_isPinnedPropertyTable = true; | |
if (transition->m_specificFunctionThrashCount == maxSpecificFunctionThrashCount) | |
transition->despecifyAllFunctions(); | |
else { | |
bool removed = transition->despecifyFunction(replaceFunction); | |
ASSERT_UNUSED(removed, removed); | |
} | |
ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); | |
return transition.release(); | |
} | |
PassRefPtr<Structure> Structure::getterSetterTransition(Structure* structure) | |
{ | |
RefPtr<Structure> transition = create(structure->storedPrototype(), structure->typeInfo(), structure->anonymousSlotCount()); | |
transition->m_propertyStorageCapacity = structure->m_propertyStorageCapacity; | |
transition->m_hasGetterSetterProperties = transition->m_hasGetterSetterProperties; | |
transition->m_hasNonEnumerableProperties = structure->m_hasNonEnumerableProperties; | |
transition->m_specificFunctionThrashCount = structure->m_specificFunctionThrashCount; | |
// Don't set m_offset, as one can not transition to this. | |
structure->materializePropertyMapIfNecessary(); | |
transition->m_propertyTable = structure->copyPropertyTable(); | |
transition->m_isPinnedPropertyTable = true; | |
ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); | |
return transition.release(); | |
} | |
PassRefPtr<Structure> Structure::toDictionaryTransition(Structure* structure, DictionaryKind kind) | |
{ | |
ASSERT(!structure->isUncacheableDictionary()); | |
RefPtr<Structure> transition = create(structure->m_prototype, structure->typeInfo(), structure->anonymousSlotCount()); | |
transition->m_dictionaryKind = kind; | |
transition->m_propertyStorageCapacity = structure->m_propertyStorageCapacity; | |
transition->m_hasGetterSetterProperties = structure->m_hasGetterSetterProperties; | |
transition->m_hasNonEnumerableProperties = structure->m_hasNonEnumerableProperties; | |
transition->m_specificFunctionThrashCount = structure->m_specificFunctionThrashCount; | |
structure->materializePropertyMapIfNecessary(); | |
transition->m_propertyTable = structure->copyPropertyTable(); | |
transition->m_isPinnedPropertyTable = true; | |
ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); | |
return transition.release(); | |
} | |
PassRefPtr<Structure> Structure::toCacheableDictionaryTransition(Structure* structure) | |
{ | |
return toDictionaryTransition(structure, CachedDictionaryKind); | |
} | |
PassRefPtr<Structure> Structure::toUncacheableDictionaryTransition(Structure* structure) | |
{ | |
return toDictionaryTransition(structure, UncachedDictionaryKind); | |
} | |
PassRefPtr<Structure> Structure::flattenDictionaryStructure(JSObject* object) | |
{ | |
ASSERT(isDictionary()); | |
if (isUncacheableDictionary()) { | |
ASSERT(m_propertyTable); | |
Vector<PropertyMapEntry*> sortedPropertyEntries(m_propertyTable->keyCount); | |
PropertyMapEntry** p = sortedPropertyEntries.data(); | |
unsigned entryCount = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; | |
for (unsigned i = 1; i <= entryCount; i++) { | |
if (m_propertyTable->entries()[i].key) | |
*p++ = &m_propertyTable->entries()[i]; | |
} | |
size_t propertyCount = p - sortedPropertyEntries.data(); | |
qsort(sortedPropertyEntries.data(), propertyCount, sizeof(PropertyMapEntry*), comparePropertyMapEntryIndices); | |
sortedPropertyEntries.resize(propertyCount); | |
// We now have the properties currently defined on this object | |
// in the order that they are expected to be in, but we need to | |
// reorder the storage, so we have to copy the current values out | |
Vector<JSValue> values(propertyCount); | |
unsigned anonymousSlotCount = m_anonymousSlotCount; | |
for (unsigned i = 0; i < propertyCount; i++) { | |
PropertyMapEntry* entry = sortedPropertyEntries[i]; | |
values[i] = object->getDirectOffset(entry->offset); | |
// Update property table to have the new property offsets | |
entry->offset = anonymousSlotCount + i; | |
entry->index = i; | |
} | |
// Copy the original property values into their final locations | |
for (unsigned i = 0; i < propertyCount; i++) | |
object->putDirectOffset(anonymousSlotCount + i, values[i]); | |
if (m_propertyTable->deletedOffsets) { | |
delete m_propertyTable->deletedOffsets; | |
m_propertyTable->deletedOffsets = 0; | |
} | |
} | |
m_dictionaryKind = NoneDictionaryKind; | |
return this; | |
} | |
size_t Structure::addPropertyWithoutTransition(const Identifier& propertyName, unsigned attributes, JSCell* specificValue) | |
{ | |
ASSERT(!m_enumerationCache); | |
if (m_specificFunctionThrashCount == maxSpecificFunctionThrashCount) | |
specificValue = 0; | |
materializePropertyMapIfNecessary(); | |
m_isPinnedPropertyTable = true; | |
size_t offset = put(propertyName, attributes, specificValue); | |
ASSERT(offset >= m_anonymousSlotCount); | |
if (propertyStorageSize() > propertyStorageCapacity()) | |
growPropertyStorageCapacity(); | |
return offset; | |
} | |
size_t Structure::removePropertyWithoutTransition(const Identifier& propertyName) | |
{ | |
ASSERT(isUncacheableDictionary()); | |
ASSERT(!m_enumerationCache); | |
materializePropertyMapIfNecessary(); | |
m_isPinnedPropertyTable = true; | |
size_t offset = remove(propertyName); | |
ASSERT(offset >= m_anonymousSlotCount); | |
return offset; | |
} | |
#if DUMP_PROPERTYMAP_STATS | |
static int numProbes; | |
static int numCollisions; | |
static int numRehashes; | |
static int numRemoves; | |
struct PropertyMapStatisticsExitLogger { | |
~PropertyMapStatisticsExitLogger(); | |
}; | |
static PropertyMapStatisticsExitLogger logger; | |
PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger() | |
{ | |
printf("\nJSC::PropertyMap statistics\n\n"); | |
printf("%d probes\n", numProbes); | |
printf("%d collisions (%.1f%%)\n", numCollisions, 100.0 * numCollisions / numProbes); | |
printf("%d rehashes\n", numRehashes); | |
printf("%d removes\n", numRemoves); | |
} | |
#endif | |
static const unsigned deletedSentinelIndex = 1; | |
#if !DO_PROPERTYMAP_CONSTENCY_CHECK | |
inline void Structure::checkConsistency() | |
{ | |
} | |
#endif | |
PropertyMapHashTable* Structure::copyPropertyTable() | |
{ | |
if (!m_propertyTable) | |
return 0; | |
size_t tableSize = PropertyMapHashTable::allocationSize(m_propertyTable->size); | |
PropertyMapHashTable* newTable = static_cast<PropertyMapHashTable*>(fastMalloc(tableSize)); | |
memcpy(newTable, m_propertyTable, tableSize); | |
unsigned entryCount = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; | |
for (unsigned i = 1; i <= entryCount; ++i) { | |
if (UString::Rep* key = newTable->entries()[i].key) | |
key->ref(); | |
} | |
// Copy the deletedOffsets vector. | |
if (m_propertyTable->deletedOffsets) | |
newTable->deletedOffsets = new Vector<unsigned>(*m_propertyTable->deletedOffsets); | |
return newTable; | |
} | |
size_t Structure::get(const UString::Rep* rep, unsigned& attributes, JSCell*& specificValue) | |
{ | |
materializePropertyMapIfNecessary(); | |
if (!m_propertyTable) | |
return notFound; | |
unsigned i = rep->existingHash(); | |
#if DUMP_PROPERTYMAP_STATS | |
++numProbes; | |
#endif | |
unsigned entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
return notFound; | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) { | |
attributes = m_propertyTable->entries()[entryIndex - 1].attributes; | |
specificValue = m_propertyTable->entries()[entryIndex - 1].specificValue; | |
ASSERT(m_propertyTable->entries()[entryIndex - 1].offset >= m_anonymousSlotCount); | |
return m_propertyTable->entries()[entryIndex - 1].offset; | |
} | |
#if DUMP_PROPERTYMAP_STATS | |
++numCollisions; | |
#endif | |
unsigned k = 1 | doubleHash(rep->existingHash()); | |
while (1) { | |
i += k; | |
#if DUMP_PROPERTYMAP_STATS | |
++numRehashes; | |
#endif | |
entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
return notFound; | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) { | |
attributes = m_propertyTable->entries()[entryIndex - 1].attributes; | |
specificValue = m_propertyTable->entries()[entryIndex - 1].specificValue; | |
ASSERT(m_propertyTable->entries()[entryIndex - 1].offset >= m_anonymousSlotCount); | |
return m_propertyTable->entries()[entryIndex - 1].offset; | |
} | |
} | |
} | |
bool Structure::despecifyFunction(const Identifier& propertyName) | |
{ | |
ASSERT(!propertyName.isNull()); | |
materializePropertyMapIfNecessary(); | |
if (!m_propertyTable) | |
return false; | |
UString::Rep* rep = propertyName._ustring.rep(); | |
unsigned i = rep->existingHash(); | |
#if DUMP_PROPERTYMAP_STATS | |
++numProbes; | |
#endif | |
unsigned entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
return false; | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) { | |
ASSERT(m_propertyTable->entries()[entryIndex - 1].specificValue); | |
m_propertyTable->entries()[entryIndex - 1].specificValue = 0; | |
return true; | |
} | |
#if DUMP_PROPERTYMAP_STATS | |
++numCollisions; | |
#endif | |
unsigned k = 1 | doubleHash(rep->existingHash()); | |
while (1) { | |
i += k; | |
#if DUMP_PROPERTYMAP_STATS | |
++numRehashes; | |
#endif | |
entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
return false; | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) { | |
ASSERT(m_propertyTable->entries()[entryIndex - 1].specificValue); | |
m_propertyTable->entries()[entryIndex - 1].specificValue = 0; | |
return true; | |
} | |
} | |
} | |
void Structure::despecifyAllFunctions() | |
{ | |
materializePropertyMapIfNecessary(); | |
if (!m_propertyTable) | |
return; | |
unsigned entryCount = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; | |
for (unsigned i = 1; i <= entryCount; ++i) | |
m_propertyTable->entries()[i].specificValue = 0; | |
} | |
size_t Structure::put(const Identifier& propertyName, unsigned attributes, JSCell* specificValue) | |
{ | |
ASSERT(!propertyName.isNull()); | |
ASSERT(get(propertyName) == notFound); | |
checkConsistency(); | |
if (attributes & DontEnum) | |
m_hasNonEnumerableProperties = true; | |
UString::Rep* rep = propertyName._ustring.rep(); | |
if (!m_propertyTable) | |
createPropertyMapHashTable(); | |
// FIXME: Consider a fast case for tables with no deleted sentinels. | |
unsigned i = rep->existingHash(); | |
unsigned k = 0; | |
bool foundDeletedElement = false; | |
unsigned deletedElementIndex = 0; // initialize to make the compiler happy | |
#if DUMP_PROPERTYMAP_STATS | |
++numProbes; | |
#endif | |
while (1) { | |
unsigned entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
break; | |
if (entryIndex == deletedSentinelIndex) { | |
// If we find a deleted-element sentinel, remember it for use later. | |
if (!foundDeletedElement) { | |
foundDeletedElement = true; | |
deletedElementIndex = i; | |
} | |
} | |
if (k == 0) { | |
k = 1 | doubleHash(rep->existingHash()); | |
#if DUMP_PROPERTYMAP_STATS | |
++numCollisions; | |
#endif | |
} | |
i += k; | |
#if DUMP_PROPERTYMAP_STATS | |
++numRehashes; | |
#endif | |
} | |
// Figure out which entry to use. | |
unsigned entryIndex = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount + 2; | |
if (foundDeletedElement) { | |
i = deletedElementIndex; | |
--m_propertyTable->deletedSentinelCount; | |
// Since we're not making the table bigger, we can't use the entry one past | |
// the end that we were planning on using, so search backwards for the empty | |
// slot that we can use. We know it will be there because we did at least one | |
// deletion in the past that left an entry empty. | |
while (m_propertyTable->entries()[--entryIndex - 1].key) { } | |
} | |
// Create a new hash table entry. | |
m_propertyTable->entryIndices[i & m_propertyTable->sizeMask] = entryIndex; | |
// Create a new hash table entry. | |
rep->ref(); | |
m_propertyTable->entries()[entryIndex - 1].key = rep; | |
m_propertyTable->entries()[entryIndex - 1].attributes = attributes; | |
m_propertyTable->entries()[entryIndex - 1].specificValue = specificValue; | |
m_propertyTable->entries()[entryIndex - 1].index = ++m_propertyTable->lastIndexUsed; | |
unsigned newOffset; | |
if (m_propertyTable->deletedOffsets && !m_propertyTable->deletedOffsets->isEmpty()) { | |
newOffset = m_propertyTable->deletedOffsets->last(); | |
m_propertyTable->deletedOffsets->removeLast(); | |
} else | |
newOffset = m_propertyTable->keyCount + m_anonymousSlotCount; | |
m_propertyTable->entries()[entryIndex - 1].offset = newOffset; | |
ASSERT(newOffset >= m_anonymousSlotCount); | |
++m_propertyTable->keyCount; | |
if ((m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount) * 2 >= m_propertyTable->size) | |
expandPropertyMapHashTable(); | |
checkConsistency(); | |
return newOffset; | |
} | |
bool Structure::hasTransition(UString::Rep* rep, unsigned attributes) | |
{ | |
return transitionTableHasTransition(make_pair(rep, attributes)); | |
} | |
size_t Structure::remove(const Identifier& propertyName) | |
{ | |
ASSERT(!propertyName.isNull()); | |
checkConsistency(); | |
UString::Rep* rep = propertyName._ustring.rep(); | |
if (!m_propertyTable) | |
return notFound; | |
#if DUMP_PROPERTYMAP_STATS | |
++numProbes; | |
++numRemoves; | |
#endif | |
// Find the thing to remove. | |
unsigned i = rep->existingHash(); | |
unsigned k = 0; | |
unsigned entryIndex; | |
UString::Rep* key = 0; | |
while (1) { | |
entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
return notFound; | |
key = m_propertyTable->entries()[entryIndex - 1].key; | |
if (rep == key) | |
break; | |
if (k == 0) { | |
k = 1 | doubleHash(rep->existingHash()); | |
#if DUMP_PROPERTYMAP_STATS | |
++numCollisions; | |
#endif | |
} | |
i += k; | |
#if DUMP_PROPERTYMAP_STATS | |
++numRehashes; | |
#endif | |
} | |
// Replace this one element with the deleted sentinel. Also clear out | |
// the entry so we can iterate all the entries as needed. | |
m_propertyTable->entryIndices[i & m_propertyTable->sizeMask] = deletedSentinelIndex; | |
size_t offset = m_propertyTable->entries()[entryIndex - 1].offset; | |
ASSERT(offset >= m_anonymousSlotCount); | |
key->deref(); | |
m_propertyTable->entries()[entryIndex - 1].key = 0; | |
m_propertyTable->entries()[entryIndex - 1].attributes = 0; | |
m_propertyTable->entries()[entryIndex - 1].specificValue = 0; | |
m_propertyTable->entries()[entryIndex - 1].offset = 0; | |
if (!m_propertyTable->deletedOffsets) | |
m_propertyTable->deletedOffsets = new Vector<unsigned>; | |
m_propertyTable->deletedOffsets->append(offset); | |
ASSERT(m_propertyTable->keyCount >= 1); | |
--m_propertyTable->keyCount; | |
++m_propertyTable->deletedSentinelCount; | |
if (m_propertyTable->deletedSentinelCount * 4 >= m_propertyTable->size) | |
rehashPropertyMapHashTable(); | |
checkConsistency(); | |
return offset; | |
} | |
void Structure::insertIntoPropertyMapHashTable(const PropertyMapEntry& entry) | |
{ | |
ASSERT(m_propertyTable); | |
ASSERT(entry.offset >= m_anonymousSlotCount); | |
unsigned i = entry.key->existingHash(); | |
unsigned k = 0; | |
#if DUMP_PROPERTYMAP_STATS | |
++numProbes; | |
#endif | |
while (1) { | |
unsigned entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
if (entryIndex == emptyEntryIndex) | |
break; | |
if (k == 0) { | |
k = 1 | doubleHash(entry.key->existingHash()); | |
#if DUMP_PROPERTYMAP_STATS | |
++numCollisions; | |
#endif | |
} | |
i += k; | |
#if DUMP_PROPERTYMAP_STATS | |
++numRehashes; | |
#endif | |
} | |
unsigned entryIndex = m_propertyTable->keyCount + 2; | |
m_propertyTable->entryIndices[i & m_propertyTable->sizeMask] = entryIndex; | |
m_propertyTable->entries()[entryIndex - 1] = entry; | |
++m_propertyTable->keyCount; | |
} | |
void Structure::createPropertyMapHashTable() | |
{ | |
ASSERT(sizeForKeyCount(7) == newTableSize); | |
createPropertyMapHashTable(newTableSize); | |
} | |
void Structure::createPropertyMapHashTable(unsigned newTableSize) | |
{ | |
ASSERT(!m_propertyTable); | |
ASSERT(isPowerOf2(newTableSize)); | |
checkConsistency(); | |
m_propertyTable = static_cast<PropertyMapHashTable*>(fastZeroedMalloc(PropertyMapHashTable::allocationSize(newTableSize))); | |
m_propertyTable->size = newTableSize; | |
m_propertyTable->sizeMask = newTableSize - 1; | |
checkConsistency(); | |
} | |
void Structure::expandPropertyMapHashTable() | |
{ | |
ASSERT(m_propertyTable); | |
rehashPropertyMapHashTable(m_propertyTable->size * 2); | |
} | |
void Structure::rehashPropertyMapHashTable() | |
{ | |
ASSERT(m_propertyTable); | |
ASSERT(m_propertyTable->size); | |
rehashPropertyMapHashTable(m_propertyTable->size); | |
} | |
void Structure::rehashPropertyMapHashTable(unsigned newTableSize) | |
{ | |
ASSERT(m_propertyTable); | |
ASSERT(isPowerOf2(newTableSize)); | |
checkConsistency(); | |
PropertyMapHashTable* oldTable = m_propertyTable; | |
m_propertyTable = static_cast<PropertyMapHashTable*>(fastZeroedMalloc(PropertyMapHashTable::allocationSize(newTableSize))); | |
m_propertyTable->size = newTableSize; | |
m_propertyTable->sizeMask = newTableSize - 1; | |
unsigned lastIndexUsed = 0; | |
unsigned entryCount = oldTable->keyCount + oldTable->deletedSentinelCount; | |
for (unsigned i = 1; i <= entryCount; ++i) { | |
if (oldTable->entries()[i].key) { | |
lastIndexUsed = max(oldTable->entries()[i].index, lastIndexUsed); | |
insertIntoPropertyMapHashTable(oldTable->entries()[i]); | |
} | |
} | |
m_propertyTable->lastIndexUsed = lastIndexUsed; | |
m_propertyTable->deletedOffsets = oldTable->deletedOffsets; | |
fastFree(oldTable); | |
checkConsistency(); | |
} | |
int comparePropertyMapEntryIndices(const void* a, const void* b) | |
{ | |
unsigned ia = static_cast<PropertyMapEntry* const*>(a)[0]->index; | |
unsigned ib = static_cast<PropertyMapEntry* const*>(b)[0]->index; | |
if (ia < ib) | |
return -1; | |
if (ia > ib) | |
return +1; | |
return 0; | |
} | |
void Structure::getPropertyNames(PropertyNameArray& propertyNames, EnumerationMode mode) | |
{ | |
materializePropertyMapIfNecessary(); | |
if (!m_propertyTable) | |
return; | |
if (m_propertyTable->keyCount < tinyMapThreshold) { | |
PropertyMapEntry* a[tinyMapThreshold]; | |
int i = 0; | |
unsigned entryCount = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; | |
for (unsigned k = 1; k <= entryCount; k++) { | |
ASSERT(m_hasNonEnumerableProperties || !(m_propertyTable->entries()[k].attributes & DontEnum)); | |
if (m_propertyTable->entries()[k].key && (!(m_propertyTable->entries()[k].attributes & DontEnum) || (mode == IncludeDontEnumProperties))) { | |
PropertyMapEntry* value = &m_propertyTable->entries()[k]; | |
int j; | |
for (j = i - 1; j >= 0 && a[j]->index > value->index; --j) | |
a[j + 1] = a[j]; | |
a[j + 1] = value; | |
++i; | |
} | |
} | |
if (!propertyNames.size()) { | |
for (int k = 0; k < i; ++k) | |
propertyNames.addKnownUnique(a[k]->key); | |
} else { | |
for (int k = 0; k < i; ++k) | |
propertyNames.add(a[k]->key); | |
} | |
return; | |
} | |
// Allocate a buffer to use to sort the keys. | |
Vector<PropertyMapEntry*, smallMapThreshold> sortedEnumerables(m_propertyTable->keyCount); | |
// Get pointers to the enumerable entries in the buffer. | |
PropertyMapEntry** p = sortedEnumerables.data(); | |
unsigned entryCount = m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; | |
for (unsigned i = 1; i <= entryCount; i++) { | |
if (m_propertyTable->entries()[i].key && (!(m_propertyTable->entries()[i].attributes & DontEnum) || (mode == IncludeDontEnumProperties))) | |
*p++ = &m_propertyTable->entries()[i]; | |
} | |
size_t enumerableCount = p - sortedEnumerables.data(); | |
// Sort the entries by index. | |
qsort(sortedEnumerables.data(), enumerableCount, sizeof(PropertyMapEntry*), comparePropertyMapEntryIndices); | |
sortedEnumerables.resize(enumerableCount); | |
// Put the keys of the sorted entries into the list. | |
if (!propertyNames.size()) { | |
for (size_t i = 0; i < sortedEnumerables.size(); ++i) | |
propertyNames.addKnownUnique(sortedEnumerables[i]->key); | |
} else { | |
for (size_t i = 0; i < sortedEnumerables.size(); ++i) | |
propertyNames.add(sortedEnumerables[i]->key); | |
} | |
} | |
#if DO_PROPERTYMAP_CONSTENCY_CHECK | |
void Structure::checkConsistency() | |
{ | |
if (!m_propertyTable) | |
return; | |
ASSERT(m_propertyTable->size >= newTableSize); | |
ASSERT(m_propertyTable->sizeMask); | |
ASSERT(m_propertyTable->size == m_propertyTable->sizeMask + 1); | |
ASSERT(!(m_propertyTable->size & m_propertyTable->sizeMask)); | |
ASSERT(m_propertyTable->keyCount <= m_propertyTable->size / 2); | |
ASSERT(m_propertyTable->deletedSentinelCount <= m_propertyTable->size / 4); | |
ASSERT(m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount <= m_propertyTable->size / 2); | |
unsigned indexCount = 0; | |
unsigned deletedIndexCount = 0; | |
for (unsigned a = 0; a != m_propertyTable->size; ++a) { | |
unsigned entryIndex = m_propertyTable->entryIndices[a]; | |
if (entryIndex == emptyEntryIndex) | |
continue; | |
if (entryIndex == deletedSentinelIndex) { | |
++deletedIndexCount; | |
continue; | |
} | |
ASSERT(entryIndex > deletedSentinelIndex); | |
ASSERT(entryIndex - 1 <= m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount); | |
++indexCount; | |
for (unsigned b = a + 1; b != m_propertyTable->size; ++b) | |
ASSERT(m_propertyTable->entryIndices[b] != entryIndex); | |
} | |
ASSERT(indexCount == m_propertyTable->keyCount); | |
ASSERT(deletedIndexCount == m_propertyTable->deletedSentinelCount); | |
ASSERT(m_propertyTable->entries()[0].key == 0); | |
unsigned nonEmptyEntryCount = 0; | |
for (unsigned c = 1; c <= m_propertyTable->keyCount + m_propertyTable->deletedSentinelCount; ++c) { | |
ASSERT(m_hasNonEnumerableProperties || !(m_propertyTable->entries()[c].attributes & DontEnum)); | |
UString::Rep* rep = m_propertyTable->entries()[c].key; | |
ASSERT(m_propertyTable->entries()[c].offset >= m_anonymousSlotCount); | |
if (!rep) | |
continue; | |
++nonEmptyEntryCount; | |
unsigned i = rep->existingHash(); | |
unsigned k = 0; | |
unsigned entryIndex; | |
while (1) { | |
entryIndex = m_propertyTable->entryIndices[i & m_propertyTable->sizeMask]; | |
ASSERT(entryIndex != emptyEntryIndex); | |
if (rep == m_propertyTable->entries()[entryIndex - 1].key) | |
break; | |
if (k == 0) | |
k = 1 | doubleHash(rep->existingHash()); | |
i += k; | |
} | |
ASSERT(entryIndex == c + 1); | |
} | |
ASSERT(nonEmptyEntryCount == m_propertyTable->keyCount); | |
} | |
#endif // DO_PROPERTYMAP_CONSTENCY_CHECK | |
} // namespace JSC |