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);
82 /* On failure, returns NULL. Does not report out of memory. */
84 PropertyTree::newShapeUnchecked()
92 JS_ARENA_ALLOCATE_CAST(shape, Shape *, &arenaPool, sizeof(Shape));
98 shape->compartment = compartment;
101 JS_COMPARTMENT_METER(compartment->livePropTreeNodes++);
102 JS_COMPARTMENT_METER(compartment->totalPropTreeNodes++);
107 PropertyTree::newShape(JSContext *cx)
109 Shape *shape = newShapeUnchecked();
111 JS_ReportOutOfMemory(cx);
116 HashChildren(Shape *kid1, Shape *kid2)
118 void *mem = js_malloc(sizeof(KidsHash));
122 KidsHash *hash = new (mem) KidsHash();
123 if (!hash->init(2)) {
128 KidsHash::AddPtr addPtr = hash->lookupForAdd(kid1);
129 JS_ALWAYS_TRUE(hash->add(addPtr, kid1));
131 addPtr = hash->lookupForAdd(kid2);
132 JS_ASSERT(!addPtr.found());
133 JS_ALWAYS_TRUE(hash->add(addPtr, kid2));
139 PropertyTree::insertChild(JSContext *cx, Shape *parent, Shape *child)
141 JS_ASSERT(!parent->inDictionary());
142 JS_ASSERT(!child->parent);
143 JS_ASSERT(!child->inDictionary());
144 JS_ASSERT(!JSID_IS_VOID(parent->id));
145 JS_ASSERT(!JSID_IS_VOID(child->id));
146 JS_ASSERT(cx->compartment == compartment);
147 JS_ASSERT(child->compartment == parent->compartment);
149 KidsPointer *kidp = &parent->kids;
151 if (kidp->isNull()) {
152 child->setParent(parent);
153 kidp->setShape(child);
157 if (kidp->isShape()) {
158 Shape *shape = kidp->toShape();
159 JS_ASSERT(shape != child);
160 JS_ASSERT(!shape->matches(child));
162 KidsHash *hash = HashChildren(shape, child);
164 JS_ReportOutOfMemory(cx);
168 child->setParent(parent);
172 KidsHash *hash = kidp->toHash();
173 KidsHash::AddPtr addPtr = hash->lookupForAdd(child);
174 JS_ASSERT(!addPtr.found());
175 if (!hash->add(addPtr, child)) {
176 JS_ReportOutOfMemory(cx);
179 child->setParent(parent);
184 PropertyTree::removeChild(Shape *child)
186 JS_ASSERT(!child->inDictionary());
188 Shape *parent = child->parent;
190 JS_ASSERT(!JSID_IS_VOID(parent->id));
192 KidsPointer *kidp = &parent->kids;
193 if (kidp->isShape()) {
194 Shape *kid = kidp->toShape();
196 parent->kids.setNull();
200 kidp->toHash()->remove(child);
204 PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child)
209 JS_ASSERT(!JSID_IS_VOID(parent->id));
212 * The property tree has extremely low fan-out below its root in
213 * popular embeddings with real-world workloads. Patterns such as
214 * defining closures that capture a constructor's environment as
215 * getters or setters on the new object that is passed in as
216 * |this| can significantly increase fan-out below the property
217 * tree root -- see bug 335700 for details.
219 KidsPointer *kidp = &parent->kids;
220 if (kidp->isShape()) {
221 shape = kidp->toShape();
222 if (shape->matches(&child))
224 } else if (kidp->isHash()) {
225 shape = *kidp->toHash()->lookup(&child);
229 /* If kidp->isNull(), we always insert. */
232 shape = newShape(cx);
236 new (shape) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs,
237 child.flags, child.shortid, js_GenerateShape(cx));
239 if (!insertChild(cx, parent, shape))
248 KidsPointer::checkConsistency(const Shape *aKid) const
251 JS_ASSERT(toShape() == aKid);
254 KidsHash *hash = toHash();
255 KidsHash::Ptr ptr = hash->lookup(aKid);
256 JS_ASSERT(*ptr == aKid);
261 Shape::dump(JSContext *cx, FILE *fp) const
263 JS_ASSERT(!JSID_IS_VOID(id));
265 if (JSID_IS_INT(id)) {
266 fprintf(fp, "[%ld]", (long) JSID_TO_INT(id));
267 } else if (JSID_IS_DEFAULT_XML_NAMESPACE(id)) {
268 fprintf(fp, "<default XML namespace>");
271 if (JSID_IS_ATOM(id)) {
272 str = JSID_TO_ATOM(id);
274 JS_ASSERT(JSID_IS_OBJECT(id));
275 JSString *s = js_ValueToString(cx, IdToValue(id));
276 fputs("object ", fp);
277 str = s ? s->ensureLinear(cx) : NULL;
280 fputs("<error>", fp);
282 FileEscapedString(fp, str, '"');
285 fprintf(fp, " g/s %p/%p slot %u attrs %x ",
286 JS_FUNC_TO_DATA_PTR(void *, rawGetter),
287 JS_FUNC_TO_DATA_PTR(void *, rawSetter),
292 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(" " #display + first, fp), first = 0
293 DUMP_ATTR(ENUMERATE, enumerate);
294 DUMP_ATTR(READONLY, readonly);
295 DUMP_ATTR(PERMANENT, permanent);
296 DUMP_ATTR(GETTER, getter);
297 DUMP_ATTR(SETTER, setter);
298 DUMP_ATTR(SHARED, shared);
303 fprintf(fp, "flags %x ", flags);
307 #define DUMP_FLAG(name, display) if (flags & name) fputs(" " #display + first, fp), first = 0
308 DUMP_FLAG(ALIAS, alias);
309 DUMP_FLAG(HAS_SHORTID, has_shortid);
310 DUMP_FLAG(METHOD, method);
311 DUMP_FLAG(MARK, mark);
312 DUMP_FLAG(SHAPE_REGEN, shape_regen);
313 DUMP_FLAG(IN_DICTIONARY, in_dictionary);
318 fprintf(fp, "shortid %d\n", shortid);
322 MeterKidCount(JSBasicStats *bs, uintN nkids)
324 JS_BASIC_STATS_ACCUM(bs, nkids);
328 js::PropertyTree::meter(JSBasicStats *bs, Shape *node)
331 const KidsPointer &kidp = node->kids;
332 if (kidp.isShape()) {
333 meter(bs, kidp.toShape());
335 } else if (kidp.isHash()) {
336 const KidsHash &hash = *kidp.toHash();
337 for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
338 Shape *kid = range.front();
345 MeterKidCount(bs, nkids);
349 Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const
352 JS_ASSERT(level == 0);
353 JS_ASSERT(JSID_IS_EMPTY(id));
354 fprintf(fp, "class %s emptyShape %u\n", clasp->name, shape);
356 fprintf(fp, "%*sid ", level, "");
360 if (!kids.isNull()) {
362 if (kids.isShape()) {
363 Shape *kid = kids.toShape();
364 JS_ASSERT(kid->parent == this);
365 kid->dumpSubtree(cx, level, fp);
367 const KidsHash &hash = *kids.toHash();
368 for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
369 Shape *kid = range.front();
371 JS_ASSERT(kid->parent == this);
372 kid->dumpSubtree(cx, level, fp);
380 JS_ALWAYS_INLINE void
381 js::PropertyTree::orphanChildren(Shape *shape)
383 KidsPointer *kidp = &shape->kids;
385 JS_ASSERT(!kidp->isNull());
387 if (kidp->isShape()) {
388 Shape *kid = kidp->toShape();
390 if (!JSID_IS_VOID(kid->id)) {
391 JS_ASSERT(kid->parent == shape);
395 KidsHash *hash = kidp->toHash();
397 for (KidsHash::Range range = hash->all(); !range.empty(); range.popFront()) {
398 Shape *kid = range.front();
399 if (!JSID_IS_VOID(kid->id)) {
400 JS_ASSERT(kid->parent == shape);
413 js::PropertyTree::sweepShapes(JSContext *cx)
415 JSRuntime *rt = compartment->rt;
419 uint32 livePropCapacity = 0, totalLiveCount = 0;
422 if (const char *filename = rt->propTreeStatFilename)
423 logfp = fopen(filename, "w");
427 JS_BASIC_STATS_INIT(&bs);
431 typedef JSCompartment::EmptyShapeSet HS;
433 HS &h = compartment->emptyShapes;
435 MeterKidCount(&bs, empties);
436 for (HS::Range r = h.all(); !r.empty(); r.popFront())
437 meter(&bs, r.front());
440 double props = rt->liveObjectPropsPreSweep;
441 double nodes = compartment->livePropTreeNodes;
442 double dicts = compartment->liveDictModeNodes;
444 /* Empty scope nodes are never hashed, so subtract them from nodes. */
445 JS_ASSERT(nodes - dicts == bs.sum);
449 double mean = JS_MeanAndStdDevBS(&bs, &sigma);
452 "props %g nodes %g (dicts %g) beta %g meankids %g sigma %g max %u\n",
453 props, nodes, dicts, nodes / props, mean, sigma, bs.max);
455 JS_DumpHistogram(&bs, logfp);
460 * Sweep the heap clean of all unmarked nodes. Here we will find nodes
461 * already GC'ed from the root ply, but we will avoid re-orphaning their
462 * kids, because the kids member will already be null.
464 JSArena **ap = &arenaPool.first.next;
465 while (JSArena *a = *ap) {
466 Shape *limit = (Shape *) a->avail;
469 for (Shape *shape = (Shape *) a->base; shape < limit; shape++) {
470 /* If the id is null, shape is already on the freelist. */
471 if (JSID_IS_VOID(shape->id))
475 * If the mark bit is set, shape is alive, so clear the mark bit
476 * and continue the while loop.
478 * Regenerate shape->shape if it hasn't already been refreshed
479 * during the mark phase, when live scopes' lastProp members are
480 * followed to update both scope->shape and lastProp->shape.
482 if (shape->marked()) {
484 if (rt->gcRegenShapes) {
485 if (shape->hasRegenFlag())
486 shape->clearRegenFlag();
488 shape->shape = js_RegenerateShapeForGC(rt);
495 if ((shape->flags & Shape::SHARED_EMPTY) &&
496 rt->meterEmptyShapes()) {
497 compartment->emptyShapes.remove((EmptyShape *) shape);
501 if (shape->inDictionary()) {
502 JS_COMPARTMENT_METER(compartment->liveDictModeNodes--);
505 * Here, shape is garbage to collect, but its parent might not
506 * be, so we may have to remove it from its parent's kids hash
507 * or kid singleton pointer set.
509 * Without a separate mark-clearing pass, we can't tell whether
510 * shape->parent is live at this point, so we must remove shape
511 * if its parent member is non-null. A saving grace: if shape's
512 * parent is dead and swept by this point, shape->parent will
513 * be null -- in the next paragraph, we null all of a property
514 * tree node's kids' parent links when sweeping that node.
519 if (!shape->kids.isNull())
520 orphanChildren(shape);
524 * Note that Shape::insertFree nulls shape->id so we know that
525 * shape is on the freelist.
527 shape->freeTable(cx);
528 shape->insertFree(&freeList);
529 JS_COMPARTMENT_METER(compartment->livePropTreeNodes--);
532 /* If a contains no live properties, return it to the malloc heap. */
533 if (liveCount == 0) {
534 for (Shape *shape = (Shape *) a->base; shape < limit; shape++)
536 JS_ARENA_DESTROY(&arenaPool, a, ap);
539 livePropCapacity += limit - (Shape *) a->base;
540 totalLiveCount += liveCount;
549 "\nProperty tree stats for gcNumber %lu\n",
550 (unsigned long) rt->gcNumber);
552 fprintf(logfp, "arenautil %g%%\n",
553 (totalLiveCount && livePropCapacity)
554 ? (totalLiveCount * 100.0) / livePropCapacity
557 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
559 /* This data is global, so only print it once per GC. */
560 if (compartment == rt->atomsCompartment) {
562 "Scope search stats:\n"
564 " hits: %6u %5.2f%% of searches\n"
565 " misses: %6u %5.2f%%\n"
566 " hashes: %6u %5.2f%%\n"
567 " hashHits: %6u %5.2f%% (%5.2f%% of hashes)\n"
568 " hashMisses: %6u %5.2f%% (%5.2f%%)\n"
569 " steps: %6u %5.2f%% (%5.2f%%)\n"
570 " stepHits: %6u %5.2f%% (%5.2f%%)\n"
571 " stepMisses: %6u %5.2f%% (%5.2f%%)\n"
572 " initSearches: %6u\n"
573 " changeSearches: %6u\n"
574 " tableAllocFails: %6u\n"
575 " toDictFails: %6u\n"
576 " wrapWatchFails: %6u\n"
580 " redundantPuts: %6u\n"
583 " changeFails: %6u\n"
587 " removeFrees: %6u\n"
588 " uselessRemoves: %6u\n"
590 js_scope_stats.searches,
591 js_scope_stats.hits, RATE(hits, searches),
592 js_scope_stats.misses, RATE(misses, searches),
593 js_scope_stats.hashes, RATE(hashes, searches),
594 js_scope_stats.hashHits, RATE(hashHits, searches), RATE(hashHits, hashes),
595 js_scope_stats.hashMisses, RATE(hashMisses, searches), RATE(hashMisses, hashes),
596 js_scope_stats.steps, RATE(steps, searches), RATE(steps, hashes),
597 js_scope_stats.stepHits, RATE(stepHits, searches), RATE(stepHits, hashes),
598 js_scope_stats.stepMisses, RATE(stepMisses, searches), RATE(stepMisses, hashes),
599 js_scope_stats.initSearches,
600 js_scope_stats.changeSearches,
601 js_scope_stats.tableAllocFails,
602 js_scope_stats.toDictFails,
603 js_scope_stats.wrapWatchFails,
605 js_scope_stats.addFails,
607 js_scope_stats.redundantPuts,
608 js_scope_stats.putFails,
609 js_scope_stats.changes,
610 js_scope_stats.changeFails,
611 js_scope_stats.compresses,
612 js_scope_stats.grows,
613 js_scope_stats.removes,
614 js_scope_stats.removeFrees,
615 js_scope_stats.uselessRemoves,
616 js_scope_stats.shrinks);
627 js::PropertyTree::unmarkShapes(JSContext *cx)
629 JSArena **ap = &arenaPool.first.next;
630 while (JSArena *a = *ap) {
631 Shape *limit = (Shape *) a->avail;
633 for (Shape *shape = (Shape *) a->base; shape < limit; shape++) {
634 /* If the id is null, shape is already on the freelist. */
635 if (JSID_IS_VOID(shape->id))
645 js::PropertyTree::dumpShapes(JSContext *cx)
648 JSRuntime *rt = cx->runtime;
650 if (const char *filename = rt->propTreeDumpFilename) {
652 JS_snprintf(pathname, sizeof pathname, "%s.%lu",
653 filename, (unsigned long)rt->gcNumber);
654 FILE *dumpfp = fopen(pathname, "w");
656 typedef JSCompartment::EmptyShapeSet HS;
658 for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
659 if (rt->gcCurrentCompartment != NULL && rt->gcCurrentCompartment != *c)
662 fprintf(dumpfp, "*** Compartment %p ***\n", (void *)*c);
664 HS &h = (*c)->emptyShapes;
665 for (HS::Range r = h.all(); !r.empty(); r.popFront()) {
666 Shape *empty = r.front();
667 empty->dumpSubtree(cx, 0, dumpfp);