2 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
22 #include "property_map.h"
26 #include "PropertyNameArray.h"
28 #include <wtf/Assertions.h>
29 #include <wtf/FastMalloc.h>
30 #include <wtf/HashTable.h>
31 #include <wtf/Vector.h>
34 using WTF::doubleHash
;
37 #define DO_PROPERTYMAP_CONSTENCY_CHECK 0
38 #define DUMP_PROPERTYMAP_STATS 0
40 #define DO_PROPERTYMAP_CONSTENCY_CHECK 0
41 #define DUMP_PROPERTYMAP_STATS 0
44 #define USE_SINGLE_ENTRY 1
46 // 2/28/2006 ggaren: command-line JS iBench says that USE_SINGLE_ENTRY is a
47 // 3.2% performance boost.
51 // Choose a number for the following so that most property maps are smaller,
52 // but it's not going to blow out the stack to allocate this number of pointers.
53 static const int smallMapThreshold
= 1024;
55 // The point at which the function call overhead of the qsort implementation
56 // becomes small compared to the inefficiency of insertion sort.
57 static const unsigned tinyMapThreshold
= 20;
59 #if DUMP_PROPERTYMAP_STATS
62 static int numCollisions
;
63 static int numRehashes
;
64 static int numRemoves
;
66 struct PropertyMapStatisticsExitLogger
{ ~PropertyMapStatisticsExitLogger(); };
68 static PropertyMapStatisticsExitLogger logger
;
70 PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger()
72 printf("\nKJS::PropertyMap statistics\n\n");
73 printf("%d probes\n", numProbes
);
74 printf("%d collisions (%.1f%%)\n", numCollisions
, 100.0 * numCollisions
/ numProbes
);
75 printf("%d rehashes\n", numRehashes
);
76 printf("%d removes\n", numRemoves
);
81 struct PropertyMapEntry
{
87 PropertyMapEntry(UString::Rep
* k
, JSValue
* v
, int a
)
88 : key(k
), value(v
), attributes(a
), index(0)
93 // lastIndexUsed is an ever-increasing index used to identify the order items
94 // were inserted into the property map. It's required that getEnumerablePropertyNames
95 // return the properties in the order they were added for compatibility with other
96 // browsers' JavaScript implementations.
97 struct PropertyMapHashTable
{
101 unsigned deletedSentinelCount
;
102 unsigned lastIndexUsed
;
103 unsigned entryIndicies
[1];
105 PropertyMapEntry
* entries()
107 // The entries vector comes after the indices vector.
108 // The 0th item in the entries vector is not really used; it has to
109 // have a 0 in its key to allow the hash table lookup to handle deleted
110 // sentinels without any special-case code, but the other fields are unused.
111 return reinterpret_cast<PropertyMapEntry
*>(&entryIndicies
[size
]);
114 static size_t allocationSize(unsigned size
)
116 // We never let a hash table get more than half full,
117 // So the number of indices we need is the size of the hash table.
118 // But the number of entries is half that (plus one for the deleted sentinel).
119 return sizeof(PropertyMapHashTable
)
120 + (size
- 1) * sizeof(unsigned)
121 + (1 + size
/ 2) * sizeof(PropertyMapEntry
);
125 static const unsigned emptyEntryIndex
= 0;
126 static const unsigned deletedSentinelIndex
= 1;
128 SavedProperties::SavedProperties()
133 SavedProperties::~SavedProperties()
137 #if !DO_PROPERTYMAP_CONSTENCY_CHECK
139 inline void PropertyMap::checkConsistency()
145 PropertyMap::~PropertyMap()
149 if (m_singleEntryKey
)
150 m_singleEntryKey
->deref();
155 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
156 for (unsigned i
= 1; i
<= entryCount
; i
++) {
157 if (UString::Rep
* key
= m_u
.table
->entries()[i
].key
)
163 void PropertyMap::clear()
167 if (m_singleEntryKey
) {
168 m_singleEntryKey
->deref();
169 m_singleEntryKey
= 0;
175 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
176 for (unsigned i
= 1; i
<= entryCount
; i
++) {
177 if (UString::Rep
* key
= m_u
.table
->entries()[i
].key
)
180 for (unsigned i
= 0; i
< m_u
.table
->size
; i
++)
181 m_u
.table
->entryIndicies
[i
] = emptyEntryIndex
;
182 m_u
.table
->keyCount
= 0;
183 m_u
.table
->deletedSentinelCount
= 0;
186 JSValue
* PropertyMap::get(const Identifier
& name
, unsigned& attributes
) const
188 ASSERT(!name
.isNull());
190 UString::Rep
* rep
= name
._ustring
.rep();
194 if (rep
== m_singleEntryKey
) {
195 attributes
= m_singleEntryAttributes
;
196 return m_u
.singleEntryValue
;
202 unsigned i
= rep
->computedHash();
204 #if DUMP_PROPERTYMAP_STATS
208 unsigned entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
209 if (entryIndex
== emptyEntryIndex
)
212 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
) {
213 attributes
= m_u
.table
->entries()[entryIndex
- 1].attributes
;
214 return m_u
.table
->entries()[entryIndex
- 1].value
;
217 #if DUMP_PROPERTYMAP_STATS
221 unsigned k
= 1 | doubleHash(rep
->computedHash());
226 #if DUMP_PROPERTYMAP_STATS
230 entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
231 if (entryIndex
== emptyEntryIndex
)
234 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
) {
235 attributes
= m_u
.table
->entries()[entryIndex
- 1].attributes
;
236 return m_u
.table
->entries()[entryIndex
- 1].value
;
241 JSValue
* PropertyMap::get(const Identifier
& name
) const
243 ASSERT(!name
.isNull());
245 UString::Rep
* rep
= name
._ustring
.rep();
249 if (rep
== m_singleEntryKey
)
250 return m_u
.singleEntryValue
;
255 unsigned i
= rep
->computedHash();
257 #if DUMP_PROPERTYMAP_STATS
261 unsigned entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
262 if (entryIndex
== emptyEntryIndex
)
265 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
)
266 return m_u
.table
->entries()[entryIndex
- 1].value
;
268 #if DUMP_PROPERTYMAP_STATS
272 unsigned k
= 1 | doubleHash(rep
->computedHash());
277 #if DUMP_PROPERTYMAP_STATS
281 entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
282 if (entryIndex
== emptyEntryIndex
)
285 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
)
286 return m_u
.table
->entries()[entryIndex
- 1].value
;
290 JSValue
** PropertyMap::getLocation(const Identifier
& name
)
292 ASSERT(!name
.isNull());
294 UString::Rep
* rep
= name
._ustring
.rep();
298 if (rep
== m_singleEntryKey
)
299 return &m_u
.singleEntryValue
;
304 unsigned i
= rep
->computedHash();
306 #if DUMP_PROPERTYMAP_STATS
310 unsigned entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
311 if (entryIndex
== emptyEntryIndex
)
314 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
)
315 return &m_u
.table
->entries()[entryIndex
- 1].value
;
317 #if DUMP_PROPERTYMAP_STATS
321 unsigned k
= 1 | doubleHash(rep
->computedHash());
326 #if DUMP_PROPERTYMAP_STATS
330 entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
331 if (entryIndex
== emptyEntryIndex
)
334 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
)
335 return &m_u
.table
->entries()[entryIndex
- 1].value
;
339 void PropertyMap::put(const Identifier
& name
, JSValue
* value
, unsigned attributes
, bool checkReadOnly
)
341 ASSERT(!name
.isNull());
346 UString::Rep
* rep
= name
._ustring
.rep();
350 if (!m_singleEntryKey
) {
352 m_singleEntryKey
= rep
;
353 m_u
.singleEntryValue
= value
;
354 m_singleEntryAttributes
= static_cast<short>(attributes
);
358 if (rep
== m_singleEntryKey
&& !(checkReadOnly
&& (m_singleEntryAttributes
& ReadOnly
))) {
359 m_u
.singleEntryValue
= value
;
365 if (!m_usingTable
|| (m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
) * 2 >= m_u
.table
->size
)
368 // FIXME: Consider a fast case for tables with no deleted sentinels.
370 unsigned i
= rep
->computedHash();
372 bool foundDeletedElement
= false;
373 unsigned deletedElementIndex
= 0; // initialize to make the compiler happy
375 #if DUMP_PROPERTYMAP_STATS
380 unsigned entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
381 if (entryIndex
== emptyEntryIndex
)
384 if (m_u
.table
->entries()[entryIndex
- 1].key
== rep
) {
385 if (checkReadOnly
&& (m_u
.table
->entries()[entryIndex
- 1].attributes
& ReadOnly
))
387 // Put a new value in an existing hash table entry.
388 m_u
.table
->entries()[entryIndex
- 1].value
= value
;
389 // Attributes are intentionally not updated.
391 } else if (entryIndex
== deletedSentinelIndex
) {
392 // If we find a deleted-element sentinel, remember it for use later.
393 if (!foundDeletedElement
) {
394 foundDeletedElement
= true;
395 deletedElementIndex
= i
;
400 k
= 1 | doubleHash(rep
->computedHash());
401 #if DUMP_PROPERTYMAP_STATS
408 #if DUMP_PROPERTYMAP_STATS
413 // Figure out which entry to use.
414 unsigned entryIndex
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
+ 2;
415 if (foundDeletedElement
) {
416 i
= deletedElementIndex
;
417 --m_u
.table
->deletedSentinelCount
;
419 // Since we're not making the table bigger, we can't use the entry one past
420 // the end that we were planning on using, so search backwards for the empty
421 // slot that we can use. We know it will be there because we did at least one
422 // deletion in the past that left an entry empty.
423 while (m_u
.table
->entries()[--entryIndex
- 1].key
)
428 // Create a new hash table entry.
429 m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
] = entryIndex
;
431 // Create a new hash table entry.
433 m_u
.table
->entries()[entryIndex
- 1].key
= rep
;
434 m_u
.table
->entries()[entryIndex
- 1].value
= value
;
435 m_u
.table
->entries()[entryIndex
- 1].attributes
= attributes
;
436 m_u
.table
->entries()[entryIndex
- 1].index
= ++m_u
.table
->lastIndexUsed
;
437 ++m_u
.table
->keyCount
;
442 void PropertyMap::insert(const Entry
& entry
)
446 unsigned i
= entry
.key
->computedHash();
449 #if DUMP_PROPERTYMAP_STATS
454 unsigned entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
455 if (entryIndex
== emptyEntryIndex
)
459 k
= 1 | doubleHash(entry
.key
->computedHash());
460 #if DUMP_PROPERTYMAP_STATS
467 #if DUMP_PROPERTYMAP_STATS
472 unsigned entryIndex
= m_u
.table
->keyCount
+ 2;
473 m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
] = entryIndex
;
474 m_u
.table
->entries()[entryIndex
- 1] = entry
;
475 ++m_u
.table
->keyCount
;
478 void PropertyMap::expand()
483 rehash(m_u
.table
->size
* 2);
486 void PropertyMap::rehash()
488 ASSERT(m_usingTable
);
490 ASSERT(m_u
.table
->size
);
491 rehash(m_u
.table
->size
);
494 void PropertyMap::createTable()
496 const unsigned newTableSize
= 16;
498 ASSERT(!m_usingTable
);
503 JSValue
* oldSingleEntryValue
= m_u
.singleEntryValue
;
506 m_u
.table
= static_cast<Table
*>(fastZeroedMalloc(Table::allocationSize(newTableSize
)));
507 m_u
.table
->size
= newTableSize
;
508 m_u
.table
->sizeMask
= newTableSize
- 1;
512 if (m_singleEntryKey
) {
513 insert(Entry(m_singleEntryKey
, oldSingleEntryValue
, m_singleEntryAttributes
));
514 m_singleEntryKey
= 0;
521 void PropertyMap::rehash(unsigned newTableSize
)
523 ASSERT(!m_singleEntryKey
);
525 ASSERT(m_usingTable
);
529 Table
* oldTable
= m_u
.table
;
531 m_u
.table
= static_cast<Table
*>(fastZeroedMalloc(Table::allocationSize(newTableSize
)));
532 m_u
.table
->size
= newTableSize
;
533 m_u
.table
->sizeMask
= newTableSize
- 1;
535 unsigned lastIndexUsed
= 0;
536 unsigned entryCount
= oldTable
->keyCount
+ oldTable
->deletedSentinelCount
;
537 for (unsigned i
= 1; i
<= entryCount
; ++i
) {
538 if (oldTable
->entries()[i
].key
) {
539 lastIndexUsed
= max(oldTable
->entries()[i
].index
, lastIndexUsed
);
540 insert(oldTable
->entries()[i
]);
543 m_u
.table
->lastIndexUsed
= lastIndexUsed
;
550 void PropertyMap::remove(const Identifier
& name
)
552 ASSERT(!name
.isNull());
556 UString::Rep
* rep
= name
._ustring
.rep();
560 if (rep
== m_singleEntryKey
) {
561 m_singleEntryKey
->deref();
562 m_singleEntryKey
= 0;
569 #if DUMP_PROPERTYMAP_STATS
574 // Find the thing to remove.
575 unsigned i
= rep
->computedHash();
578 UString::Rep
* key
= 0;
580 entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
581 if (entryIndex
== emptyEntryIndex
)
584 key
= m_u
.table
->entries()[entryIndex
- 1].key
;
589 k
= 1 | doubleHash(rep
->computedHash());
590 #if DUMP_PROPERTYMAP_STATS
597 #if DUMP_PROPERTYMAP_STATS
602 // Replace this one element with the deleted sentinel. Also clear out
603 // the entry so we can iterate all the entries as needed.
604 m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
] = deletedSentinelIndex
;
606 m_u
.table
->entries()[entryIndex
- 1].key
= 0;
607 m_u
.table
->entries()[entryIndex
- 1].value
= jsUndefined();
608 m_u
.table
->entries()[entryIndex
- 1].attributes
= 0;
609 ASSERT(m_u
.table
->keyCount
>= 1);
610 --m_u
.table
->keyCount
;
611 ++m_u
.table
->deletedSentinelCount
;
613 if (m_u
.table
->deletedSentinelCount
* 4 >= m_u
.table
->size
)
619 void PropertyMap::mark() const
623 if (m_singleEntryKey
) {
624 JSValue
* v
= m_u
.singleEntryValue
;
632 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
633 for (unsigned i
= 1; i
<= entryCount
; i
++) {
634 JSValue
* v
= m_u
.table
->entries()[i
].value
;
640 static int comparePropertyMapEntryIndices(const void* a
, const void* b
)
642 unsigned ia
= static_cast<PropertyMapEntry
* const*>(a
)[0]->index
;
643 unsigned ib
= static_cast<PropertyMapEntry
* const*>(b
)[0]->index
;
651 bool PropertyMap::containsGettersOrSetters() const
655 return !!(m_singleEntryAttributes
& GetterSetter
);
661 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
662 for (unsigned i
= 1; i
<= entryCount
; i
++) {
663 if (m_u
.table
->entries()[i
].attributes
& GetterSetter
)
670 void PropertyMap::getEnumerablePropertyNames(PropertyNameArray
& propertyNames
) const
674 UString::Rep
* key
= m_singleEntryKey
;
675 if (key
&& !(m_singleEntryAttributes
& DontEnum
))
676 propertyNames
.add(Identifier(key
));
681 if (m_u
.table
->keyCount
< tinyMapThreshold
) {
682 Entry
* a
[tinyMapThreshold
];
684 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
685 for (unsigned k
= 1; k
<= entryCount
; k
++) {
686 if (m_u
.table
->entries()[k
].key
&& !(m_u
.table
->entries()[k
].attributes
& DontEnum
)) {
687 Entry
* value
= &m_u
.table
->entries()[k
];
689 for (j
= i
- 1; j
>= 0 && a
[j
]->index
> value
->index
; --j
)
695 for (int k
= 0; k
< i
; ++k
)
696 propertyNames
.add(Identifier(a
[k
]->key
));
700 // Allocate a buffer to use to sort the keys.
701 Vector
<Entry
*, smallMapThreshold
> sortedEnumerables(m_u
.table
->keyCount
);
703 // Get pointers to the enumerable entries in the buffer.
704 Entry
** p
= sortedEnumerables
.data();
705 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
706 for (unsigned i
= 1; i
<= entryCount
; i
++) {
707 if (m_u
.table
->entries()[i
].key
&& !(m_u
.table
->entries()[i
].attributes
& DontEnum
))
708 *p
++ = &m_u
.table
->entries()[i
];
711 // Sort the entries by index.
712 qsort(sortedEnumerables
.data(), p
- sortedEnumerables
.data(), sizeof(Entry
*), comparePropertyMapEntryIndices
);
714 // Put the keys of the sorted entries into the list.
715 for (Entry
** q
= sortedEnumerables
.data(); q
!= p
; ++q
)
716 propertyNames
.add(Identifier(q
[0]->key
));
719 void PropertyMap::save(SavedProperties
& s
) const
725 if (m_singleEntryKey
&& !(m_singleEntryAttributes
& (ReadOnly
| Function
)))
729 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
730 for (unsigned i
= 1; i
<= entryCount
; ++i
)
731 if (m_u
.table
->entries()[i
].key
&& !(m_u
.table
->entries()[i
].attributes
& (ReadOnly
| Function
)))
735 s
.properties
.clear();
741 s
.properties
.set(new SavedProperty
[count
]);
743 SavedProperty
* prop
= s
.properties
.get();
747 prop
->init(m_singleEntryKey
, m_u
.singleEntryValue
, m_singleEntryAttributes
);
752 // Save in the right order so we don't lose the order.
753 // Another possibility would be to save the indices.
755 // Allocate a buffer to use to sort the keys.
756 Vector
<Entry
*, smallMapThreshold
> sortedEntries(count
);
758 // Get pointers to the entries in the buffer.
759 Entry
** p
= sortedEntries
.data();
760 unsigned entryCount
= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
;
761 for (unsigned i
= 1; i
<= entryCount
; ++i
) {
762 if (m_u
.table
->entries()[i
].key
&& !(m_u
.table
->entries()[i
].attributes
& (ReadOnly
| Function
)))
763 *p
++ = &m_u
.table
->entries()[i
];
765 ASSERT(p
== sortedEntries
.data() + count
);
767 // Sort the entries by index.
768 qsort(sortedEntries
.data(), p
- sortedEntries
.data(), sizeof(Entry
*), comparePropertyMapEntryIndices
);
770 // Put the sorted entries into the saved properties list.
771 for (Entry
** q
= sortedEntries
.data(); q
!= p
; ++q
, ++prop
) {
773 prop
->init(e
->key
, e
->value
, e
->attributes
);
777 void PropertyMap::restore(const SavedProperties
& p
)
779 for (unsigned i
= 0; i
!= p
.count
; ++i
)
780 put(Identifier(p
.properties
[i
].name()), p
.properties
[i
].value(), p
.properties
[i
].attributes());
783 #if DO_PROPERTYMAP_CONSTENCY_CHECK
785 void PropertyMap::checkConsistency()
790 ASSERT(m_u
.table
->size
>= 16);
791 ASSERT(m_u
.table
->sizeMask
);
792 ASSERT(m_u
.table
->size
== m_u
.table
->sizeMask
+ 1);
793 ASSERT(!(m_u
.table
->size
& m_u
.table
->sizeMask
));
795 ASSERT(m_u
.table
->keyCount
<= m_u
.table
->size
/ 2);
796 ASSERT(m_u
.table
->deletedSentinelCount
<= m_u
.table
->size
/ 4);
798 ASSERT(m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
<= m_u
.table
->size
/ 2);
800 unsigned indexCount
= 0;
801 unsigned deletedIndexCount
= 0;
802 for (unsigned a
= 0; a
!= m_u
.table
->size
; ++a
) {
803 unsigned entryIndex
= m_u
.table
->entryIndicies
[a
];
804 if (entryIndex
== emptyEntryIndex
)
806 if (entryIndex
== deletedSentinelIndex
) {
810 ASSERT(entryIndex
> deletedSentinelIndex
);
811 ASSERT(entryIndex
- 1 <= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
);
814 for (unsigned b
= a
+ 1; b
!= m_u
.table
->size
; ++b
)
815 ASSERT(m_u
.table
->entryIndicies
[b
] != entryIndex
);
817 ASSERT(indexCount
== m_u
.table
->keyCount
);
818 ASSERT(deletedIndexCount
== m_u
.table
->deletedSentinelCount
);
820 ASSERT(m_u
.table
->entries()[0].key
== 0);
822 unsigned nonEmptyEntryCount
= 0;
823 for (unsigned c
= 1; c
<= m_u
.table
->keyCount
+ m_u
.table
->deletedSentinelCount
; ++c
) {
824 UString::Rep
* rep
= m_u
.table
->entries()[c
].key
;
826 ASSERT(m_u
.table
->entries()[c
].value
->isUndefined());
829 ++nonEmptyEntryCount
;
830 unsigned i
= rep
->computedHash();
834 entryIndex
= m_u
.table
->entryIndicies
[i
& m_u
.table
->sizeMask
];
835 ASSERT(entryIndex
!= emptyEntryIndex
);
836 if (rep
== m_u
.table
->entries()[entryIndex
- 1].key
)
839 k
= 1 | doubleHash(rep
->computedHash());
842 ASSERT(entryIndex
== c
+ 1);
845 ASSERT(nonEmptyEntryCount
== m_u
.table
->keyCount
);
848 #endif // DO_PROPERTYMAP_CONSTENCY_CHECK