Bug 617935: Check string lengths using StringBuffer. (r=lw)
[mozilla-central.git] / js / src / jspropertytree.cpp
blob43d1ddc94b945e7cf9876e756c065bef3f8ef30c
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
14 * License.
16 * The Original Code is the Mozilla SpiderMonkey property tree implementation
18 * The Initial Developer of the Original Code is
19 * Mozilla Foundation
20 * Portions created by the Initial Developer are Copyright (C) 2002-2010
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
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 ***** */
40 #include <new>
42 #include "jstypes.h"
43 #include "jsarena.h"
44 #include "jsprf.h"
45 #include "jsapi.h"
46 #include "jscntxt.h"
47 #include "jsgc.h"
48 #include "jspropertytree.h"
49 #include "jsscope.h"
51 #include "jsobjinlines.h"
52 #include "jsscopeinlines.h"
54 using namespace js;
56 inline HashNumber
57 ShapeHasher::hash(const Lookup l)
59 return l->hash();
62 inline bool
63 ShapeHasher::match(const Key k, const Lookup l)
65 return l->matches(k);
68 bool
69 PropertyTree::init()
71 JS_InitArenaPool(&arenaPool, "properties",
72 256 * sizeof(Shape), sizeof(void *), NULL);
73 return true;
76 void
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.
86 Shape *
87 PropertyTree::newShape(JSContext *cx, bool gcLocked)
89 Shape *shape;
91 if (!gcLocked)
92 JS_LOCK_GC(cx->runtime);
93 shape = freeList;
94 if (shape) {
95 shape->removeFree();
96 } else {
97 JS_ARENA_ALLOCATE_CAST(shape, Shape *, &arenaPool, sizeof(Shape));
98 if (!shape) {
99 JS_UNLOCK_GC(cx->runtime);
100 JS_ReportOutOfMemory(cx);
101 return NULL;
104 if (!gcLocked)
105 JS_UNLOCK_GC(cx->runtime);
107 JS_RUNTIME_METER(cx->runtime, livePropTreeNodes);
108 JS_RUNTIME_METER(cx->runtime, totalPropTreeNodes);
109 return shape;
113 * NB: Called with cx->runtime->gcLock held, always.
114 * On failure, return null after unlocking the GC and reporting out of memory.
116 KidsChunk *
117 KidsChunk::create(JSContext *cx)
119 KidsChunk *chunk;
121 chunk = (KidsChunk *) js_calloc(sizeof *chunk);
122 if (!chunk) {
123 JS_UNLOCK_GC(cx->runtime);
124 JS_ReportOutOfMemory(cx);
125 return NULL;
127 JS_RUNTIME_METER(cx->runtime, propTreeKidsChunks);
128 return chunk;
131 KidsChunk *
132 KidsChunk::destroy(JSContext *cx, KidsChunk *chunk)
134 JS_RUNTIME_UNMETER(cx->runtime, propTreeKidsChunks);
136 KidsChunk *nextChunk = chunk->next;
137 js_free(chunk);
138 return nextChunk;
142 * NB: Called with cx->runtime->gcLock held, always.
143 * On failure, return false after unlocking the GC and reporting out of memory.
145 bool
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);
159 return true;
162 Shape *shape;
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);
176 if (!chunk)
177 return false;
178 parent->kids.setChunk(chunk);
179 chunk->kids[0] = shape;
180 chunk->kids[1] = child;
181 return true;
184 if (kidp->isChunk()) {
185 KidsChunk **chunkp;
186 KidsChunk *chunk = kidp->toChunk();
188 do {
189 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
190 shape = chunk->kids[i];
191 if (!shape) {
192 chunk->kids[i] = child;
193 return true;
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
202 * which to insert.
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);
212 if (!chunk)
213 return false;
214 *chunkp = chunk;
215 chunk->kids[0] = child;
216 return true;
219 KidsHash *hash = kidp->toHash();
220 KidsHash::AddPtr addPtr = hash->lookupForAdd(child);
221 if (!addPtr) {
222 if (!hash->add(addPtr, child)) {
223 JS_UNLOCK_GC(cx->runtime);
224 JS_ReportOutOfMemory(cx);
225 return false;
227 } else {
228 // FIXME ignore duplicate child case here, going thread-local soon!
230 return true;
233 /* NB: Called with cx->runtime->gcLock held. */
234 void
235 PropertyTree::removeChild(JSContext *cx, Shape *child)
237 JS_ASSERT(!child->inDictionary());
239 Shape *parent = child->parent;
240 JS_ASSERT(parent);
241 JS_ASSERT(!JSID_IS_VOID(parent->id));
243 KidsPointer *kidp = &parent->kids;
244 if (kidp->isShape()) {
245 Shape *kid = kidp->toShape();
246 if (kid == child)
247 parent->kids.setNull();
248 return;
251 if (kidp->isChunk()) {
252 KidsChunk *list = kidp->toChunk();
253 KidsChunk *chunk = list;
254 KidsChunk **chunkp = &list;
256 do {
257 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
258 if (chunk->kids[i] == child) {
259 KidsChunk *lastChunk = chunk;
261 uintN j;
262 if (!lastChunk->next) {
263 j = i + 1;
264 } else {
265 j = 0;
266 do {
267 chunkp = &lastChunk->next;
268 lastChunk = *chunkp;
269 } while (lastChunk->next);
271 for (; j < MAX_KIDS_PER_CHUNK; j++) {
272 if (!lastChunk->kids[j])
273 break;
275 --j;
277 if (chunk != lastChunk || j > i)
278 chunk->kids[i] = lastChunk->kids[j];
279 lastChunk->kids[j] = NULL;
280 if (j == 0) {
281 *chunkp = NULL;
282 if (!list)
283 parent->kids.setNull();
284 KidsChunk::destroy(cx, lastChunk);
286 return;
290 chunkp = &chunk->next;
291 } while ((chunk = *chunkp) != NULL);
292 return;
295 kidp->toHash()->remove(child);
298 static KidsHash *
299 HashChunks(KidsChunk *chunk, uintN n)
301 void *mem = js_malloc(sizeof(KidsHash));
302 if (!mem)
303 return NULL;
305 KidsHash *hash = new (mem) KidsHash();
306 if (!hash->init(n)) {
307 js_free(hash);
308 return NULL;
311 do {
312 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
313 Shape *shape = chunk->kids[i];
314 if (!shape)
315 break;
316 KidsHash::AddPtr addPtr = hash->lookupForAdd(shape);
317 if (!addPtr) {
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));
323 } else {
325 * Duplicate child case, we don't handle this race,
326 * multi-threaded shapes are going away...
330 } while ((chunk = chunk->next) != NULL);
331 return hash;
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.
342 Shape *
343 PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child)
345 Shape *shape;
347 JS_ASSERT(parent);
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))
368 return shape;
369 } else if (kidp->isChunk()) {
370 KidsChunk *chunk = kidp->toChunk();
372 uintN n = 0;
373 do {
374 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
375 shape = chunk->kids[i];
376 if (!shape) {
377 n += 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"
382 * is for the birds!
384 if (!kidp->isHash()) {
385 chunk = kidp->toChunk();
387 KidsHash *hash = HashChunks(chunk, n);
388 if (!hash) {
389 JS_ReportOutOfMemory(cx);
390 return NULL;
393 JS_LOCK_GC(cx->runtime);
394 if (kidp->isHash()) {
395 hash->~KidsHash();
396 js_free(hash);
397 } else {
398 // FIXME unsafe race with kidp->is/toChunk() above.
399 // But this is all going single-threaded soon...
400 while (chunk)
401 chunk = KidsChunk::destroy(cx, chunk);
402 kidp->setHash(hash);
404 goto locked_not_found;
407 goto not_found;
410 if (shape->matches(&child))
411 return shape;
413 n += MAX_KIDS_PER_CHUNK;
414 } while ((chunk = chunk->next) != NULL);
415 } else {
416 JS_LOCK_GC(cx->runtime);
417 shape = *kidp->toHash()->lookup(&child);
418 if (shape)
419 goto out;
420 goto locked_not_found;
424 not_found:
425 JS_LOCK_GC(cx->runtime);
427 locked_not_found:
428 shape = newShape(cx, true);
429 if (!shape)
430 return NULL;
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))
436 return NULL;
438 out:
439 JS_UNLOCK_GC(cx->runtime);
440 return shape;
443 #ifdef DEBUG
445 void
446 KidsPointer::checkConsistency(const Shape *aKid) const
448 if (isShape()) {
449 JS_ASSERT(toShape() == aKid);
450 } else if (isChunk()) {
451 bool found = false;
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]);
458 break;
460 if (chunk->kids[i] == aKid) {
461 JS_ASSERT(!found);
462 found = true;
466 JS_ASSERT(found);
467 } else {
468 JS_ASSERT(isHash());
469 KidsHash *hash = toHash();
470 KidsHash::Ptr ptr = hash->lookup(aKid);
471 JS_ASSERT(*ptr == aKid);
475 void
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));
482 } else {
483 JSLinearString *str;
484 if (JSID_IS_ATOM(id)) {
485 str = JSID_TO_ATOM(id);
486 } else {
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;
492 if (!str)
493 fputs("<error>", fp);
494 else
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),
501 slot, attrs);
502 if (attrs) {
503 int first = 1;
504 fputs("(", fp);
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);
512 #undef DUMP_ATTR
513 fputs(") ", fp);
516 fprintf(fp, "flags %x ", flags);
517 if (flags) {
518 int first = 1;
519 fputs("(", fp);
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);
527 #undef DUMP_FLAG
528 fputs(") ", fp);
531 fprintf(fp, "shortid %d\n", shortid);
534 static void
535 MeterKidCount(JSBasicStats *bs, uintN nkids)
537 JS_BASIC_STATS_ACCUM(bs, nkids);
540 void
541 js::PropertyTree::meter(JSBasicStats *bs, Shape *node)
543 uintN nkids = 0;
544 const KidsPointer &kids = node->kids;
545 if (!kids.isNull()) {
546 if (kids.isShape()) {
547 meter(bs, kids.toShape());
548 nkids = 1;
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];
553 if (!kid)
554 break;
555 meter(bs, kid);
556 nkids++;
559 } else {
560 const KidsHash &hash = *kids.toHash();
561 for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
562 Shape *kid = range.front();
564 meter(bs, kid);
565 nkids++;
570 MeterKidCount(bs, nkids);
573 void
574 Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const
576 if (!parent) {
577 JS_ASSERT(level == 0);
578 JS_ASSERT(JSID_IS_EMPTY(id));
579 fprintf(fp, "class %s emptyShape %u\n", clasp->name, shape);
580 } else {
581 fprintf(fp, "%*sid ", level, "");
582 dump(cx, fp);
585 if (!kids.isNull()) {
586 ++level;
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();
593 do {
594 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
595 Shape *kid = chunk->kids[i];
596 if (!kid)
597 break;
598 JS_ASSERT(kid->parent == this);
599 kid->dumpSubtree(cx, level, fp);
601 } while ((chunk = chunk->next) != NULL);
602 } else {
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);
614 #endif /* DEBUG */
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
627 * kid.
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);
636 kid->parent = NULL;
638 } else if (kidp->isChunk()) {
639 KidsChunk *chunk = kidp->toChunk();
641 do {
642 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
643 Shape *kid = chunk->kids[i];
644 if (!kid)
645 break;
647 if (!JSID_IS_VOID(kid->id)) {
648 JS_ASSERT(kid->parent == shape);
649 kid->parent = NULL;
652 } while ((chunk = KidsChunk::destroy(cx, chunk)) != NULL);
653 } else {
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);
660 kid->parent = NULL;
664 hash->~KidsHash();
665 js_free(hash);
668 kidp->setNull();
671 void
672 js::PropertyTree::sweepShapes(JSContext *cx)
674 #ifdef DEBUG
675 JSBasicStats bs;
676 uint32 livePropCapacity = 0, totalLiveCount = 0;
677 static FILE *logfp;
678 if (!logfp) {
679 if (const char *filename = cx->runtime->propTreeStatFilename)
680 logfp = fopen(filename, "w");
683 if (logfp) {
684 JS_BASIC_STATS_INIT(&bs);
686 uint32 empties;
688 typedef JSRuntime::EmptyShapeSet HS;
690 HS &h = cx->runtime->emptyShapes;
691 empties = h.count();
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);
703 nodes -= empties;
705 double sigma;
706 double mean = JS_MeanAndStdDevBS(&bs, &sigma);
708 fprintf(logfp,
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);
714 #endif
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;
724 uintN liveCount = 0;
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))
729 continue;
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()) {
740 shape->clearMark();
741 if (cx->runtime->gcRegenShapes) {
742 if (shape->hasRegenFlag())
743 shape->clearRegenFlag();
744 else
745 shape->shape = js_RegenerateShapeForGC(cx);
747 liveCount++;
748 continue;
751 #ifdef DEBUG
752 if ((shape->flags & Shape::SHARED_EMPTY) &&
753 cx->runtime->meterEmptyShapes()) {
754 cx->runtime->emptyShapes.remove((EmptyShape *) shape);
756 #endif
758 if (shape->inDictionary()) {
759 JS_RUNTIME_UNMETER(cx->runtime, liveDictModeNodes);
760 } else {
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.
773 if (shape->parent)
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++)
792 shape->removeFree();
793 JS_ARENA_DESTROY(&JS_PROPERTY_TREE(cx).arenaPool, a, ap);
794 } else {
795 #ifdef DEBUG
796 livePropCapacity += limit - (Shape *) a->base;
797 totalLiveCount += liveCount;
798 #endif
799 ap = &a->next;
803 #ifdef DEBUG
804 if (logfp) {
805 fprintf(logfp,
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
812 : 0.0);
814 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
816 fprintf(logfp,
817 "Scope search stats:\n"
818 " searches: %6u\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"
832 " adds: %6u\n"
833 " addFails: %6u\n"
834 " puts: %6u\n"
835 " redundantPuts: %6u\n"
836 " putFails: %6u\n"
837 " changes: %6u\n"
838 " changeFails: %6u\n"
839 " compresses: %6u\n"
840 " grows: %6u\n"
841 " removes: %6u\n"
842 " removeFrees: %6u\n"
843 " uselessRemoves: %6u\n"
844 " shrinks: %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,
859 js_scope_stats.adds,
860 js_scope_stats.addFails,
861 js_scope_stats.puts,
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);
873 #undef RATE
875 fflush(logfp);
878 if (const char *filename = cx->runtime->propTreeDumpFilename) {
879 char pathname[1024];
880 JS_snprintf(pathname, sizeof pathname, "%s.%lu",
881 filename, (unsigned long)cx->runtime->gcNumber);
882 FILE *dumpfp = fopen(pathname, "w");
883 if (dumpfp) {
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);
890 putc('\n', dumpfp);
893 fclose(dumpfp);
896 #endif /* DEBUG */
899 void
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))
909 continue;
911 if (shape->marked())
912 shape->clearMark();
914 ap = &a->next;