1 /* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
2 /* vim: set ts=40 sw=4 et tw=99: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is the Mozilla SpiderMonkey property tree implementation
18 * The Initial Developer of the Original Code is
20 * Portions created by the Initial Developer are Copyright (C) 2002-2010
21 * the Initial Developer. All Rights Reserved.
24 * Brendan Eich <brendan@mozilla.org>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
47 #include "jspropertytree.h"
50 #include "jsscopeinlines.h"
54 struct PropertyRootKey
56 const JSScopeProperty
*firstProp
;
59 PropertyRootKey(const JSScopeProperty
*child
, uint32 shape
)
60 : firstProp(child
), emptyShape(shape
) {}
62 static JSDHashNumber
hash(JSDHashTable
*table
, const void *key
) {
63 const PropertyRootKey
*rkey
= (const PropertyRootKey
*)key
;
65 return rkey
->firstProp
->hash() ^ rkey
->emptyShape
;
69 struct PropertyRootEntry
: public JSDHashEntryHdr
71 JSScopeProperty
*firstProp
;
75 static JSBool
match(JSDHashTable
*table
, const JSDHashEntryHdr
*hdr
, const void *key
) {
76 const PropertyRootEntry
*rent
= (const PropertyRootEntry
*)hdr
;
77 const PropertyRootKey
*rkey
= (const PropertyRootKey
*)key
;
79 return rent
->firstProp
->matches(rkey
->firstProp
) &&
80 rent
->emptyShape
== rkey
->emptyShape
;
84 static const JSDHashTableOps PropertyRootHashOps
= {
87 PropertyRootKey::hash
,
88 PropertyRootEntry::match
,
89 JS_DHashMoveEntryStub
,
90 JS_DHashClearEntryStub
,
96 HashScopeProperty(JSDHashTable
*table
, const void *key
)
98 const JSScopeProperty
*sprop
= (const JSScopeProperty
*)key
;
103 MatchScopeProperty(JSDHashTable
*table
,
104 const JSDHashEntryHdr
*hdr
,
107 const JSPropertyTreeEntry
*entry
= (const JSPropertyTreeEntry
*)hdr
;
108 const JSScopeProperty
*sprop
= entry
->child
;
109 const JSScopeProperty
*kprop
= (const JSScopeProperty
*)key
;
111 return sprop
->matches(kprop
);
114 static const JSDHashTableOps PropertyTreeHashOps
= {
119 JS_DHashMoveEntryStub
,
120 JS_DHashClearEntryStub
,
121 JS_DHashFinalizeStub
,
128 if (!JS_DHashTableInit(&hash
, &PropertyRootHashOps
, NULL
,
129 sizeof(PropertyRootEntry
), JS_DHASH_MIN_SIZE
)) {
133 arenaPool
.init("properties", 256 * sizeof(JSScopeProperty
), sizeof(void *), NULL
);
134 emptyShapeChanges
= 0;
139 PropertyTree::finish()
142 JS_DHashTableFinish(&hash
);
149 * NB: Called with cx->runtime->gcLock held if gcLocked is true.
150 * On failure, return null after unlocking the GC and reporting out of memory.
153 PropertyTree::newScopeProperty(JSContext
*cx
, bool gcLocked
)
155 JSScopeProperty
*sprop
;
158 JS_LOCK_GC(cx
->runtime
);
163 arenaPool
.allocateCast
<JSScopeProperty
*>(sprop
, sizeof *sprop
);
165 JS_UNLOCK_GC(cx
->runtime
);
166 JS_ReportOutOfMemory(cx
);
171 JS_UNLOCK_GC(cx
->runtime
);
173 JS_RUNTIME_METER(cx
->runtime
, livePropTreeNodes
);
174 JS_RUNTIME_METER(cx
->runtime
, totalPropTreeNodes
);
178 #define CHUNKY_KIDS_TAG ((jsuword)1)
179 #define KIDS_IS_CHUNKY(kids) ((jsuword)(kids) & CHUNKY_KIDS_TAG)
180 #define KIDS_TO_CHUNK(kids) ((PropTreeKidsChunk *) \
181 ((jsuword)(kids) & ~CHUNKY_KIDS_TAG))
182 #define CHUNK_TO_KIDS(chunk) ((JSScopeProperty *) \
183 ((jsuword)(chunk) | CHUNKY_KIDS_TAG))
184 #define MAX_KIDS_PER_CHUNK 10
185 #define CHUNK_HASH_THRESHOLD 30
187 struct PropTreeKidsChunk
{
188 JSScopeProperty
*kids
[MAX_KIDS_PER_CHUNK
];
190 PropTreeKidsChunk
*next
;
194 * NB: Called with cx->runtime->gcLock held, always.
195 * On failure, return null after unlocking the GC and reporting out of memory.
197 static PropTreeKidsChunk
*
198 NewPropTreeKidsChunk(JSContext
*cx
)
200 PropTreeKidsChunk
*chunk
;
202 chunk
= (PropTreeKidsChunk
*) js_calloc(sizeof *chunk
);
204 JS_UNLOCK_GC(cx
->runtime
);
205 JS_ReportOutOfMemory(cx
);
208 JS_ASSERT(((jsuword
)chunk
& CHUNKY_KIDS_TAG
) == 0);
209 JS_RUNTIME_METER(cx
->runtime
, propTreeKidsChunks
);
213 static PropTreeKidsChunk
*
214 DestroyPropTreeKidsChunk(JSContext
*cx
, PropTreeKidsChunk
*chunk
)
216 JS_RUNTIME_UNMETER(cx
->runtime
, propTreeKidsChunks
);
218 JS_DHashTableDestroy(chunk
->table
);
220 PropTreeKidsChunk
*nextChunk
= chunk
->next
;
226 * NB: Called with cx->runtime->gcLock held, always.
227 * On failure, return null after unlocking the GC and reporting out of memory.
230 PropertyTree::insertChild(JSContext
*cx
, JSScopeProperty
*parent
,
231 JSScopeProperty
*child
)
234 JS_ASSERT(!child
->parent
);
235 JS_ASSERT(!JSVAL_IS_NULL(parent
->id
));
236 JS_ASSERT(!JSVAL_IS_NULL(child
->id
));
238 JSScopeProperty
**childp
= &parent
->kids
;
239 if (JSScopeProperty
*kids
= *childp
) {
240 JSScopeProperty
*sprop
;
241 PropTreeKidsChunk
*chunk
;
243 if (!KIDS_IS_CHUNKY(kids
)) {
245 JS_ASSERT(sprop
!= child
);
246 if (sprop
->matches(child
)) {
248 * Duplicate child created while racing to getChild on the same
249 * node label. See PropertyTree::getChild, further below.
251 JS_RUNTIME_METER(cx
->runtime
, duplicatePropTreeNodes
);
253 chunk
= NewPropTreeKidsChunk(cx
);
256 parent
->kids
= CHUNK_TO_KIDS(chunk
);
257 chunk
->kids
[0] = sprop
;
258 childp
= &chunk
->kids
[1];
260 PropTreeKidsChunk
**chunkp
;
262 chunk
= KIDS_TO_CHUNK(kids
);
263 if (JSDHashTable
*table
= chunk
->table
) {
264 JSPropertyTreeEntry
*entry
= (JSPropertyTreeEntry
*)
265 JS_DHashTableOperate(table
, child
, JS_DHASH_ADD
);
267 JS_UNLOCK_GC(cx
->runtime
);
268 JS_ReportOutOfMemory(cx
);
272 entry
->child
= child
;
275 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
276 childp
= &chunk
->kids
[i
];
281 chunkp
= &chunk
->next
;
287 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
288 childp
= &chunk
->kids
[i
];
293 JS_ASSERT(sprop
!= child
);
294 if (sprop
->matches(child
)) {
296 * Duplicate child, see comment above. In this
297 * case, we must let the duplicate be inserted at
298 * this level in the tree, so we keep iterating,
299 * looking for an empty slot in which to insert.
301 JS_ASSERT(sprop
!= child
);
302 JS_RUNTIME_METER(cx
->runtime
, duplicatePropTreeNodes
);
305 chunkp
= &chunk
->next
;
306 } while ((chunk
= *chunkp
) != NULL
);
309 chunk
= NewPropTreeKidsChunk(cx
);
313 childp
= &chunk
->kids
[0];
319 child
->parent
= parent
;
323 /* NB: Called with cx->runtime->gcLock held. */
325 PropertyTree::removeChild(JSContext
*cx
, JSScopeProperty
*child
)
328 JSPropertyTreeEntry
*entry
;
330 JSScopeProperty
*parent
= child
->parent
;
332 JS_ASSERT(!JSVAL_IS_NULL(parent
->id
));
334 JSScopeProperty
*kids
= parent
->kids
;
335 if (!KIDS_IS_CHUNKY(kids
)) {
336 JSScopeProperty
*kid
= kids
;
342 PropTreeKidsChunk
*list
= KIDS_TO_CHUNK(kids
);
343 PropTreeKidsChunk
*chunk
= list
;
344 PropTreeKidsChunk
**chunkp
= &list
;
346 JSDHashTable
*table
= chunk
->table
;
347 PropTreeKidsChunk
*freeChunk
= NULL
;
350 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
351 if (chunk
->kids
[i
] == child
) {
352 PropTreeKidsChunk
*lastChunk
= chunk
;
353 if (!lastChunk
->next
) {
358 chunkp
= &lastChunk
->next
;
360 } while (lastChunk
->next
);
362 for (; j
< MAX_KIDS_PER_CHUNK
; j
++) {
363 if (!lastChunk
->kids
[j
])
367 if (chunk
!= lastChunk
|| j
> i
)
368 chunk
->kids
[i
] = lastChunk
->kids
[j
];
369 lastChunk
->kids
[j
] = NULL
;
374 freeChunk
= lastChunk
;
380 chunkp
= &chunk
->next
;
381 } while ((chunk
= *chunkp
) != NULL
);
385 entry
= (JSPropertyTreeEntry
*)
386 JS_DHashTableOperate(table
, child
, JS_DHASH_LOOKUP
);
388 if (entry
->child
== child
)
389 JS_DHashTableRawRemove(table
, &entry
->hdr
);
392 DestroyPropTreeKidsChunk(cx
, freeChunk
);
396 PropertyTree::emptyShapeChange(uint32 oldEmptyShape
, uint32 newEmptyShape
)
398 if (oldEmptyShape
== newEmptyShape
)
401 PropertyRootEntry
*rent
= (PropertyRootEntry
*) hash
.entryStore
;
402 PropertyRootEntry
*rend
= rent
+ JS_DHASH_TABLE_SIZE(&hash
);
404 while (rent
< rend
) {
405 if (rent
->emptyShape
== oldEmptyShape
)
406 rent
->newEmptyShape
= newEmptyShape
;
413 static JSDHashTable
*
414 HashChunks(PropTreeKidsChunk
*chunk
, uintN n
)
418 JSScopeProperty
*sprop
;
419 JSPropertyTreeEntry
*entry
;
421 table
= JS_NewDHashTable(&PropertyTreeHashOps
, NULL
,
422 sizeof(JSPropertyTreeEntry
),
423 JS_DHASH_DEFAULT_CAPACITY(n
+ 1));
427 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
428 sprop
= chunk
->kids
[i
];
431 entry
= (JSPropertyTreeEntry
*)
432 JS_DHashTableOperate(table
, sprop
, JS_DHASH_ADD
);
433 entry
->child
= sprop
;
435 } while ((chunk
= chunk
->next
) != NULL
);
440 * Called without cx->runtime->gcLock held. This function acquires that lock
441 * only when inserting a new child. Thus there may be races to find or add a
442 * node that result in duplicates. We expect such races to be rare!
444 * We use cx->runtime->gcLock, not ...->rtLock, to avoid nesting the former
445 * inside the latter in js_GenerateShape below.
448 PropertyTree::getChild(JSContext
*cx
, JSScopeProperty
*parent
, uint32 shape
,
449 const JSScopeProperty
&child
)
451 PropertyRootEntry
*rent
;
452 JSScopeProperty
*sprop
;
455 PropertyRootKey
rkey(&child
, shape
);
457 JS_LOCK_GC(cx
->runtime
);
458 rent
= (PropertyRootEntry
*) JS_DHashTableOperate(&hash
, &rkey
, JS_DHASH_ADD
);
460 JS_UNLOCK_GC(cx
->runtime
);
461 JS_ReportOutOfMemory(cx
);
465 sprop
= rent
->firstProp
;
469 JS_ASSERT(!JSVAL_IS_NULL(parent
->id
));
472 * Because chunks are appended at the end and never deleted except by
473 * the GC, we can search without taking the runtime's GC lock. We may
474 * miss a matching sprop added by another thread, and make a duplicate
475 * one, but that is an unlikely, therefore small, cost. The property
476 * tree has extremely low fan-out below its root in popular embeddings
477 * with real-world workloads.
479 * Patterns such as defining closures that capture a constructor's
480 * environment as getters or setters on the new object that is passed
481 * in as |this| can significantly increase fan-out below the property
482 * tree root -- see bug 335700 for details.
485 sprop
= parent
->kids
;
487 if (!KIDS_IS_CHUNKY(sprop
)) {
488 if (sprop
->matches(&child
))
491 PropTreeKidsChunk
*chunk
= KIDS_TO_CHUNK(sprop
);
493 if (JSDHashTable
*table
= chunk
->table
) {
494 JS_LOCK_GC(cx
->runtime
);
495 JSPropertyTreeEntry
*entry
= (JSPropertyTreeEntry
*)
496 JS_DHashTableOperate(table
, &child
, JS_DHASH_LOOKUP
);
497 sprop
= entry
->child
;
500 goto locked_not_found
;
505 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
506 sprop
= chunk
->kids
[i
];
509 if (n
>= CHUNK_HASH_THRESHOLD
) {
510 chunk
= KIDS_TO_CHUNK(parent
->kids
);
512 JSDHashTable
*table
= HashChunks(chunk
, n
);
514 JS_ReportOutOfMemory(cx
);
518 JS_LOCK_GC(cx
->runtime
);
520 JS_DHashTableDestroy(table
);
522 chunk
->table
= table
;
523 goto locked_not_found
;
529 if (sprop
->matches(&child
))
532 n
+= MAX_KIDS_PER_CHUNK
;
533 } while ((chunk
= chunk
->next
) != NULL
);
538 JS_LOCK_GC(cx
->runtime
);
542 sprop
= newScopeProperty(cx
, true);
546 new (sprop
) JSScopeProperty(child
.id
, child
.rawGetter
, child
.rawSetter
, child
.slot
,
547 child
.attrs
, child
.flags
, child
.shortid
);
548 sprop
->parent
= sprop
->kids
= NULL
;
549 sprop
->shape
= js_GenerateShape(cx
, true);
552 rent
->firstProp
= sprop
;
553 rent
->emptyShape
= shape
;
554 rent
->newEmptyShape
= 0;
556 if (!PropertyTree::insertChild(cx
, parent
, sprop
))
561 JS_UNLOCK_GC(cx
->runtime
);
568 MeterKidCount(JSBasicStats
*bs
, uintN nkids
)
570 JS_BASIC_STATS_ACCUM(bs
, nkids
);
571 bs
->hist
[JS_MIN(nkids
, 10)]++;
575 MeterPropertyTree(JSBasicStats
*bs
, JSScopeProperty
*node
)
578 JSScopeProperty
*kids
, *kid
;
579 PropTreeKidsChunk
*chunk
;
584 if (KIDS_IS_CHUNKY(kids
)) {
585 for (chunk
= KIDS_TO_CHUNK(kids
); chunk
; chunk
= chunk
->next
) {
586 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
587 kid
= chunk
->kids
[i
];
590 MeterPropertyTree(bs
, kid
);
595 MeterPropertyTree(bs
, kids
);
600 MeterKidCount(bs
, nkids
);
603 static JSDHashOperator
604 js_MeterPropertyTree(JSDHashTable
*table
, JSDHashEntryHdr
*hdr
, uint32 number
,
607 PropertyRootEntry
*rent
= (PropertyRootEntry
*)hdr
;
608 JSBasicStats
*bs
= (JSBasicStats
*)arg
;
610 MeterPropertyTree(bs
, rent
->firstProp
);
611 return JS_DHASH_NEXT
;
615 JSScopeProperty::dump(JSContext
*cx
, FILE *fp
)
617 JS_ASSERT(!JSVAL_IS_NULL(id
));
619 jsval idval
= ID_TO_VALUE(id
);
620 if (JSVAL_IS_INT(idval
)) {
621 fprintf(fp
, "[%ld]", (long) JSVAL_TO_INT(idval
));
624 if (JSVAL_IS_STRING(idval
)) {
625 str
= JSVAL_TO_STRING(idval
);
627 JS_ASSERT(JSVAL_IS_OBJECT(idval
));
628 str
= js_ValueToString(cx
, idval
);
629 fputs("object ", fp
);
632 fputs("<error>", fp
);
634 js_FileEscapedString(fp
, str
, '"');
637 fprintf(fp
, " g/s %p/%p slot %u attrs %x ",
638 JS_FUNC_TO_DATA_PTR(void *, rawGetter
),
639 JS_FUNC_TO_DATA_PTR(void *, rawSetter
),
644 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(" " #display + first, fp), first = 0
645 DUMP_ATTR(ENUMERATE
, enumerate
);
646 DUMP_ATTR(READONLY
, readonly
);
647 DUMP_ATTR(PERMANENT
, permanent
);
648 DUMP_ATTR(GETTER
, getter
);
649 DUMP_ATTR(SETTER
, setter
);
650 DUMP_ATTR(SHARED
, shared
);
655 fprintf(fp
, "flags %x ", flags
);
659 #define DUMP_FLAG(name, display) if (flags & name) fputs(" " #display + first, fp), first = 0
660 DUMP_FLAG(ALIAS
, alias
);
661 DUMP_FLAG(HAS_SHORTID
, has_shortid
);
662 DUMP_FLAG(METHOD
, method
);
663 DUMP_FLAG(MARK
, mark
);
664 DUMP_FLAG(SHAPE_REGEN
, shape_regen
);
665 DUMP_FLAG(IN_DICTIONARY
, in_dictionary
);
670 fprintf(fp
, "shortid %d\n", shortid
);
674 JSScopeProperty::dumpSubtree(JSContext
*cx
, int level
, FILE *fp
)
676 fprintf(fp
, "%*sid ", level
, "");
681 if (KIDS_IS_CHUNKY(kids
)) {
682 PropTreeKidsChunk
*chunk
= KIDS_TO_CHUNK(kids
);
684 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
685 JSScopeProperty
*kid
= chunk
->kids
[i
];
688 JS_ASSERT(kid
->parent
== this);
689 kid
->dumpSubtree(cx
, level
, fp
);
691 } while ((chunk
= chunk
->next
) != NULL
);
693 JSScopeProperty
*kid
= kids
;
694 JS_ASSERT(kid
->parent
== this);
695 kid
->dumpSubtree(cx
, level
, fp
);
703 OrphanNodeKids(JSContext
*cx
, JSScopeProperty
*sprop
)
705 JSScopeProperty
*kids
= sprop
->kids
;
711 * The grandparent must have either no kids or (still, after the
712 * removeChild call above) chunky kids.
714 JS_ASSERT(!sprop
->parent
|| !sprop
->parent
->kids
||
715 KIDS_IS_CHUNKY(sprop
->parent
->kids
));
717 if (KIDS_IS_CHUNKY(kids
)) {
718 PropTreeKidsChunk
*chunk
= KIDS_TO_CHUNK(kids
);
721 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
722 JSScopeProperty
*kid
= chunk
->kids
[i
];
726 if (!JSVAL_IS_NULL(kid
->id
)) {
727 JS_ASSERT(kid
->parent
== sprop
);
731 } while ((chunk
= DestroyPropTreeKidsChunk(cx
, chunk
)) != NULL
);
733 JSScopeProperty
*kid
= kids
;
735 if (!JSVAL_IS_NULL(kid
->id
)) {
736 JS_ASSERT(kid
->parent
== sprop
);
743 js::RemoveNodeIfDead(JSDHashTable
*table
, JSDHashEntryHdr
*hdr
, uint32 number
, void *arg
)
745 PropertyRootEntry
*rent
= (PropertyRootEntry
*)hdr
;
746 JSScopeProperty
*sprop
= rent
->firstProp
;
748 JS_ASSERT(!sprop
->parent
);
749 if (!sprop
->marked()) {
751 OrphanNodeKids((JSContext
*)arg
, sprop
);
752 return JS_DHASH_REMOVE
;
754 return JS_DHASH_NEXT
;
758 js::SweepScopeProperties(JSContext
*cx
)
762 uint32 livePropCapacity
= 0, totalLiveCount
= 0;
765 if (const char *filename
= getenv("JS_PROPTREE_STATFILE"))
766 logfp
= fopen(filename
, "w");
770 JS_BASIC_STATS_INIT(&bs
);
771 MeterKidCount(&bs
, JS_PROPERTY_TREE(cx
).hash
.entryCount
);
772 JS_DHashTableEnumerate(&JS_PROPERTY_TREE(cx
).hash
, js_MeterPropertyTree
, &bs
);
774 double props
, nodes
, mean
, sigma
;
776 props
= cx
->runtime
->liveScopePropsPreSweep
;
777 nodes
= cx
->runtime
->livePropTreeNodes
;
778 JS_ASSERT(nodes
== bs
.sum
);
779 mean
= JS_MeanAndStdDevBS(&bs
, &sigma
);
782 "props %g nodes %g beta %g meankids %g sigma %g max %u\n",
783 props
, nodes
, nodes
/ props
, mean
, sigma
, bs
.max
);
785 JS_DumpHistogram(&bs
, logfp
);
790 * First, remove unmarked nodes from JS_PROPERTY_TREE(cx).hash. This table
791 * requires special handling up front, rather than removal during regular
792 * heap sweeping, because we cannot find an entry in it from the firstProp
793 * node pointer alone -- we would need the emptyShape too.
795 * Rather than encode emptyShape in firstProp somehow (a tagged overlay on
796 * parent, perhaps, but that would slow down JSScope::search and other hot
797 * paths), we simply orphan kids of garbage nodes in the property tree's
798 * root-ply before sweeping the node heap.
800 JS_DHashTableEnumerate(&JS_PROPERTY_TREE(cx
).hash
, RemoveNodeIfDead
, cx
);
803 * Second, if any empty scopes have been reshaped, rehash the root ply of
804 * this tree using the new empty shape numbers as key-halves. If we run out
805 * of memory trying to allocate the new hash, disable the property cache by
806 * setting SHAPE_OVERFLOW_BIT in rt->shapeGen. The next GC will therefore
807 * renumber shapes as well as (we hope, eventually) free sufficient memory
808 * for a successful re-run through this code.
810 if (JS_PROPERTY_TREE(cx
).emptyShapeChanges
) {
811 JSDHashTable
&oldHash
= JS_PROPERTY_TREE(cx
).hash
;
812 uint32 tableSize
= JS_DHASH_TABLE_SIZE(&oldHash
);
813 JSDHashTable newHash
;
815 if (!JS_DHashTableInit(&newHash
, &PropertyRootHashOps
, NULL
,
816 sizeof(PropertyRootEntry
), tableSize
)) {
817 cx
->runtime
->shapeGen
|= SHAPE_OVERFLOW_BIT
;
819 PropertyRootEntry
*rent
= (PropertyRootEntry
*) oldHash
.entryStore
;
820 PropertyRootEntry
*rend
= rent
+ tableSize
;
822 while (rent
< rend
) {
823 if (rent
->firstProp
) {
824 uint32 emptyShape
= rent
->newEmptyShape
;
826 emptyShape
= rent
->emptyShape
;
828 PropertyRootKey
rkey(rent
->firstProp
, emptyShape
);
829 PropertyRootEntry
*newRent
=
830 (PropertyRootEntry
*) JS_DHashTableOperate(&newHash
, &rkey
, JS_DHASH_ADD
);
832 newRent
->firstProp
= rent
->firstProp
;
833 newRent
->emptyShape
= emptyShape
;
834 newRent
->newEmptyShape
= 0;
839 JS_ASSERT(newHash
.generation
== 0);
840 JS_DHashTableFinish(&oldHash
);
841 JS_PROPERTY_TREE(cx
).hash
= newHash
;
842 JS_PROPERTY_TREE(cx
).emptyShapeChanges
= 0;
847 * Third, sweep the heap clean of all unmarked nodes. Here we will find
848 * nodes already GC'ed from the root ply, but we will avoid re-orphaning
849 * their kids, because the kids member will already be null.
851 JSArenaPool
&arenaPool
= JS_PROPERTY_TREE(cx
).arenaPool
;
852 JSArena
* const first
= arenaPool
.getFirst();
853 for (JSArena
*a
= arenaPool
.getSecond(); a
;) {
854 JSScopeProperty
*limit
= (JSScopeProperty
*) a
->getAvail();
857 for (JSScopeProperty
*sprop
= (JSScopeProperty
*) a
->getBase(); sprop
< limit
; sprop
++) {
858 /* If the id is null, sprop is already on the freelist. */
859 if (JSVAL_IS_NULL(sprop
->id
))
863 * If the mark bit is set, sprop is alive, so clear the mark bit
864 * and continue the while loop.
866 * Regenerate sprop->shape if it hasn't already been refreshed
867 * during the mark phase, when live scopes' lastProp members are
868 * followed to update both scope->shape and lastProp->shape.
870 if (sprop
->marked()) {
872 if (cx
->runtime
->gcRegenShapes
) {
873 if (sprop
->hasRegenFlag())
874 sprop
->clearRegenFlag();
876 sprop
->shape
= js_RegenerateShapeForGC(cx
);
882 if (!sprop
->inDictionary()) {
884 * Here, sprop is garbage to collect, but its parent might not
885 * be, so we may have to remove it from its parent's kids chunk
886 * list or kid singleton pointer set.
888 * Without a separate mark-clearing pass, we can't tell whether
889 * sprop->parent is live at this point, so we must remove sprop
890 * if its parent member is non-null. A saving grace: if sprop's
891 * parent is dead and swept by this point, sprop->parent will
892 * be null -- in the next paragraph, we null all of a property
893 * tree node's kids' parent links when sweeping that node.
896 JS_PROPERTY_TREE(cx
).removeChild(cx
, sprop
);
899 OrphanNodeKids(cx
, sprop
);
903 * Note that JSScopeProperty::insertFree nulls sprop->id so we know
904 * that sprop is on the freelist.
906 sprop
->insertFree(JS_PROPERTY_TREE(cx
).freeList
);
907 JS_RUNTIME_UNMETER(cx
->runtime
, livePropTreeNodes
);
910 /* If a contains no live properties, return it to the malloc heap. */
911 if (liveCount
== 0) {
912 for (JSScopeProperty
*sprop
= (JSScopeProperty
*) a
->getBase(); sprop
< limit
; sprop
++)
914 a
= arenaPool
.destroy(a
);
918 livePropCapacity
+= limit
- (JSScopeProperty
*) a
->getBase();
919 totalLiveCount
+= liveCount
;
921 first
->munge(a
->getNext());
929 "\nProperty tree stats for gcNumber %lu\n",
930 (unsigned long) cx
->runtime
->gcNumber
);
932 fprintf(logfp
, "arenautil %g%%\n",
933 (totalLiveCount
&& livePropCapacity
)
934 ? (totalLiveCount
* 100.0) / livePropCapacity
937 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
940 "Scope search stats:\n"
942 " hits: %6u %5.2f%% of searches\n"
943 " misses: %6u %5.2f%%\n"
944 " hashes: %6u %5.2f%%\n"
945 " steps: %6u %5.2f%% %5.2f%% of hashes\n"
946 " stepHits: %6u %5.2f%% %5.2f%%\n"
947 " stepMisses: %6u %5.2f%% %5.2f%%\n"
948 " tableAllocFails %6u\n"
950 " wrapWatchFails %6u\n"
954 " redundantPuts: %6u\n"
957 " changeFails: %6u\n"
961 " removeFrees: %6u\n"
962 " uselessRemoves: %6u\n"
964 js_scope_stats
.searches
,
965 js_scope_stats
.hits
, RATE(hits
, searches
),
966 js_scope_stats
.misses
, RATE(misses
, searches
),
967 js_scope_stats
.hashes
, RATE(hashes
, searches
),
968 js_scope_stats
.steps
, RATE(steps
, searches
), RATE(steps
, hashes
),
969 js_scope_stats
.stepHits
,
970 RATE(stepHits
, searches
), RATE(stepHits
, hashes
),
971 js_scope_stats
.stepMisses
,
972 RATE(stepMisses
, searches
), RATE(stepMisses
, hashes
),
973 js_scope_stats
.tableAllocFails
,
974 js_scope_stats
.toDictFails
,
975 js_scope_stats
.wrapWatchFails
,
977 js_scope_stats
.addFails
,
979 js_scope_stats
.redundantPuts
,
980 js_scope_stats
.putFails
,
981 js_scope_stats
.changes
,
982 js_scope_stats
.changeFails
,
983 js_scope_stats
.compresses
,
984 js_scope_stats
.grows
,
985 js_scope_stats
.removes
,
986 js_scope_stats
.removeFrees
,
987 js_scope_stats
.uselessRemoves
,
988 js_scope_stats
.shrinks
);
995 if (const char *filename
= getenv("JS_PROPTREE_DUMPFILE")) {
997 JS_snprintf(pathname
, sizeof pathname
, "%s.%lu",
998 filename
, (unsigned long)cx
->runtime
->gcNumber
);
999 FILE *dumpfp
= fopen(pathname
, "w");
1001 PropertyRootEntry
*rent
= (PropertyRootEntry
*) JS_PROPERTY_TREE(cx
).hash
.entryStore
;
1002 PropertyRootEntry
*rend
= rent
+ JS_DHASH_TABLE_SIZE(&JS_PROPERTY_TREE(cx
).hash
);
1004 while (rent
< rend
) {
1005 if (rent
->firstProp
) {
1006 fprintf(dumpfp
, "emptyShape %u ", rent
->emptyShape
);
1007 rent
->firstProp
->dumpSubtree(cx
, 0, dumpfp
);