Bug 559408: Arena pool macros to methods. (r=gal)
[mozilla-central.git] / js / src / jspropertytree.cpp
blob0dd018c5b9115864904bdd5e35c8a71998281ec2
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 "jstypes.h"
41 #include "jsarena.h"
42 #include "jsdhash.h"
43 #include "jsprf.h"
44 #include "jsapi.h"
45 #include "jscntxt.h"
46 #include "jsgc.h"
47 #include "jspropertytree.h"
48 #include "jsscope.h"
50 #include "jsscopeinlines.h"
52 using namespace js;
54 struct PropertyRootKey
56 const JSScopeProperty *firstProp;
57 uint32 emptyShape;
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;
72 uint32 emptyShape;
73 uint32 newEmptyShape;
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 = {
85 JS_DHashAllocTable,
86 JS_DHashFreeTable,
87 PropertyRootKey::hash,
88 PropertyRootEntry::match,
89 JS_DHashMoveEntryStub,
90 JS_DHashClearEntryStub,
91 JS_DHashFinalizeStub,
92 NULL
95 static JSDHashNumber
96 HashScopeProperty(JSDHashTable *table, const void *key)
98 const JSScopeProperty *sprop = (const JSScopeProperty *)key;
99 return sprop->hash();
102 static JSBool
103 MatchScopeProperty(JSDHashTable *table,
104 const JSDHashEntryHdr *hdr,
105 const void *key)
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 = {
115 JS_DHashAllocTable,
116 JS_DHashFreeTable,
117 HashScopeProperty,
118 MatchScopeProperty,
119 JS_DHashMoveEntryStub,
120 JS_DHashClearEntryStub,
121 JS_DHashFinalizeStub,
122 NULL
125 bool
126 PropertyTree::init()
128 if (!JS_DHashTableInit(&hash, &PropertyRootHashOps, NULL,
129 sizeof(PropertyRootEntry), JS_DHASH_MIN_SIZE)) {
130 hash.ops = NULL;
131 return false;
133 arenaPool.init("properties", 256 * sizeof(JSScopeProperty), sizeof(void *), NULL);
134 emptyShapeChanges = 0;
135 return true;
138 void
139 PropertyTree::finish()
141 if (hash.ops) {
142 JS_DHashTableFinish(&hash);
143 hash.ops = NULL;
145 arenaPool.finish();
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.
152 JSScopeProperty *
153 PropertyTree::newScopeProperty(JSContext *cx, bool gcLocked)
155 JSScopeProperty *sprop;
157 if (!gcLocked)
158 JS_LOCK_GC(cx->runtime);
159 sprop = freeList;
160 if (sprop) {
161 sprop->removeFree();
162 } else {
163 arenaPool.allocateCast<JSScopeProperty *>(sprop, sizeof *sprop);
164 if (!sprop) {
165 JS_UNLOCK_GC(cx->runtime);
166 JS_ReportOutOfMemory(cx);
167 return NULL;
170 if (!gcLocked)
171 JS_UNLOCK_GC(cx->runtime);
173 JS_RUNTIME_METER(cx->runtime, livePropTreeNodes);
174 JS_RUNTIME_METER(cx->runtime, totalPropTreeNodes);
175 return sprop;
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];
189 JSDHashTable *table;
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);
203 if (!chunk) {
204 JS_UNLOCK_GC(cx->runtime);
205 JS_ReportOutOfMemory(cx);
206 return NULL;
208 JS_ASSERT(((jsuword)chunk & CHUNKY_KIDS_TAG) == 0);
209 JS_RUNTIME_METER(cx->runtime, propTreeKidsChunks);
210 return chunk;
213 static PropTreeKidsChunk *
214 DestroyPropTreeKidsChunk(JSContext *cx, PropTreeKidsChunk *chunk)
216 JS_RUNTIME_UNMETER(cx->runtime, propTreeKidsChunks);
217 if (chunk->table)
218 JS_DHashTableDestroy(chunk->table);
220 PropTreeKidsChunk *nextChunk = chunk->next;
221 js_free(chunk);
222 return nextChunk;
226 * NB: Called with cx->runtime->gcLock held, always.
227 * On failure, return null after unlocking the GC and reporting out of memory.
229 bool
230 PropertyTree::insertChild(JSContext *cx, JSScopeProperty *parent,
231 JSScopeProperty *child)
233 JS_ASSERT(parent);
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)) {
244 sprop = 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);
254 if (!chunk)
255 return false;
256 parent->kids = CHUNK_TO_KIDS(chunk);
257 chunk->kids[0] = sprop;
258 childp = &chunk->kids[1];
259 } else {
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);
266 if (!entry) {
267 JS_UNLOCK_GC(cx->runtime);
268 JS_ReportOutOfMemory(cx);
269 return false;
271 if (!entry->child) {
272 entry->child = child;
273 while (chunk->next)
274 chunk = chunk->next;
275 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
276 childp = &chunk->kids[i];
277 sprop = *childp;
278 if (!sprop)
279 goto insert;
281 chunkp = &chunk->next;
282 goto new_chunk;
286 do {
287 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
288 childp = &chunk->kids[i];
289 sprop = *childp;
290 if (!sprop)
291 goto insert;
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);
308 new_chunk:
309 chunk = NewPropTreeKidsChunk(cx);
310 if (!chunk)
311 return false;
312 *chunkp = chunk;
313 childp = &chunk->kids[0];
317 insert:
318 *childp = child;
319 child->parent = parent;
320 return true;
323 /* NB: Called with cx->runtime->gcLock held. */
324 void
325 PropertyTree::removeChild(JSContext *cx, JSScopeProperty *child)
327 uintN i, j;
328 JSPropertyTreeEntry *entry;
330 JSScopeProperty *parent = child->parent;
331 JS_ASSERT(parent);
332 JS_ASSERT(!JSVAL_IS_NULL(parent->id));
334 JSScopeProperty *kids = parent->kids;
335 if (!KIDS_IS_CHUNKY(kids)) {
336 JSScopeProperty *kid = kids;
337 if (kid == child)
338 parent->kids = NULL;
339 return;
342 PropTreeKidsChunk *list = KIDS_TO_CHUNK(kids);
343 PropTreeKidsChunk *chunk = list;
344 PropTreeKidsChunk **chunkp = &list;
346 JSDHashTable *table = chunk->table;
347 PropTreeKidsChunk *freeChunk = NULL;
349 do {
350 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
351 if (chunk->kids[i] == child) {
352 PropTreeKidsChunk *lastChunk = chunk;
353 if (!lastChunk->next) {
354 j = i + 1;
355 } else {
356 j = 0;
357 do {
358 chunkp = &lastChunk->next;
359 lastChunk = *chunkp;
360 } while (lastChunk->next);
362 for (; j < MAX_KIDS_PER_CHUNK; j++) {
363 if (!lastChunk->kids[j])
364 break;
366 --j;
367 if (chunk != lastChunk || j > i)
368 chunk->kids[i] = lastChunk->kids[j];
369 lastChunk->kids[j] = NULL;
370 if (j == 0) {
371 *chunkp = NULL;
372 if (!list)
373 parent->kids = NULL;
374 freeChunk = lastChunk;
376 goto out;
380 chunkp = &chunk->next;
381 } while ((chunk = *chunkp) != NULL);
383 out:
384 if (table) {
385 entry = (JSPropertyTreeEntry *)
386 JS_DHashTableOperate(table, child, JS_DHASH_LOOKUP);
388 if (entry->child == child)
389 JS_DHashTableRawRemove(table, &entry->hdr);
391 if (freeChunk)
392 DestroyPropTreeKidsChunk(cx, freeChunk);
395 void
396 PropertyTree::emptyShapeChange(uint32 oldEmptyShape, uint32 newEmptyShape)
398 if (oldEmptyShape == newEmptyShape)
399 return;
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;
407 rent++;
410 ++emptyShapeChanges;
413 static JSDHashTable *
414 HashChunks(PropTreeKidsChunk *chunk, uintN n)
416 JSDHashTable *table;
417 uintN i;
418 JSScopeProperty *sprop;
419 JSPropertyTreeEntry *entry;
421 table = JS_NewDHashTable(&PropertyTreeHashOps, NULL,
422 sizeof(JSPropertyTreeEntry),
423 JS_DHASH_DEFAULT_CAPACITY(n + 1));
424 if (!table)
425 return NULL;
426 do {
427 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
428 sprop = chunk->kids[i];
429 if (!sprop)
430 break;
431 entry = (JSPropertyTreeEntry *)
432 JS_DHashTableOperate(table, sprop, JS_DHASH_ADD);
433 entry->child = sprop;
435 } while ((chunk = chunk->next) != NULL);
436 return table;
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.
447 JSScopeProperty *
448 PropertyTree::getChild(JSContext *cx, JSScopeProperty *parent, uint32 shape,
449 const JSScopeProperty &child)
451 PropertyRootEntry *rent;
452 JSScopeProperty *sprop;
454 if (!parent) {
455 PropertyRootKey rkey(&child, shape);
457 JS_LOCK_GC(cx->runtime);
458 rent = (PropertyRootEntry *) JS_DHashTableOperate(&hash, &rkey, JS_DHASH_ADD);
459 if (!rent) {
460 JS_UNLOCK_GC(cx->runtime);
461 JS_ReportOutOfMemory(cx);
462 return NULL;
465 sprop = rent->firstProp;
466 if (sprop)
467 goto out;
468 } else {
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.
484 rent = NULL;
485 sprop = parent->kids;
486 if (sprop) {
487 if (!KIDS_IS_CHUNKY(sprop)) {
488 if (sprop->matches(&child))
489 return sprop;
490 } else {
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;
498 if (sprop)
499 goto out;
500 goto locked_not_found;
503 uintN n = 0;
504 do {
505 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
506 sprop = chunk->kids[i];
507 if (!sprop) {
508 n += i;
509 if (n >= CHUNK_HASH_THRESHOLD) {
510 chunk = KIDS_TO_CHUNK(parent->kids);
511 if (!chunk->table) {
512 JSDHashTable *table = HashChunks(chunk, n);
513 if (!table) {
514 JS_ReportOutOfMemory(cx);
515 return NULL;
518 JS_LOCK_GC(cx->runtime);
519 if (chunk->table)
520 JS_DHashTableDestroy(table);
521 else
522 chunk->table = table;
523 goto locked_not_found;
526 goto not_found;
529 if (sprop->matches(&child))
530 return sprop;
532 n += MAX_KIDS_PER_CHUNK;
533 } while ((chunk = chunk->next) != NULL);
537 not_found:
538 JS_LOCK_GC(cx->runtime);
541 locked_not_found:
542 sprop = newScopeProperty(cx, true);
543 if (!sprop)
544 return NULL;
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);
551 if (!parent) {
552 rent->firstProp = sprop;
553 rent->emptyShape = shape;
554 rent->newEmptyShape = 0;
555 } else {
556 if (!PropertyTree::insertChild(cx, parent, sprop))
557 return NULL;
560 out:
561 JS_UNLOCK_GC(cx->runtime);
562 return sprop;
565 #ifdef DEBUG
567 static void
568 MeterKidCount(JSBasicStats *bs, uintN nkids)
570 JS_BASIC_STATS_ACCUM(bs, nkids);
571 bs->hist[JS_MIN(nkids, 10)]++;
574 static void
575 MeterPropertyTree(JSBasicStats *bs, JSScopeProperty *node)
577 uintN i, nkids;
578 JSScopeProperty *kids, *kid;
579 PropTreeKidsChunk *chunk;
581 nkids = 0;
582 kids = node->kids;
583 if (kids) {
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];
588 if (!kid)
589 break;
590 MeterPropertyTree(bs, kid);
591 nkids++;
594 } else {
595 MeterPropertyTree(bs, kids);
596 nkids = 1;
600 MeterKidCount(bs, nkids);
603 static JSDHashOperator
604 js_MeterPropertyTree(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
605 void *arg)
607 PropertyRootEntry *rent = (PropertyRootEntry *)hdr;
608 JSBasicStats *bs = (JSBasicStats *)arg;
610 MeterPropertyTree(bs, rent->firstProp);
611 return JS_DHASH_NEXT;
614 void
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));
622 } else {
623 JSString *str;
624 if (JSVAL_IS_STRING(idval)) {
625 str = JSVAL_TO_STRING(idval);
626 } else {
627 JS_ASSERT(JSVAL_IS_OBJECT(idval));
628 str = js_ValueToString(cx, idval);
629 fputs("object ", fp);
631 if (!str)
632 fputs("<error>", fp);
633 else
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),
640 slot, attrs);
641 if (attrs) {
642 int first = 1;
643 fputs("(", fp);
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);
651 #undef DUMP_ATTR
652 fputs(") ", fp);
655 fprintf(fp, "flags %x ", flags);
656 if (flags) {
657 int first = 1;
658 fputs("(", fp);
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);
666 #undef DUMP_FLAG
667 fputs(") ", fp);
670 fprintf(fp, "shortid %d\n", shortid);
673 void
674 JSScopeProperty::dumpSubtree(JSContext *cx, int level, FILE *fp)
676 fprintf(fp, "%*sid ", level, "");
677 dump(cx, fp);
679 if (kids) {
680 ++level;
681 if (KIDS_IS_CHUNKY(kids)) {
682 PropTreeKidsChunk *chunk = KIDS_TO_CHUNK(kids);
683 do {
684 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
685 JSScopeProperty *kid = chunk->kids[i];
686 if (!kid)
687 break;
688 JS_ASSERT(kid->parent == this);
689 kid->dumpSubtree(cx, level, fp);
691 } while ((chunk = chunk->next) != NULL);
692 } else {
693 JSScopeProperty *kid = kids;
694 JS_ASSERT(kid->parent == this);
695 kid->dumpSubtree(cx, level, fp);
700 #endif /* DEBUG */
702 static void
703 OrphanNodeKids(JSContext *cx, JSScopeProperty *sprop)
705 JSScopeProperty *kids = sprop->kids;
707 JS_ASSERT(kids);
708 sprop->kids = NULL;
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);
720 do {
721 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
722 JSScopeProperty *kid = chunk->kids[i];
723 if (!kid)
724 break;
726 if (!JSVAL_IS_NULL(kid->id)) {
727 JS_ASSERT(kid->parent == sprop);
728 kid->parent = NULL;
731 } while ((chunk = DestroyPropTreeKidsChunk(cx, chunk)) != NULL);
732 } else {
733 JSScopeProperty *kid = kids;
735 if (!JSVAL_IS_NULL(kid->id)) {
736 JS_ASSERT(kid->parent == sprop);
737 kid->parent = NULL;
742 JSDHashOperator
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()) {
750 if (sprop->kids)
751 OrphanNodeKids((JSContext *)arg, sprop);
752 return JS_DHASH_REMOVE;
754 return JS_DHASH_NEXT;
757 void
758 js::SweepScopeProperties(JSContext *cx)
760 #ifdef DEBUG
761 JSBasicStats bs;
762 uint32 livePropCapacity = 0, totalLiveCount = 0;
763 static FILE *logfp;
764 if (!logfp) {
765 if (const char *filename = getenv("JS_PROPTREE_STATFILE"))
766 logfp = fopen(filename, "w");
769 if (logfp) {
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);
781 fprintf(logfp,
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);
787 #endif
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;
818 } else {
819 PropertyRootEntry *rent = (PropertyRootEntry *) oldHash.entryStore;
820 PropertyRootEntry *rend = rent + tableSize;
822 while (rent < rend) {
823 if (rent->firstProp) {
824 uint32 emptyShape = rent->newEmptyShape;
825 if (emptyShape == 0)
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;
836 rent++;
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();
855 uintN liveCount = 0;
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))
860 continue;
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()) {
871 sprop->clearMark();
872 if (cx->runtime->gcRegenShapes) {
873 if (sprop->hasRegenFlag())
874 sprop->clearRegenFlag();
875 else
876 sprop->shape = js_RegenerateShapeForGC(cx);
878 liveCount++;
879 continue;
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.
895 if (sprop->parent)
896 JS_PROPERTY_TREE(cx).removeChild(cx, sprop);
898 if (sprop->kids)
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++)
913 sprop->removeFree();
914 a = arenaPool.destroy(a);
915 first->munge(a);
916 } else {
917 #ifdef DEBUG
918 livePropCapacity += limit - (JSScopeProperty *) a->getBase();
919 totalLiveCount += liveCount;
920 #endif
921 first->munge(a->getNext());
922 a = a->getNext();
926 #ifdef DEBUG
927 if (logfp) {
928 fprintf(logfp,
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
935 : 0.0);
937 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
939 fprintf(logfp,
940 "Scope search stats:\n"
941 " searches: %6u\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"
949 " toDictFails %6u\n"
950 " wrapWatchFails %6u\n"
951 " adds: %6u\n"
952 " addFails: %6u\n"
953 " puts: %6u\n"
954 " redundantPuts: %6u\n"
955 " putFails: %6u\n"
956 " changes: %6u\n"
957 " changeFails: %6u\n"
958 " compresses: %6u\n"
959 " grows: %6u\n"
960 " removes: %6u\n"
961 " removeFrees: %6u\n"
962 " uselessRemoves: %6u\n"
963 " shrinks: %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,
976 js_scope_stats.adds,
977 js_scope_stats.addFails,
978 js_scope_stats.puts,
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);
990 #undef RATE
992 fflush(logfp);
995 if (const char *filename = getenv("JS_PROPTREE_DUMPFILE")) {
996 char pathname[1024];
997 JS_snprintf(pathname, sizeof pathname, "%s.%lu",
998 filename, (unsigned long)cx->runtime->gcNumber);
999 FILE *dumpfp = fopen(pathname, "w");
1000 if (dumpfp) {
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);
1009 rent++;
1011 fclose(dumpfp);
1014 #endif /* DEBUG */