blob: 6d13f4b98af3d42ae06c24e28633c066085e6427 [file] [log] [blame]
/*
* 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