property_map.cpp   [plain text]


/*
 *  Copyright (C) 2004, 2005, 2006, 2007, 2008 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.
 *
 */

#include "config.h"
#include "property_map.h"

#include "object.h"
#include "protect.h"
#include "PropertyNameArray.h"
#include <algorithm>
#include <wtf/Assertions.h>
#include <wtf/FastMalloc.h>
#include <wtf/HashTable.h>
#include <wtf/Vector.h>

using std::max;
using WTF::doubleHash;

#ifndef NDEBUG
#define DO_PROPERTYMAP_CONSTENCY_CHECK 0
#define DUMP_PROPERTYMAP_STATS 0
#else
#define DO_PROPERTYMAP_CONSTENCY_CHECK 0
#define DUMP_PROPERTYMAP_STATS 0
#endif

#define USE_SINGLE_ENTRY 1

// 2/28/2006 ggaren: command-line JS iBench says that USE_SINGLE_ENTRY is a
// 3.2% performance boost.

namespace KJS {

// 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;

#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("\nKJS::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

struct PropertyMapEntry {
    UString::Rep* key;
    JSValue* value;
    unsigned attributes;
    unsigned index;

    PropertyMapEntry(UString::Rep* k, JSValue* v, int a)
        : key(k), value(v), attributes(a), index(0)
    {
    }
};

// lastIndexUsed is an ever-increasing index used to identify the order items
// were inserted into the property map. It's required that getEnumerablePropertyNames
// return the properties in the order they were added for compatibility with other
// browsers' JavaScript implementations.
struct PropertyMapHashTable {
    unsigned sizeMask;
    unsigned size;
    unsigned keyCount;
    unsigned deletedSentinelCount;
    unsigned lastIndexUsed;
    unsigned entryIndicies[1];

    PropertyMapEntry* entries()
    {
        // The entries vector comes after the indices vector.
        // The 0th item in the entries vector is not really used; it has to
        // have a 0 in its key to allow the hash table lookup to handle deleted
        // sentinels without any special-case code, but the other fields are unused.
        return reinterpret_cast<PropertyMapEntry*>(&entryIndicies[size]);
    }

    static size_t allocationSize(unsigned size)
    {
        // We never let a hash table get more than half full,
        // So the number of indices we need is the size of the hash table.
        // But the number of entries is half that (plus one for the deleted sentinel).
        return sizeof(PropertyMapHashTable)
            + (size - 1) * sizeof(unsigned)
            + (1 + size / 2) * sizeof(PropertyMapEntry);
    }
};

static const unsigned emptyEntryIndex = 0;
static const unsigned deletedSentinelIndex = 1;

SavedProperties::SavedProperties()
    : count(0)
{
}

SavedProperties::~SavedProperties()
{
}

#if !DO_PROPERTYMAP_CONSTENCY_CHECK

inline void PropertyMap::checkConsistency()
{
}

#endif

PropertyMap::~PropertyMap()
{
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (m_singleEntryKey)
            m_singleEntryKey->deref();
#endif
        return;
    }
    
    unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
    for (unsigned i = 1; i <= entryCount; i++) {
        if (UString::Rep* key = m_u.table->entries()[i].key)
            key->deref();
    }
    fastFree(m_u.table);
}

void PropertyMap::clear()
{
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (m_singleEntryKey) {
            m_singleEntryKey->deref();
            m_singleEntryKey = 0;
        }
#endif
        return;
    }

    unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
    for (unsigned i = 1; i <= entryCount; i++) {
        if (UString::Rep* key = m_u.table->entries()[i].key)
            key->deref();
    }
    for (unsigned i = 0; i < m_u.table->size; i++)
        m_u.table->entryIndicies[i] = emptyEntryIndex;
    m_u.table->keyCount = 0;
    m_u.table->deletedSentinelCount = 0;
}

JSValue* PropertyMap::get(const Identifier& name, unsigned& attributes) const
{
    ASSERT(!name.isNull());
    
    UString::Rep* rep = name._ustring.rep();
    
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (rep == m_singleEntryKey) {
            attributes = m_singleEntryAttributes;
            return m_u.singleEntryValue;
        }
#endif
        return 0;
    }
    
    unsigned i = rep->computedHash();

#if DUMP_PROPERTYMAP_STATS
    ++numProbes;
#endif

    unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
    if (entryIndex == emptyEntryIndex)
        return 0;

    if (rep == m_u.table->entries()[entryIndex - 1].key) {
        attributes = m_u.table->entries()[entryIndex - 1].attributes;
        return m_u.table->entries()[entryIndex - 1].value;
    }

#if DUMP_PROPERTYMAP_STATS
    ++numCollisions;
#endif

    unsigned k = 1 | doubleHash(rep->computedHash());

    while (1) {
        i += k;

#if DUMP_PROPERTYMAP_STATS
        ++numRehashes;
#endif

        entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
        if (entryIndex == emptyEntryIndex)
            return 0;

        if (rep == m_u.table->entries()[entryIndex - 1].key) {
            attributes = m_u.table->entries()[entryIndex - 1].attributes;
            return m_u.table->entries()[entryIndex - 1].value;
        }
    }
}

JSValue* PropertyMap::get(const Identifier& name) const
{
    ASSERT(!name.isNull());
    
    UString::Rep* rep = name._ustring.rep();
    
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (rep == m_singleEntryKey)
            return m_u.singleEntryValue;
#endif
        return 0;
    }
    
    unsigned i = rep->computedHash();

#if DUMP_PROPERTYMAP_STATS
    ++numProbes;
#endif

    unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
    if (entryIndex == emptyEntryIndex)
        return 0;

    if (rep == m_u.table->entries()[entryIndex - 1].key)
        return m_u.table->entries()[entryIndex - 1].value;

#if DUMP_PROPERTYMAP_STATS
    ++numCollisions;
#endif

    unsigned k = 1 | doubleHash(rep->computedHash());

    while (1) {
        i += k;

#if DUMP_PROPERTYMAP_STATS
        ++numRehashes;
#endif

        entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
        if (entryIndex == emptyEntryIndex)
            return 0;

        if (rep == m_u.table->entries()[entryIndex - 1].key)
            return m_u.table->entries()[entryIndex - 1].value;
    }
}

JSValue** PropertyMap::getLocation(const Identifier& name)
{
    ASSERT(!name.isNull());
    
    UString::Rep* rep = name._ustring.rep();
    
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (rep == m_singleEntryKey)
            return &m_u.singleEntryValue;
#endif
        return 0;
    }
    
    unsigned i = rep->computedHash();

#if DUMP_PROPERTYMAP_STATS
    ++numProbes;
#endif

    unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
    if (entryIndex == emptyEntryIndex)
        return 0;

    if (rep == m_u.table->entries()[entryIndex - 1].key)
        return &m_u.table->entries()[entryIndex - 1].value;

#if DUMP_PROPERTYMAP_STATS
    ++numCollisions;
#endif

    unsigned k = 1 | doubleHash(rep->computedHash());

    while (1) {
        i += k;

#if DUMP_PROPERTYMAP_STATS
        ++numRehashes;
#endif

        entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
        if (entryIndex == emptyEntryIndex)
            return 0;

        if (rep == m_u.table->entries()[entryIndex - 1].key)
            return &m_u.table->entries()[entryIndex - 1].value;
    }
}

void PropertyMap::put(const Identifier& name, JSValue* value, unsigned attributes, bool checkReadOnly)
{
    ASSERT(!name.isNull());
    ASSERT(value);
    
    checkConsistency();

    UString::Rep* rep = name._ustring.rep();
    
#if USE_SINGLE_ENTRY
    if (!m_usingTable) {
        if (!m_singleEntryKey) {
            rep->ref();
            m_singleEntryKey = rep;
            m_u.singleEntryValue = value;
            m_singleEntryAttributes = static_cast<short>(attributes);
            checkConsistency();
            return;
        }
        if (rep == m_singleEntryKey && !(checkReadOnly && (m_singleEntryAttributes & ReadOnly))) {
            m_u.singleEntryValue = value;
            return;
        }
    }
#endif

    if (!m_usingTable || (m_u.table->keyCount + m_u.table->deletedSentinelCount) * 2 >= m_u.table->size)
        expand();

    // FIXME: Consider a fast case for tables with no deleted sentinels.

    unsigned i = rep->computedHash();
    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_u.table->entryIndicies[i & m_u.table->sizeMask];
        if (entryIndex == emptyEntryIndex)
            break;

        if (m_u.table->entries()[entryIndex - 1].key == rep) {
            if (checkReadOnly && (m_u.table->entries()[entryIndex - 1].attributes & ReadOnly)) 
                return;
            // Put a new value in an existing hash table entry.
            m_u.table->entries()[entryIndex - 1].value = value;
            // Attributes are intentionally not updated.
            return;
        } else 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->computedHash());
#if DUMP_PROPERTYMAP_STATS
            ++numCollisions;
#endif
        }

        i += k;

#if DUMP_PROPERTYMAP_STATS
        ++numRehashes;
#endif
    }

    // Figure out which entry to use.
    unsigned entryIndex = m_u.table->keyCount + m_u.table->deletedSentinelCount + 2;
    if (foundDeletedElement) {
        i = deletedElementIndex;
        --m_u.table->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_u.table->entries()[--entryIndex - 1].key)
            ;
    }


    // Create a new hash table entry.
    m_u.table->entryIndicies[i & m_u.table->sizeMask] = entryIndex;

    // Create a new hash table entry.
    rep->ref();
    m_u.table->entries()[entryIndex - 1].key = rep;
    m_u.table->entries()[entryIndex - 1].value = value;
    m_u.table->entries()[entryIndex - 1].attributes = attributes;
    m_u.table->entries()[entryIndex - 1].index = ++m_u.table->lastIndexUsed;
    ++m_u.table->keyCount;

    checkConsistency();
}

void PropertyMap::insert(const Entry& entry)
{
    ASSERT(m_u.table);

    unsigned i = entry.key->computedHash();
    unsigned k = 0;

#if DUMP_PROPERTYMAP_STATS
    ++numProbes;
#endif

    while (1) {
        unsigned entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
        if (entryIndex == emptyEntryIndex)
            break;

        if (k == 0) {
            k = 1 | doubleHash(entry.key->computedHash());
#if DUMP_PROPERTYMAP_STATS
            ++numCollisions;
#endif
        }

        i += k;

#if DUMP_PROPERTYMAP_STATS
        ++numRehashes;
#endif
    }

    unsigned entryIndex = m_u.table->keyCount + 2;
    m_u.table->entryIndicies[i & m_u.table->sizeMask] = entryIndex;
    m_u.table->entries()[entryIndex - 1] = entry;
    ++m_u.table->keyCount;
}

void PropertyMap::expand()
{
    if (!m_usingTable)
        createTable();
    else
        rehash(m_u.table->size * 2);
}

void PropertyMap::rehash()
{
    ASSERT(m_usingTable);
    ASSERT(m_u.table);
    ASSERT(m_u.table->size);
    rehash(m_u.table->size);
}

void PropertyMap::createTable()
{
    const unsigned newTableSize = 16;

    ASSERT(!m_usingTable);

    checkConsistency();

#if USE_SINGLE_ENTRY
    JSValue* oldSingleEntryValue = m_u.singleEntryValue;
#endif

    m_u.table = static_cast<Table*>(fastZeroedMalloc(Table::allocationSize(newTableSize)));
    m_u.table->size = newTableSize;
    m_u.table->sizeMask = newTableSize - 1;
    m_usingTable = true;

#if USE_SINGLE_ENTRY
    if (m_singleEntryKey) {
        insert(Entry(m_singleEntryKey, oldSingleEntryValue, m_singleEntryAttributes));
        m_singleEntryKey = 0;
    }
#endif

    checkConsistency();
}

void PropertyMap::rehash(unsigned newTableSize)
{
    ASSERT(!m_singleEntryKey);
    ASSERT(m_u.table);
    ASSERT(m_usingTable);

    checkConsistency();

    Table* oldTable = m_u.table;
    
    m_u.table = static_cast<Table*>(fastZeroedMalloc(Table::allocationSize(newTableSize)));
    m_u.table->size = newTableSize;
    m_u.table->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);
            insert(oldTable->entries()[i]);
        }
    }
    m_u.table->lastIndexUsed = lastIndexUsed;

    fastFree(oldTable);

    checkConsistency();
}

void PropertyMap::remove(const Identifier& name)
{
    ASSERT(!name.isNull());
    
    checkConsistency();

    UString::Rep* rep = name._ustring.rep();

    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (rep == m_singleEntryKey) {
            m_singleEntryKey->deref();
            m_singleEntryKey = 0;
            checkConsistency();
        }
#endif
        return;
    }

#if DUMP_PROPERTYMAP_STATS
    ++numProbes;
    ++numRemoves;
#endif

    // Find the thing to remove.
    unsigned i = rep->computedHash();
    unsigned k = 0;
    unsigned entryIndex;
    UString::Rep* key = 0;
    while (1) {
        entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
        if (entryIndex == emptyEntryIndex)
            return;

        key = m_u.table->entries()[entryIndex - 1].key;
        if (rep == key)
            break;

        if (k == 0) {
            k = 1 | doubleHash(rep->computedHash());
#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_u.table->entryIndicies[i & m_u.table->sizeMask] = deletedSentinelIndex;
    key->deref();
    m_u.table->entries()[entryIndex - 1].key = 0;
    m_u.table->entries()[entryIndex - 1].value = jsUndefined();
    m_u.table->entries()[entryIndex - 1].attributes = 0;
    ASSERT(m_u.table->keyCount >= 1);
    --m_u.table->keyCount;
    ++m_u.table->deletedSentinelCount;

    if (m_u.table->deletedSentinelCount * 4 >= m_u.table->size)
        rehash();

    checkConsistency();
}

void PropertyMap::mark() const
{
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (m_singleEntryKey) {
            JSValue* v = m_u.singleEntryValue;
            if (!v->marked())
                v->mark();
        }
#endif
        return;
    }

    unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
    for (unsigned i = 1; i <= entryCount; i++) {
        JSValue* v = m_u.table->entries()[i].value;
        if (!v->marked())
            v->mark();
    }
}

static 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;
}

bool PropertyMap::containsGettersOrSetters() const
{
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        return !!(m_singleEntryAttributes & GetterSetter);
#else
        return false;
#endif
    }

    unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
    for (unsigned i = 1; i <= entryCount; i++) {
        if (m_u.table->entries()[i].attributes & GetterSetter)
            return true;
    }

    return false;
}

void PropertyMap::getEnumerablePropertyNames(PropertyNameArray& propertyNames) const
{
    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        UString::Rep* key = m_singleEntryKey;
        if (key && !(m_singleEntryAttributes & DontEnum))
            propertyNames.add(Identifier(key));
#endif
        return;
    }

    if (m_u.table->keyCount < tinyMapThreshold) {
        Entry* a[tinyMapThreshold];
        int i = 0;
        unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
        for (unsigned k = 1; k <= entryCount; k++) {
            if (m_u.table->entries()[k].key && !(m_u.table->entries()[k].attributes & DontEnum)) {
                Entry* value = &m_u.table->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;
            }
        }
        for (int k = 0; k < i; ++k)
            propertyNames.add(Identifier(a[k]->key));
        return;
    }

    // Allocate a buffer to use to sort the keys.
    Vector<Entry*, smallMapThreshold> sortedEnumerables(m_u.table->keyCount);

    // Get pointers to the enumerable entries in the buffer.
    Entry** p = sortedEnumerables.data();
    unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
    for (unsigned i = 1; i <= entryCount; i++) {
        if (m_u.table->entries()[i].key && !(m_u.table->entries()[i].attributes & DontEnum))
            *p++ = &m_u.table->entries()[i];
    }

    // Sort the entries by index.
    qsort(sortedEnumerables.data(), p - sortedEnumerables.data(), sizeof(Entry*), comparePropertyMapEntryIndices);

    // Put the keys of the sorted entries into the list.
    for (Entry** q = sortedEnumerables.data(); q != p; ++q)
        propertyNames.add(Identifier(q[0]->key));
}

void PropertyMap::save(SavedProperties& s) const
{
    unsigned count = 0;

    if (!m_usingTable) {
#if USE_SINGLE_ENTRY
        if (m_singleEntryKey && !(m_singleEntryAttributes & (ReadOnly | Function)))
            ++count;
#endif
    } else {
        unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
        for (unsigned i = 1; i <= entryCount; ++i)
            if (m_u.table->entries()[i].key && !(m_u.table->entries()[i].attributes & (ReadOnly | Function)))
                ++count;
    }

    s.properties.clear();
    s.count = count;

    if (count == 0)
        return;
    
    s.properties.set(new SavedProperty[count]);
    
    SavedProperty* prop = s.properties.get();
    
#if USE_SINGLE_ENTRY
    if (!m_usingTable) {
        prop->init(m_singleEntryKey, m_u.singleEntryValue, m_singleEntryAttributes);
        return;
    }
#endif

    // Save in the right order so we don't lose the order.
    // Another possibility would be to save the indices.

    // Allocate a buffer to use to sort the keys.
    Vector<Entry*, smallMapThreshold> sortedEntries(count);

    // Get pointers to the entries in the buffer.
    Entry** p = sortedEntries.data();
    unsigned entryCount = m_u.table->keyCount + m_u.table->deletedSentinelCount;
    for (unsigned i = 1; i <= entryCount; ++i) {
        if (m_u.table->entries()[i].key && !(m_u.table->entries()[i].attributes & (ReadOnly | Function)))
            *p++ = &m_u.table->entries()[i];
    }
    ASSERT(p == sortedEntries.data() + count);

    // Sort the entries by index.
    qsort(sortedEntries.data(), p - sortedEntries.data(), sizeof(Entry*), comparePropertyMapEntryIndices);

    // Put the sorted entries into the saved properties list.
    for (Entry** q = sortedEntries.data(); q != p; ++q, ++prop) {
        Entry* e = *q;
        prop->init(e->key, e->value, e->attributes);
    }
}

void PropertyMap::restore(const SavedProperties& p)
{
    for (unsigned i = 0; i != p.count; ++i)
        put(Identifier(p.properties[i].name()), p.properties[i].value(), p.properties[i].attributes());
}

#if DO_PROPERTYMAP_CONSTENCY_CHECK

void PropertyMap::checkConsistency()
{
    if (!m_usingTable)
        return;

    ASSERT(m_u.table->size >= 16);
    ASSERT(m_u.table->sizeMask);
    ASSERT(m_u.table->size == m_u.table->sizeMask + 1);
    ASSERT(!(m_u.table->size & m_u.table->sizeMask));

    ASSERT(m_u.table->keyCount <= m_u.table->size / 2);
    ASSERT(m_u.table->deletedSentinelCount <= m_u.table->size / 4);

    ASSERT(m_u.table->keyCount + m_u.table->deletedSentinelCount <= m_u.table->size / 2);

    unsigned indexCount = 0;
    unsigned deletedIndexCount = 0;
    for (unsigned a = 0; a != m_u.table->size; ++a) {
        unsigned entryIndex = m_u.table->entryIndicies[a];
        if (entryIndex == emptyEntryIndex)
            continue;
        if (entryIndex == deletedSentinelIndex) {
            ++deletedIndexCount;
            continue;
        }
        ASSERT(entryIndex > deletedSentinelIndex);
        ASSERT(entryIndex - 1 <= m_u.table->keyCount + m_u.table->deletedSentinelCount);
        ++indexCount;

        for (unsigned b = a + 1; b != m_u.table->size; ++b)
            ASSERT(m_u.table->entryIndicies[b] != entryIndex);
    }
    ASSERT(indexCount == m_u.table->keyCount);
    ASSERT(deletedIndexCount == m_u.table->deletedSentinelCount);

    ASSERT(m_u.table->entries()[0].key == 0);

    unsigned nonEmptyEntryCount = 0;
    for (unsigned c = 1; c <= m_u.table->keyCount + m_u.table->deletedSentinelCount; ++c) {
        UString::Rep* rep = m_u.table->entries()[c].key;
        if (!rep) {
            ASSERT(m_u.table->entries()[c].value->isUndefined());
            continue;
        }
        ++nonEmptyEntryCount;
        unsigned i = rep->computedHash();
        unsigned k = 0;
        unsigned entryIndex;
        while (1) {
            entryIndex = m_u.table->entryIndicies[i & m_u.table->sizeMask];
            ASSERT(entryIndex != emptyEntryIndex);
            if (rep == m_u.table->entries()[entryIndex - 1].key)
                break;
            if (k == 0)
                k = 1 | doubleHash(rep->computedHash());
            i += k;
        }
        ASSERT(entryIndex == c + 1);
    }

    ASSERT(nonEmptyEntryCount == m_u.table->keyCount);
}

#endif // DO_PROPERTYMAP_CONSTENCY_CHECK

} // namespace KJS