You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdelibs/kjs/property_map.cpp

657 lines
17 KiB

/*
* This file is part of the KDE libraries
* Copyright (C) 2003 Apple Computer, Inc.
*
* 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 "property_map.h"
#include "object.h"
#include "reference_list.h"
#include <assert.h>
#define DEBUG_PROPERTIES 0
#define DO_CONSISTENCY_CHECK 0
#define DUMP_STATISTICS 0
#define USE_SINGLE_ENTRY 1
// At the time I added USE_SINGLE_ENTRY, the optimization still gave a 1.5%
// performance boost to the iBench JavaScript benchmark so I didn't remove it.
#if !DO_CONSISTENCY_CHECK
#define checkConsistency() ((void)0)
#endif
namespace KJS {
#if DUMP_STATISTICS
static int numProbes;
static int numCollisions;
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);
}
#endif
struct PropertyMapHashTable
{
int sizeMask;
int size;
int keyCount;
// gets initialized in expand, an array that stores insert order of a particular hash
int *hashToIndex; // NOTE: is one based 1,2,3 etc..
// keeps trac on how many insertions we have made, cant use keyCount because delete a key in the middle messes things
int indexCount;
PropertyMapHashTableEntry entries[1];
};
class SavedProperty {
public:
Identifier key;
Value value;
int attributes;
};
SavedProperties::SavedProperties() : _count(0), _properties(0) { }
SavedProperties::~SavedProperties()
{
delete [] _properties;
}
// Algorithm concepts from Algorithms in C++, Sedgewick.
PropertyMap::PropertyMap() : _table(0)
{
}
PropertyMap::~PropertyMap()
{
if (!_table) {
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (key)
key->deref();
#endif
return;
}
for (int i = 0; i < _table->size; i++) {
UString::Rep *key = _table->entries[i].key;
if (key)
key->deref();
}
// fredrik added to cleanup sortorder
if (_table)
delete [] _table->hashToIndex;
free(_table);
}
void PropertyMap::clear()
{
if (!_table) {
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (key) {
key->deref();
_singleEntry.key = 0;
}
#endif
return;
}
for (int i = 0; i < _table->size; i++) {
UString::Rep *key = _table->entries[i].key;
if (key) {
key->deref();
_table->entries[i].key = 0;
}
}
_table->keyCount = 0;
}
inline int PropertyMap::hash(const UString::Rep *s) const
{
return s->hash() & _table->sizeMask;
}
ValueImp *PropertyMap::get(const Identifier &name, int &attributes) const
{
assert(!name.isNull());
UString::Rep *rep = name._ustring.rep;
if (!_table) {
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (rep == key) {
attributes = _singleEntry.attributes;
return _singleEntry.value;
}
#endif
return 0;
}
int i = hash(rep);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
#endif
while (UString::Rep *key = _table->entries[i].key) {
if (rep == key) {
attributes = _table->entries[i].attributes;
return _table->entries[i].value;
}
i = (i + 1) & _table->sizeMask;
}
return 0;
}
ValueImp *PropertyMap::get(const Identifier &name) const
{
assert(!name.isNull());
UString::Rep *rep = name._ustring.rep;
if (!_table) {
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (rep == key)
return _singleEntry.value;
#endif
return 0;
}
int i = hash(rep);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
#endif
while (UString::Rep *key = _table->entries[i].key) {
if (rep == key)
return _table->entries[i].value;
i = (i + 1) & _table->sizeMask;
}
return 0;
}
#if DEBUG_PROPERTIES
static void printAttributes(int attributes)
{
if (attributes == 0)
printf ("None ");
if (attributes & ReadOnly)
printf ("ReadOnly ");
if (attributes & DontEnum)
printf ("DontEnum ");
if (attributes & DontDelete)
printf ("DontDelete ");
if (attributes & Internal)
printf ("Internal ");
if (attributes & Function)
printf ("Function ");
}
#endif
void PropertyMap::put(const Identifier &name, ValueImp *value, int attributes)
{
assert(!name.isNull());
assert(value != 0);
#if DO_CONSISTENCY_CHECK // speed, why call a stub if we dont need to??
checkConsistency();
#endif
UString::Rep *rep = name._ustring.rep;
#if DEBUG_PROPERTIES
printf("adding property %s, attributes = 0x%08x (", name.ascii(), attributes);
printAttributes(attributes);
printf(")\n");
#endif
#if USE_SINGLE_ENTRY
if (!_table) {
UString::Rep *key = _singleEntry.key;
if (key) {
if (rep == key) {
_singleEntry.value = value;
return;
}
} else {
rep->ref();
_singleEntry.key = rep;
_singleEntry.value = value;
_singleEntry.attributes = attributes;
checkConsistency();
return;
}
}
#endif
if (!_table || _table->keyCount * 2 >= _table->size)
expand();
int i = hash(rep);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
#endif
while (UString::Rep *key = _table->entries[i].key) {
if (rep == key) {
// Put a new value in an existing hash table entry.
_table->entries[i].value = value;
// Attributes are intentionally not updated.
return;
}
i = (i + 1) & _table->sizeMask;
}
// Create a new hash table entry.
rep->ref();
_table->entries[i].key = rep;
_table->entries[i].value = value;
_table->entries[i].attributes = attributes;
++_table->keyCount;
// store insert order
_table->indexCount++;
_table->hashToIndex[i] = _table->indexCount;
#if DO_CONSISTENCY_CHECK// speed, why call a stub if we dont need to??
checkConsistency();
#endif
}
inline void PropertyMap::insert(UString::Rep *key, ValueImp *value, int attributes)
{
assert(_table);
int i = hash(key);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[i].key && _table->entries[i].key != key;
#endif
while (_table->entries[i].key)
i = (i + 1) & _table->sizeMask;
_table->entries[i].key = key;
_table->entries[i].value = value;
_table->entries[i].attributes = attributes;
}
void PropertyMap::expand()
{
#if DO_CONSISTENCY_CHECK// speed, why call a stub if we dont need to??
checkConsistency();
#endif
Table *oldTable = _table;
int oldTableSize = oldTable ? oldTable->size : 0;
int newTableSize = oldTableSize ? oldTableSize * 2 : 16;
// Is this realy the best way? wouldnt it be easier to use new/delete on an array instead
// and do a pointer in Table to that array, that way we wouldnt need to delete the whole _table
// every time we need to expand
_table = (Table *)calloc(1, sizeof(Table) + ((newTableSize - 1) * sizeof(Entry)) );
int *p = new int[newTableSize];
for (int i = 0; i < newTableSize; i++)
p[i] = 0;
_table->hashToIndex = p;
_table->size = newTableSize;
_table->sizeMask = newTableSize - 1;
_table->keyCount = oldTable ? oldTable->keyCount : 0;
_table->indexCount = oldTable ? oldTable->indexCount : 0;
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (key) {
insert(key, _singleEntry.value, _singleEntry.attributes);
_table->keyCount++;
_singleEntry.key = 0;
// store sort order
// first get the id of newly inserted key, check for trashed hash, then store it
int k = hash(key);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[k].key && _table->entries[k].key != key;
#endif
while (UString::Rep *newKey = _table->entries[k].key) {
if (key == newKey)
break;
k = (k + 1) & _table->sizeMask;
}
_table->indexCount++;
_table->hashToIndex[k] = _table->indexCount;
}
#endif
for (int i = 0; i != oldTableSize; ++i) {
UString::Rep *key = oldTable->entries[i].key;
if (key) {
insert(key, oldTable->entries[i].value, oldTable->entries[i].attributes);
// store sort order
// first get the id of newly inserted key, check for trashed hash, then store it
int k = hash(key);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[k].key && _table->entries[k].key != key;
#endif
while (UString::Rep *newKey = _table->entries[k].key) {
if (key == newKey)
break;
k = (k + 1) & _table->sizeMask;
}
// store hashindex on the newly inserted entry
_table->hashToIndex[k] = oldTable->hashToIndex[i];
}
}
if (oldTable){
delete [] oldTable->hashToIndex;
}
free(oldTable);
#if DO_CONSISTENCY_CHECK// speed, why call a stub if we dont need to??
checkConsistency();
#endif
}
void PropertyMap::remove(const Identifier &name)
{
assert(!name.isNull());
#if DO_CONSISTENCY_CHECK// speed, why call a stub if we dont need to??
checkConsistency();
#endif
UString::Rep *rep = name._ustring.rep;
UString::Rep *key;
if (!_table) {
#if USE_SINGLE_ENTRY
key = _singleEntry.key;
if (rep == key) {
key->deref();
_singleEntry.key = 0;
checkConsistency();
}
#endif
return;
}
// Find the thing to remove.
int i = hash(rep);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
#endif
while ((key = _table->entries[i].key)) {
if (rep == key)
break;
i = (i + 1) & _table->sizeMask;
}
if (!key)
return;
// Remove the one key.
key->deref();
_table->entries[i].key = 0;
assert(_table->keyCount >= 1);
--_table->keyCount;
// Reinsert all the items to the right in the same cluster.
while (1) {
i = (i + 1) & _table->sizeMask;
key = _table->entries[i].key;
if (!key)
break;
_table->entries[i].key = 0;
insert(key, _table->entries[i].value, _table->entries[i].attributes);
// store the index of the new hash
int k = hash(key);
#if DUMP_STATISTICS
++numProbes;
numCollisions += _table->entries[k].key && _table->entries[k].key != key;
#endif
while (UString::Rep *newKey = _table->entries[k].key) {
if (key == newKey)
break;
k = (k + 1) & _table->sizeMask;
}
// store hashindex on the newly moved entry
_table->hashToIndex[k] = _table->hashToIndex[i];
}
#if DO_CONSISTENCY_CHECK// speed, why call a stub if we dont need to??
checkConsistency();
#endif
}
void PropertyMap::mark() const
{
if (!_table) {
#if USE_SINGLE_ENTRY
if (_singleEntry.key) {
ValueImp *v = _singleEntry.value;
if (!v->marked())
v->mark();
}
#endif
return;
}
for (int i = 0; i != _table->size; ++i) {
if (_table->entries[i].key) {
ValueImp *v = _table->entries[i].value;
if (!v->marked())
v->mark();
}
}
}
void PropertyMap::addEnumerablesToReferenceList(ReferenceList &list, const Object &base) const
{
if (!_table) {
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (key && !(_singleEntry.attributes & DontEnum))
list.append(Reference(base, Identifier(key)));
#endif
return;
}
// Allocate a buffer to use to sort the keys.
int indexSize = _table->indexCount + 1; // indexes is one based
UString::Rep **allKeys = new UString::Rep*[indexSize];
for (int i = 0; i < indexSize; i++)
allKeys[i] = NULL;
// push valid hashes to the array allKeys, using insert order as index.
// we need to pass array hashes instead of pointers to keys, because we got
// memory corruption sometimes, seems that Identifier in below call deletes the key
int size = _table->size;
Entry *entries = _table->entries;
for (int i = 0; i != size; ++i) {
if (entries[i].key && !(entries[i].attributes & DontEnum)) {
int idx = _table->hashToIndex[i];
if (idx) {
allKeys[idx] = entries[i].key;
} else { // nonsorted key, failure
//cout<<"Error with in KJS property_map.addEnumerablesToReferenceList \nUnsorted key"<<endl;
assert(0==1); // allways throw error if get here
}
}
}
// Put the keys of the sorted entries into the reference list.
for (int i = 0; i < indexSize; ++i) {
if (allKeys[i] != NULL){
list.append(Reference(base, Identifier(allKeys[i])));
}
allKeys[i] = NULL; // dont deallocate key by accident, when we delete allKeys
}
// Deallocate the buffer.
delete [] allKeys;
}
void PropertyMap::addSparseArrayPropertiesToReferenceList(ReferenceList &list, const Object &base) const
{
// NOTE: I did'nt add sort in this method because It seems to be referenced in ArrayInstanceImp
// only and arrays are sorted by definition, dont need the extra overhead
if (!_table) {
#if USE_SINGLE_ENTRY
UString::Rep *key = _singleEntry.key;
if (key) {
UString k(key);
bool fitsInULong;
unsigned long i = k.toULong(&fitsInULong);
if (fitsInULong && i <= 0xFFFFFFFFU)
list.append(Reference(base, Identifier(key)));
}
#endif
return;
}
for (int i = 0; i != _table->size; ++i) {
UString::Rep *key = _table->entries[i].key;
if (key) {
UString k(key);
bool fitsInULong;
unsigned long i = k.toULong(&fitsInULong);
if (fitsInULong && i <= 0xFFFFFFFFU)
list.append(Reference(base, Identifier(key)));
}
}
}
void PropertyMap::save(SavedProperties &p) const
{
int count = 0;
if (!_table) {
#if USE_SINGLE_ENTRY
if (_singleEntry.key && !(_singleEntry.attributes & (ReadOnly | DontEnum | Function)))
++count;
#endif
} else {
for (int i = 0; i != _table->size; ++i)
if (_table->entries[i].key && !(_table->entries[i].attributes & (ReadOnly | DontEnum | Function)))
++count;
}
delete [] p._properties;
p._count = count;
if (count == 0) {
p._properties = 0;
return;
}
p._properties = new SavedProperty [count];
SavedProperty *prop = p._properties;
if (!_table) {
#if USE_SINGLE_ENTRY
if (_singleEntry.key && !(_singleEntry.attributes & (ReadOnly | DontEnum | Function))) {
prop->key = Identifier(_singleEntry.key);
prop->value = Value(_singleEntry.value);
prop->attributes = _singleEntry.attributes;
++prop;
}
#endif
} else {
for (int i = 0; i != _table->size; ++i) {
if (_table->entries[i].key && !(_table->entries[i].attributes & (ReadOnly | DontEnum | Function))) {
prop->key = Identifier(_table->entries[i].key);
prop->value = Value(_table->entries[i].value);
prop->attributes = _table->entries[i].attributes;
++prop;
}
}
}
}
void PropertyMap::restore(const SavedProperties &p)
{
for (int i = 0; i != p._count; ++i)
put(p._properties[i].key, p._properties[i].value.imp(), p._properties[i].attributes);
}
#if DO_CONSISTENCY_CHECK
void PropertyMap::checkConsistency()
{
if (!_table)
return;
int count = 0;
for (int j = 0; j != _table->size; ++j) {
UString::Rep *rep = _table->entries[j].key;
if (!rep)
continue;
int i = hash(rep);
while (UString::Rep *key = _table->entries[i].key) {
if (rep == key)
break;
i = (i + 1) & _table->sizeMask;
}
assert(i == j);
assert(_table->hashToIndex[i] > 0);
count++;
}
assert(count == _table->keyCount);
assert(_table->size >= 16);
assert(_table->sizeMask);
assert(_table->size == _table->sizeMask + 1);
}
#endif // DO_CONSISTENCY_CHECK
} // namespace KJS