2 * This file is part of the KDE libraries
3 * Copyright (C) 2004 Apple Computer, Inc.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
22 #include "property_map.h"
26 #include "reference_list.h"
28 #define DEBUG_PROPERTIES 0
29 #define DO_CONSISTENCY_CHECK 0
30 #define DUMP_STATISTICS 0
31 #define USE_SINGLE_ENTRY 1
33 // At the time I added USE_SINGLE_ENTRY, the optimization still gave a 1.5%
34 // performance boost to the iBench JavaScript benchmark so I didn't remove it.
36 // FIXME: _singleEntry.index is unused.
38 #if !DO_CONSISTENCY_CHECK
39 #define checkConsistency() ((void)0)
44 // Choose a number for the following so that most property maps are smaller,
45 // but it's not going to blow out the stack to allocate this number of pointers.
46 const int smallMapThreshold = 1024;
51 static int numCollisions;
52 static int numRehashes;
53 static int numRemoves;
55 struct PropertyMapStatisticsExitLogger { ~PropertyMapStatisticsExitLogger(); };
57 static PropertyMapStatisticsExitLogger logger;
59 PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger()
61 printf("\nKJS::PropertyMap statistics\n\n");
62 printf("%d probes\n", numProbes);
63 printf("%d collisions (%.1f%%)\n", numCollisions, 100.0 * numCollisions / numProbes);
64 printf("%d rehashes\n", numRehashes);
65 printf("%d removes\n", numRemoves);
70 // lastIndexUsed is an ever-increasing index used to identify the order items
71 // were inserted into the property map. It's vital that addEnumerablesToReferenceList
72 // return the properties in the order they were added for compatibility with other
73 // browsers' JavaScript implementations.
74 struct PropertyMapHashTable
81 PropertyMapHashTableEntry entries[1];
91 SavedProperties::SavedProperties() : _count(0), _properties(0) { }
93 SavedProperties::~SavedProperties()
95 delete [] _properties;
98 // Algorithm concepts from Algorithms in C++, Sedgewick.
100 PropertyMap::PropertyMap() : _table(0)
104 PropertyMap::~PropertyMap()
108 UString::Rep *key = _singleEntry.key;
115 for (int i = 0; i < _table->size; i++) {
116 UString::Rep *key = _table->entries[i].key;
123 void PropertyMap::clear()
127 UString::Rep *key = _singleEntry.key;
130 _singleEntry.key = 0;
136 for (int i = 0; i < _table->size; i++) {
137 UString::Rep *key = _table->entries[i].key;
140 _table->entries[i].key = 0;
143 _table->keyCount = 0;
144 _table->sentinelCount = 0;
147 ValueImp *PropertyMap::get(const Identifier &name, int &attributes) const
149 assert(!name.isNull());
151 UString::Rep *rep = name._ustring.rep;
155 UString::Rep *key = _singleEntry.key;
157 attributes = _singleEntry.attributes;
158 return _singleEntry.value;
164 unsigned h = rep->hash();
165 int i = h & _table->sizeMask;
169 numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
171 while (UString::Rep *key = _table->entries[i].key) {
173 attributes = _table->entries[i].attributes;
174 return _table->entries[i].value;
177 k = 1 | (h % _table->sizeMask);
178 i = (i + k) & _table->sizeMask;
186 ValueImp *PropertyMap::get(const Identifier &name) const
188 assert(!name.isNull());
190 UString::Rep *rep = name._ustring.rep;
194 UString::Rep *key = _singleEntry.key;
196 return _singleEntry.value;
201 unsigned h = rep->hash();
202 int i = h & _table->sizeMask;
206 numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
208 while (UString::Rep *key = _table->entries[i].key) {
210 return _table->entries[i].value;
212 k = 1 | (h % _table->sizeMask);
213 i = (i + k) & _table->sizeMask;
222 static void printAttributes(int attributes)
227 if (attributes & ReadOnly)
229 if (attributes & DontEnum)
231 if (attributes & DontDelete)
232 printf("DontDelete ");
233 if (attributes & Internal)
235 if (attributes & Function)
241 void PropertyMap::put(const Identifier &name, ValueImp *value, int attributes)
243 assert(!name.isNull());
248 UString::Rep *rep = name._ustring.rep;
251 printf("adding property %s, attributes = 0x%08x (", name.ascii(), attributes);
252 printAttributes(attributes);
258 UString::Rep *key = _singleEntry.key;
261 _singleEntry.value = value;
266 _singleEntry.key = rep;
267 _singleEntry.value = value;
268 _singleEntry.attributes = attributes;
275 if (!_table || _table->keyCount * 2 >= _table->size)
278 unsigned h = rep->hash();
279 int i = h & _table->sizeMask;
281 bool foundDeletedElement = false;
282 int deletedElementIndex = 0; /* initialize to make the compiler happy */
285 numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
287 while (UString::Rep *key = _table->entries[i].key) {
289 // Put a new value in an existing hash table entry.
290 _table->entries[i].value = value;
291 // Attributes are intentionally not updated.
294 // If we find the deleted-element sentinel, remember it for use later.
295 if (key == &UString::Rep::null && !foundDeletedElement) {
296 foundDeletedElement = true;
297 deletedElementIndex = i;
300 k = 1 | (h % _table->sizeMask);
301 i = (i + k) & _table->sizeMask;
307 // Use either the deleted element or the 0 at the end of the chain.
308 if (foundDeletedElement) {
309 i = deletedElementIndex;
310 _table->entries[i].key->deref();
311 --_table->sentinelCount;
314 // Create a new hash table entry.
316 _table->entries[i].key = rep;
317 _table->entries[i].value = value;
318 _table->entries[i].attributes = attributes;
319 _table->entries[i].index = ++_table->lastIndexUsed;
325 void PropertyMap::insert(UString::Rep *key, ValueImp *value, int attributes, int index)
329 unsigned h = key->hash();
330 int i = h & _table->sizeMask;
334 numCollisions += _table->entries[i].key && _table->entries[i].key != key;
336 while (_table->entries[i].key) {
337 assert(_table->entries[i].key != &UString::Rep::null);
339 k = 1 | (h % _table->sizeMask);
340 i = (i + k) & _table->sizeMask;
346 _table->entries[i].key = key;
347 _table->entries[i].value = value;
348 _table->entries[i].attributes = attributes;
349 _table->entries[i].index = index;
352 void PropertyMap::expand()
354 Table *oldTable = _table;
355 int oldTableSize = oldTable ? oldTable->size : 0;
356 rehash(oldTableSize ? oldTableSize * 2 : 16);
359 void PropertyMap::rehash()
362 assert(_table->size);
363 rehash(_table->size);
366 void PropertyMap::rehash(int newTableSize)
370 Table *oldTable = _table;
371 int oldTableSize = oldTable ? oldTable->size : 0;
372 int oldTableKeyCount = oldTable ? oldTable->keyCount : 0;
374 _table = (Table *)calloc(1, sizeof(Table) + (newTableSize - 1) * sizeof(Entry) );
375 _table->size = newTableSize;
376 _table->sizeMask = newTableSize - 1;
377 _table->keyCount = oldTableKeyCount;
380 UString::Rep *key = _singleEntry.key;
382 insert(key, _singleEntry.value, _singleEntry.attributes, 0);
383 _singleEntry.key = 0;
384 // update the count, because single entries don't count towards
385 // the table key count
387 assert(_table->keyCount == 1);
391 int lastIndexUsed = 0;
392 for (int i = 0; i != oldTableSize; ++i) {
393 Entry &entry = oldTable->entries[i];
394 UString::Rep *key = entry.key;
396 // Don't copy deleted-element sentinels.
397 if (key == &UString::Rep::null)
400 int index = entry.index;
401 lastIndexUsed = MAX(index, lastIndexUsed);
402 insert(key, entry.value, entry.attributes, index);
406 _table->lastIndexUsed = lastIndexUsed;
413 void PropertyMap::remove(const Identifier &name)
415 assert(!name.isNull());
419 UString::Rep *rep = name._ustring.rep;
425 key = _singleEntry.key;
428 _singleEntry.key = 0;
435 // Find the thing to remove.
436 unsigned h = rep->hash();
437 int i = h & _table->sizeMask;
442 numCollisions += _table->entries[i].key && _table->entries[i].key != rep;
444 while ((key = _table->entries[i].key)) {
448 k = 1 | (h % _table->sizeMask);
449 i = (i + k) & _table->sizeMask;
457 // Replace this one element with the deleted sentinel,
458 // &UString::Rep::null; also set value to 0 and attributes to DontEnum
459 // to help callers that iterate all keys not have to check for the sentinel.
461 key = &UString::Rep::null;
463 _table->entries[i].key = key;
464 _table->entries[i].value = 0;
465 _table->entries[i].attributes = DontEnum;
466 assert(_table->keyCount >= 1);
468 ++_table->sentinelCount;
470 if (_table->sentinelCount * 4 >= _table->size)
476 void PropertyMap::mark() const
480 if (_singleEntry.key) {
481 ValueImp *v = _singleEntry.value;
489 for (int i = 0; i != _table->size; ++i) {
490 UString::Rep *key = _table->entries[i].key;
492 ValueImp *v = _table->entries[i].value;
493 // Check v against 0 to handle deleted elements
494 // without comparing key to UString::Rep::null.
495 if (v && !v->marked())
501 static int comparePropertyMapEntryIndices(const void *a, const void *b)
503 int ia = static_cast<PropertyMapHashTableEntry * const *>(a)[0]->index;
504 int ib = static_cast<PropertyMapHashTableEntry * const *>(b)[0]->index;
512 void PropertyMap::addEnumerablesToReferenceList(ReferenceList &list, const Object &base) const
516 UString::Rep *key = _singleEntry.key;
517 if (key && !(_singleEntry.attributes & DontEnum))
518 list.append(Reference(base, Identifier(key)));
523 // Allocate a buffer to use to sort the keys.
524 Entry *fixedSizeBuffer[smallMapThreshold];
525 Entry **sortedEnumerables;
526 if (_table->keyCount <= smallMapThreshold)
527 sortedEnumerables = fixedSizeBuffer;
529 sortedEnumerables = new Entry *[_table->keyCount];
531 // Get pointers to the enumerable entries in the buffer.
532 Entry **p = sortedEnumerables;
533 for (int i = 0; i != _table->size; ++i) {
534 Entry *e = &_table->entries[i];
535 if (e->key && !(e->attributes & DontEnum))
539 // Sort the entries by index.
540 qsort(sortedEnumerables, p - sortedEnumerables, sizeof(sortedEnumerables[0]), comparePropertyMapEntryIndices);
542 // Put the keys of the sorted entries into the reference list.
543 Entry **q = sortedEnumerables;
545 list.append(Reference(base, Identifier((*q++)->key)));
547 // Deallocate the buffer.
548 if (sortedEnumerables != fixedSizeBuffer)
549 delete [] sortedEnumerables;
552 void PropertyMap::addSparseArrayPropertiesToReferenceList(ReferenceList &list, const Object &base) const
556 UString::Rep *key = _singleEntry.key;
560 k.toUInt32(&fitsInUInt32);
562 list.append(Reference(base, Identifier(key)));
568 for (int i = 0; i != _table->size; ++i) {
569 UString::Rep *key = _table->entries[i].key;
570 if (key && key != &UString::Rep::null)
574 k.toUInt32(&fitsInUInt32);
576 list.append(Reference(base, Identifier(key)));
581 void PropertyMap::save(SavedProperties &p) const
587 if (_singleEntry.key && !(_singleEntry.attributes & (ReadOnly | Function)))
591 for (int i = 0; i != _table->size; ++i)
592 if (_table->entries[i].key && !(_table->entries[i].attributes & (ReadOnly | Function)))
596 delete [] p._properties;
605 p._properties = new SavedProperty [count];
607 SavedProperty *prop = p._properties;
611 if (_singleEntry.key && !(_singleEntry.attributes & (ReadOnly | Function))) {
612 prop->key = Identifier(_singleEntry.key);
613 prop->value = Value(_singleEntry.value);
614 prop->attributes = _singleEntry.attributes;
619 // Save in the right order so we don't lose the order.
620 // Another possibility would be to save the indices.
622 // Allocate a buffer to use to sort the keys.
623 Entry *fixedSizeBuffer[smallMapThreshold];
624 Entry **sortedEntries;
625 if (count <= smallMapThreshold)
626 sortedEntries = fixedSizeBuffer;
628 sortedEntries = new Entry *[count];
630 // Get pointers to the entries in the buffer.
631 Entry **p = sortedEntries;
632 for (int i = 0; i != _table->size; ++i) {
633 Entry *e = &_table->entries[i];
634 if (e->key && !(e->attributes & (ReadOnly | Function)))
637 assert(p - sortedEntries == count);
639 // Sort the entries by index.
640 qsort(sortedEntries, p - sortedEntries, sizeof(sortedEntries[0]), comparePropertyMapEntryIndices);
642 // Put the sorted entries into the saved properties list.
643 Entry **q = sortedEntries;
646 prop->key = Identifier(e->key);
647 prop->value = Value(e->value);
648 prop->attributes = e->attributes;
652 // Deallocate the buffer.
653 if (sortedEntries != fixedSizeBuffer)
654 delete [] sortedEntries;
658 void PropertyMap::restore(const SavedProperties &p)
660 for (int i = 0; i != p._count; ++i)
661 put(p._properties[i].key, p._properties[i].value.imp(), p._properties[i].attributes);
664 #if DO_CONSISTENCY_CHECK
666 void PropertyMap::checkConsistency()
672 int sentinelCount = 0;
673 for (int j = 0; j != _table->size; ++j) {
674 UString::Rep *rep = _table->entries[j].key;
677 if (rep == &UString::Rep::null) {
681 unsigned h = rep->hash();
682 int i = h & _table->sizeMask;
684 while (UString::Rep *key = _table->entries[i].key) {
688 k = 1 | (h % _table->sizeMask);
689 i = (i + k) & _table->sizeMask;
694 assert(count == _table->keyCount);
695 assert(sentinelCount == _table->sentinelCount);
696 assert(_table->size >= 16);
697 assert(_table->sizeMask);
698 assert(_table->size == _table->sizeMask + 1);
701 #endif // DO_CONSISTENCY_CHECK