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 ***** */
48 #include "jspropertytree.h"
51 #include "jsobjinlines.h"
52 #include "jsscopeinlines.h"
57 ShapeHasher::hash(const Lookup l
)
63 ShapeHasher::match(const Key k
, const Lookup l
)
71 JS_InitArenaPool(&arenaPool
, "properties",
72 256 * sizeof(Shape
), sizeof(void *), NULL
);
77 PropertyTree::finish()
79 JS_FinishArenaPool(&arenaPool
);
83 * NB: Called with cx->runtime->gcLock held if gcLocked is true.
84 * On failure, return null after unlocking the GC and reporting out of memory.
87 PropertyTree::newShape(JSContext
*cx
, bool gcLocked
)
92 JS_LOCK_GC(cx
->runtime
);
97 JS_ARENA_ALLOCATE_CAST(shape
, Shape
*, &arenaPool
, sizeof(Shape
));
99 JS_UNLOCK_GC(cx
->runtime
);
100 JS_ReportOutOfMemory(cx
);
105 JS_UNLOCK_GC(cx
->runtime
);
107 JS_RUNTIME_METER(cx
->runtime
, livePropTreeNodes
);
108 JS_RUNTIME_METER(cx
->runtime
, totalPropTreeNodes
);
113 * NB: Called with cx->runtime->gcLock held, always.
114 * On failure, return null after unlocking the GC and reporting out of memory.
117 KidsChunk::create(JSContext
*cx
)
121 chunk
= (KidsChunk
*) js_calloc(sizeof *chunk
);
123 JS_UNLOCK_GC(cx
->runtime
);
124 JS_ReportOutOfMemory(cx
);
127 JS_RUNTIME_METER(cx
->runtime
, propTreeKidsChunks
);
132 KidsChunk::destroy(JSContext
*cx
, KidsChunk
*chunk
)
134 JS_RUNTIME_UNMETER(cx
->runtime
, propTreeKidsChunks
);
136 KidsChunk
*nextChunk
= chunk
->next
;
142 * NB: Called with cx->runtime->gcLock held, always.
143 * On failure, return false after unlocking the GC and reporting out of memory.
146 PropertyTree::insertChild(JSContext
*cx
, Shape
*parent
, Shape
*child
)
148 JS_ASSERT(!parent
->inDictionary());
149 JS_ASSERT(!child
->parent
);
150 JS_ASSERT(!child
->inDictionary());
151 JS_ASSERT(!JSID_IS_VOID(parent
->id
));
152 JS_ASSERT(!JSID_IS_VOID(child
->id
));
154 child
->setParent(parent
);
156 KidsPointer
*kidp
= &parent
->kids
;
157 if (kidp
->isNull()) {
158 kidp
->setShape(child
);
164 if (kidp
->isShape()) {
165 shape
= kidp
->toShape();
166 JS_ASSERT(shape
!= child
);
167 if (shape
->matches(child
)) {
169 * Duplicate child created while racing to getChild on the same
170 * node label. See PropertyTree::getChild, further below.
172 JS_RUNTIME_METER(cx
->runtime
, duplicatePropTreeNodes
);
175 KidsChunk
*chunk
= KidsChunk::create(cx
);
178 parent
->kids
.setChunk(chunk
);
179 chunk
->kids
[0] = shape
;
180 chunk
->kids
[1] = child
;
184 if (kidp
->isChunk()) {
186 KidsChunk
*chunk
= kidp
->toChunk();
189 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
190 shape
= chunk
->kids
[i
];
192 chunk
->kids
[i
] = child
;
196 JS_ASSERT(shape
!= child
);
197 if (shape
->matches(child
)) {
199 * Duplicate child, see comment above. In this case, we
200 * must let the duplicate be inserted at this level in the
201 * tree, so we keep iterating, looking for an empty slot in
204 JS_ASSERT(shape
!= child
);
205 JS_RUNTIME_METER(cx
->runtime
, duplicatePropTreeNodes
);
208 chunkp
= &chunk
->next
;
209 } while ((chunk
= *chunkp
) != NULL
);
211 chunk
= KidsChunk::create(cx
);
215 chunk
->kids
[0] = child
;
219 KidsHash
*hash
= kidp
->toHash();
220 KidsHash::AddPtr addPtr
= hash
->lookupForAdd(child
);
222 if (!hash
->add(addPtr
, child
)) {
223 JS_UNLOCK_GC(cx
->runtime
);
224 JS_ReportOutOfMemory(cx
);
228 // FIXME ignore duplicate child case here, going thread-local soon!
233 /* NB: Called with cx->runtime->gcLock held. */
235 PropertyTree::removeChild(JSContext
*cx
, Shape
*child
)
237 JS_ASSERT(!child
->inDictionary());
239 Shape
*parent
= child
->parent
;
241 JS_ASSERT(!JSID_IS_VOID(parent
->id
));
243 KidsPointer
*kidp
= &parent
->kids
;
244 if (kidp
->isShape()) {
245 Shape
*kid
= kidp
->toShape();
247 parent
->kids
.setNull();
251 if (kidp
->isChunk()) {
252 KidsChunk
*list
= kidp
->toChunk();
253 KidsChunk
*chunk
= list
;
254 KidsChunk
**chunkp
= &list
;
257 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
258 if (chunk
->kids
[i
] == child
) {
259 KidsChunk
*lastChunk
= chunk
;
262 if (!lastChunk
->next
) {
267 chunkp
= &lastChunk
->next
;
269 } while (lastChunk
->next
);
271 for (; j
< MAX_KIDS_PER_CHUNK
; j
++) {
272 if (!lastChunk
->kids
[j
])
277 if (chunk
!= lastChunk
|| j
> i
)
278 chunk
->kids
[i
] = lastChunk
->kids
[j
];
279 lastChunk
->kids
[j
] = NULL
;
283 parent
->kids
.setNull();
284 KidsChunk::destroy(cx
, lastChunk
);
290 chunkp
= &chunk
->next
;
291 } while ((chunk
= *chunkp
) != NULL
);
295 kidp
->toHash()->remove(child
);
299 HashChunks(KidsChunk
*chunk
, uintN n
)
301 void *mem
= js_malloc(sizeof(KidsHash
));
305 KidsHash
*hash
= new (mem
) KidsHash();
306 if (!hash
->init(n
)) {
312 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
313 Shape
*shape
= chunk
->kids
[i
];
316 KidsHash::AddPtr addPtr
= hash
->lookupForAdd(shape
);
319 * Infallible, we right-sized via hash->init(n) just above.
320 * Assert just in case jshashtable.h ever regresses.
322 JS_ALWAYS_TRUE(hash
->add(addPtr
, shape
));
325 * Duplicate child case, we don't handle this race,
326 * multi-threaded shapes are going away...
330 } while ((chunk
= chunk
->next
) != NULL
);
335 * Called without cx->runtime->gcLock held. This function acquires that lock
336 * only when inserting a new child. Thus there may be races to find or add a
337 * node that result in duplicates. We expect such races to be rare!
339 * We use cx->runtime->gcLock, not ...->rtLock, to avoid nesting the former
340 * inside the latter in js_GenerateShape below.
343 PropertyTree::getChild(JSContext
*cx
, Shape
*parent
, const Shape
&child
)
348 JS_ASSERT(!JSID_IS_VOID(parent
->id
));
351 * Because chunks are appended at the end and never deleted except by
352 * the GC, we can search without taking the runtime's GC lock. We may
353 * miss a matching shape added by another thread, and make a duplicate
354 * one, but that is an unlikely, therefore small, cost. The property
355 * tree has extremely low fan-out below its root in popular embeddings
356 * with real-world workloads.
358 * Patterns such as defining closures that capture a constructor's
359 * environment as getters or setters on the new object that is passed
360 * in as |this| can significantly increase fan-out below the property
361 * tree root -- see bug 335700 for details.
363 KidsPointer
*kidp
= &parent
->kids
;
364 if (!kidp
->isNull()) {
365 if (kidp
->isShape()) {
366 shape
= kidp
->toShape();
367 if (shape
->matches(&child
))
369 } else if (kidp
->isChunk()) {
370 KidsChunk
*chunk
= kidp
->toChunk();
374 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
375 shape
= chunk
->kids
[i
];
378 if (n
>= CHUNK_HASH_THRESHOLD
) {
380 * kidp->isChunk() was true, but if we're racing it
381 * may not be by this point. FIXME: thread "safety"
384 if (!kidp
->isHash()) {
385 chunk
= kidp
->toChunk();
387 KidsHash
*hash
= HashChunks(chunk
, n
);
389 JS_ReportOutOfMemory(cx
);
393 JS_LOCK_GC(cx
->runtime
);
394 if (kidp
->isHash()) {
398 // FIXME unsafe race with kidp->is/toChunk() above.
399 // But this is all going single-threaded soon...
401 chunk
= KidsChunk::destroy(cx
, chunk
);
404 goto locked_not_found
;
410 if (shape
->matches(&child
))
413 n
+= MAX_KIDS_PER_CHUNK
;
414 } while ((chunk
= chunk
->next
) != NULL
);
416 JS_LOCK_GC(cx
->runtime
);
417 shape
= *kidp
->toHash()->lookup(&child
);
420 goto locked_not_found
;
425 JS_LOCK_GC(cx
->runtime
);
428 shape
= newShape(cx
, true);
432 new (shape
) Shape(child
.id
, child
.rawGetter
, child
.rawSetter
, child
.slot
, child
.attrs
,
433 child
.flags
, child
.shortid
, js_GenerateShape(cx
, true));
435 if (!insertChild(cx
, parent
, shape
))
439 JS_UNLOCK_GC(cx
->runtime
);
446 KidsPointer::checkConsistency(const Shape
*aKid
) const
449 JS_ASSERT(toShape() == aKid
);
450 } else if (isChunk()) {
452 for (KidsChunk
*chunk
= toChunk(); chunk
; chunk
= chunk
->next
) {
453 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
454 if (!chunk
->kids
[i
]) {
455 JS_ASSERT(!chunk
->next
);
456 for (uintN j
= i
+ 1; j
< MAX_KIDS_PER_CHUNK
; j
++)
457 JS_ASSERT(!chunk
->kids
[j
]);
460 if (chunk
->kids
[i
] == aKid
) {
469 KidsHash
*hash
= toHash();
470 KidsHash::Ptr ptr
= hash
->lookup(aKid
);
471 JS_ASSERT(*ptr
== aKid
);
476 Shape::dump(JSContext
*cx
, FILE *fp
) const
478 JS_ASSERT(!JSID_IS_VOID(id
));
480 if (JSID_IS_INT(id
)) {
481 fprintf(fp
, "[%ld]", (long) JSID_TO_INT(id
));
484 if (JSID_IS_ATOM(id
)) {
485 str
= JSID_TO_ATOM(id
);
487 JS_ASSERT(JSID_IS_OBJECT(id
));
488 JSString
*s
= js_ValueToString(cx
, IdToValue(id
));
489 fputs("object ", fp
);
490 str
= s
? s
->ensureLinear(cx
) : NULL
;
493 fputs("<error>", fp
);
495 FileEscapedString(fp
, str
, '"');
498 fprintf(fp
, " g/s %p/%p slot %u attrs %x ",
499 JS_FUNC_TO_DATA_PTR(void *, rawGetter
),
500 JS_FUNC_TO_DATA_PTR(void *, rawSetter
),
505 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(" " #display + first, fp), first = 0
506 DUMP_ATTR(ENUMERATE
, enumerate
);
507 DUMP_ATTR(READONLY
, readonly
);
508 DUMP_ATTR(PERMANENT
, permanent
);
509 DUMP_ATTR(GETTER
, getter
);
510 DUMP_ATTR(SETTER
, setter
);
511 DUMP_ATTR(SHARED
, shared
);
516 fprintf(fp
, "flags %x ", flags
);
520 #define DUMP_FLAG(name, display) if (flags & name) fputs(" " #display + first, fp), first = 0
521 DUMP_FLAG(ALIAS
, alias
);
522 DUMP_FLAG(HAS_SHORTID
, has_shortid
);
523 DUMP_FLAG(METHOD
, method
);
524 DUMP_FLAG(MARK
, mark
);
525 DUMP_FLAG(SHAPE_REGEN
, shape_regen
);
526 DUMP_FLAG(IN_DICTIONARY
, in_dictionary
);
531 fprintf(fp
, "shortid %d\n", shortid
);
535 MeterKidCount(JSBasicStats
*bs
, uintN nkids
)
537 JS_BASIC_STATS_ACCUM(bs
, nkids
);
541 js::PropertyTree::meter(JSBasicStats
*bs
, Shape
*node
)
544 const KidsPointer
&kids
= node
->kids
;
545 if (!kids
.isNull()) {
546 if (kids
.isShape()) {
547 meter(bs
, kids
.toShape());
549 } else if (kids
.isChunk()) {
550 for (KidsChunk
*chunk
= kids
.toChunk(); chunk
; chunk
= chunk
->next
) {
551 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
552 Shape
*kid
= chunk
->kids
[i
];
560 const KidsHash
&hash
= *kids
.toHash();
561 for (KidsHash::Range range
= hash
.all(); !range
.empty(); range
.popFront()) {
562 Shape
*kid
= range
.front();
570 MeterKidCount(bs
, nkids
);
574 Shape::dumpSubtree(JSContext
*cx
, int level
, FILE *fp
) const
577 JS_ASSERT(level
== 0);
578 JS_ASSERT(JSID_IS_EMPTY(id
));
579 fprintf(fp
, "class %s emptyShape %u\n", clasp
->name
, shape
);
581 fprintf(fp
, "%*sid ", level
, "");
585 if (!kids
.isNull()) {
587 if (kids
.isShape()) {
588 Shape
*kid
= kids
.toShape();
589 JS_ASSERT(kid
->parent
== this);
590 kid
->dumpSubtree(cx
, level
, fp
);
591 } else if (kids
.isChunk()) {
592 KidsChunk
*chunk
= kids
.toChunk();
594 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
595 Shape
*kid
= chunk
->kids
[i
];
598 JS_ASSERT(kid
->parent
== this);
599 kid
->dumpSubtree(cx
, level
, fp
);
601 } while ((chunk
= chunk
->next
) != NULL
);
603 const KidsHash
&hash
= *kids
.toHash();
604 for (KidsHash::Range range
= hash
.all(); !range
.empty(); range
.popFront()) {
605 Shape
*kid
= range
.front();
607 JS_ASSERT(kid
->parent
== this);
608 kid
->dumpSubtree(cx
, level
, fp
);
616 JS_ALWAYS_INLINE
void
617 js::PropertyTree::orphanKids(JSContext
*cx
, Shape
*shape
)
619 KidsPointer
*kidp
= &shape
->kids
;
621 JS_ASSERT(!kidp
->isNull());
624 * Note that JS_PROPERTY_TREE(cx).removeChild(cx, shape) precedes the call
625 * to orphanKids in sweepShapes, below. Therefore the grandparent must have
626 * either no kids left, or else space in chunks or a hash for more than one
629 JS_ASSERT_IF(shape
->parent
, !shape
->parent
->kids
.isShape());
631 if (kidp
->isShape()) {
632 Shape
*kid
= kidp
->toShape();
634 if (!JSID_IS_VOID(kid
->id
)) {
635 JS_ASSERT(kid
->parent
== shape
);
638 } else if (kidp
->isChunk()) {
639 KidsChunk
*chunk
= kidp
->toChunk();
642 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
643 Shape
*kid
= chunk
->kids
[i
];
647 if (!JSID_IS_VOID(kid
->id
)) {
648 JS_ASSERT(kid
->parent
== shape
);
652 } while ((chunk
= KidsChunk::destroy(cx
, chunk
)) != NULL
);
654 KidsHash
*hash
= kidp
->toHash();
656 for (KidsHash::Range range
= hash
->all(); !range
.empty(); range
.popFront()) {
657 Shape
*kid
= range
.front();
658 if (!JSID_IS_VOID(kid
->id
)) {
659 JS_ASSERT(kid
->parent
== shape
);
672 js::PropertyTree::sweepShapes(JSContext
*cx
)
676 uint32 livePropCapacity
= 0, totalLiveCount
= 0;
679 if (const char *filename
= cx
->runtime
->propTreeStatFilename
)
680 logfp
= fopen(filename
, "w");
684 JS_BASIC_STATS_INIT(&bs
);
688 typedef JSRuntime::EmptyShapeSet HS
;
690 HS
&h
= cx
->runtime
->emptyShapes
;
692 MeterKidCount(&bs
, empties
);
693 for (HS::Range r
= h
.all(); !r
.empty(); r
.popFront())
694 meter(&bs
, r
.front());
697 double props
= cx
->runtime
->liveObjectPropsPreSweep
;
698 double nodes
= cx
->runtime
->livePropTreeNodes
;
699 double dicts
= cx
->runtime
->liveDictModeNodes
;
701 /* Empty scope nodes are never hashed, so subtract them from nodes. */
702 JS_ASSERT(nodes
- dicts
== bs
.sum
);
706 double mean
= JS_MeanAndStdDevBS(&bs
, &sigma
);
709 "props %g nodes %g (dicts %g) beta %g meankids %g sigma %g max %u\n",
710 props
, nodes
, dicts
, nodes
/ props
, mean
, sigma
, bs
.max
);
712 JS_DumpHistogram(&bs
, logfp
);
717 * Sweep the heap clean of all unmarked nodes. Here we will find nodes
718 * already GC'ed from the root ply, but we will avoid re-orphaning their
719 * kids, because the kids member will already be null.
721 JSArena
**ap
= &JS_PROPERTY_TREE(cx
).arenaPool
.first
.next
;
722 while (JSArena
*a
= *ap
) {
723 Shape
*limit
= (Shape
*) a
->avail
;
726 for (Shape
*shape
= (Shape
*) a
->base
; shape
< limit
; shape
++) {
727 /* If the id is null, shape is already on the freelist. */
728 if (JSID_IS_VOID(shape
->id
))
732 * If the mark bit is set, shape is alive, so clear the mark bit
733 * and continue the while loop.
735 * Regenerate shape->shape if it hasn't already been refreshed
736 * during the mark phase, when live scopes' lastProp members are
737 * followed to update both scope->shape and lastProp->shape.
739 if (shape
->marked()) {
741 if (cx
->runtime
->gcRegenShapes
) {
742 if (shape
->hasRegenFlag())
743 shape
->clearRegenFlag();
745 shape
->shape
= js_RegenerateShapeForGC(cx
);
752 if ((shape
->flags
& Shape::SHARED_EMPTY
) &&
753 cx
->runtime
->meterEmptyShapes()) {
754 cx
->runtime
->emptyShapes
.remove((EmptyShape
*) shape
);
758 if (shape
->inDictionary()) {
759 JS_RUNTIME_UNMETER(cx
->runtime
, liveDictModeNodes
);
762 * Here, shape is garbage to collect, but its parent might not
763 * be, so we may have to remove it from its parent's kids hash,
764 * chunk list, or kid singleton pointer set.
766 * Without a separate mark-clearing pass, we can't tell whether
767 * shape->parent is live at this point, so we must remove shape
768 * if its parent member is non-null. A saving grace: if shape's
769 * parent is dead and swept by this point, shape->parent will
770 * be null -- in the next paragraph, we null all of a property
771 * tree node's kids' parent links when sweeping that node.
774 JS_PROPERTY_TREE(cx
).removeChild(cx
, shape
);
776 if (!shape
->kids
.isNull())
777 orphanKids(cx
, shape
);
781 * Note that Shape::insertFree nulls shape->id so we know that
782 * shape is on the freelist.
784 shape
->freeTable(cx
);
785 shape
->insertFree(&JS_PROPERTY_TREE(cx
).freeList
);
786 JS_RUNTIME_UNMETER(cx
->runtime
, livePropTreeNodes
);
789 /* If a contains no live properties, return it to the malloc heap. */
790 if (liveCount
== 0) {
791 for (Shape
*shape
= (Shape
*) a
->base
; shape
< limit
; shape
++)
793 JS_ARENA_DESTROY(&JS_PROPERTY_TREE(cx
).arenaPool
, a
, ap
);
796 livePropCapacity
+= limit
- (Shape
*) a
->base
;
797 totalLiveCount
+= liveCount
;
806 "\nProperty tree stats for gcNumber %lu\n",
807 (unsigned long) cx
->runtime
->gcNumber
);
809 fprintf(logfp
, "arenautil %g%%\n",
810 (totalLiveCount
&& livePropCapacity
)
811 ? (totalLiveCount
* 100.0) / livePropCapacity
814 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
817 "Scope search stats:\n"
819 " hits: %6u %5.2f%% of searches\n"
820 " misses: %6u %5.2f%%\n"
821 " hashes: %6u %5.2f%%\n"
822 " hashHits: %6u %5.2f%% (%5.2f%% of hashes)\n"
823 " hashMisses: %6u %5.2f%% (%5.2f%%)\n"
824 " steps: %6u %5.2f%% (%5.2f%%)\n"
825 " stepHits: %6u %5.2f%% (%5.2f%%)\n"
826 " stepMisses: %6u %5.2f%% (%5.2f%%)\n"
827 " initSearches: %6u\n"
828 " changeSearches: %6u\n"
829 " tableAllocFails: %6u\n"
830 " toDictFails: %6u\n"
831 " wrapWatchFails: %6u\n"
835 " redundantPuts: %6u\n"
838 " changeFails: %6u\n"
842 " removeFrees: %6u\n"
843 " uselessRemoves: %6u\n"
845 js_scope_stats
.searches
,
846 js_scope_stats
.hits
, RATE(hits
, searches
),
847 js_scope_stats
.misses
, RATE(misses
, searches
),
848 js_scope_stats
.hashes
, RATE(hashes
, searches
),
849 js_scope_stats
.hashHits
, RATE(hashHits
, searches
), RATE(hashHits
, hashes
),
850 js_scope_stats
.hashMisses
, RATE(hashMisses
, searches
), RATE(hashMisses
, hashes
),
851 js_scope_stats
.steps
, RATE(steps
, searches
), RATE(steps
, hashes
),
852 js_scope_stats
.stepHits
, RATE(stepHits
, searches
), RATE(stepHits
, hashes
),
853 js_scope_stats
.stepMisses
, RATE(stepMisses
, searches
), RATE(stepMisses
, hashes
),
854 js_scope_stats
.initSearches
,
855 js_scope_stats
.changeSearches
,
856 js_scope_stats
.tableAllocFails
,
857 js_scope_stats
.toDictFails
,
858 js_scope_stats
.wrapWatchFails
,
860 js_scope_stats
.addFails
,
862 js_scope_stats
.redundantPuts
,
863 js_scope_stats
.putFails
,
864 js_scope_stats
.changes
,
865 js_scope_stats
.changeFails
,
866 js_scope_stats
.compresses
,
867 js_scope_stats
.grows
,
868 js_scope_stats
.removes
,
869 js_scope_stats
.removeFrees
,
870 js_scope_stats
.uselessRemoves
,
871 js_scope_stats
.shrinks
);
878 if (const char *filename
= cx
->runtime
->propTreeDumpFilename
) {
880 JS_snprintf(pathname
, sizeof pathname
, "%s.%lu",
881 filename
, (unsigned long)cx
->runtime
->gcNumber
);
882 FILE *dumpfp
= fopen(pathname
, "w");
884 typedef JSRuntime::EmptyShapeSet HS
;
886 HS
&h
= cx
->runtime
->emptyShapes
;
887 for (HS::Range r
= h
.all(); !r
.empty(); r
.popFront()) {
888 Shape
*empty
= r
.front();
889 empty
->dumpSubtree(cx
, 0, dumpfp
);
900 js::PropertyTree::unmarkShapes(JSContext
*cx
)
902 JSArena
**ap
= &JS_PROPERTY_TREE(cx
).arenaPool
.first
.next
;
903 while (JSArena
*a
= *ap
) {
904 Shape
*limit
= (Shape
*) a
->avail
;
906 for (Shape
*shape
= (Shape
*) a
->base
; shape
< limit
; shape
++) {
907 /* If the id is null, shape is already on the freelist. */
908 if (JSID_IS_VOID(shape
->id
))