Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / js / src / jspropertytree.cpp
blob25696c37faeb4ef811b32896e7f6f6508ffb349f
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);
82 /* On failure, returns NULL. Does not report out of memory. */
83 Shape *
84 PropertyTree::newShapeUnchecked()
86 Shape *shape;
88 shape = freeList;
89 if (shape) {
90 shape->removeFree();
91 } else {
92 JS_ARENA_ALLOCATE_CAST(shape, Shape *, &arenaPool, sizeof(Shape));
93 if (!shape)
94 return NULL;
97 #ifdef DEBUG
98 shape->compartment = compartment;
99 #endif
101 JS_COMPARTMENT_METER(compartment->livePropTreeNodes++);
102 JS_COMPARTMENT_METER(compartment->totalPropTreeNodes++);
103 return shape;
106 Shape *
107 PropertyTree::newShape(JSContext *cx)
109 Shape *shape = newShapeUnchecked();
110 if (!shape)
111 JS_ReportOutOfMemory(cx);
112 return shape;
115 static KidsHash *
116 HashChildren(Shape *kid1, Shape *kid2)
118 void *mem = js_malloc(sizeof(KidsHash));
119 if (!mem)
120 return NULL;
122 KidsHash *hash = new (mem) KidsHash();
123 if (!hash->init(2)) {
124 js_free(hash);
125 return NULL;
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));
135 return hash;
138 bool
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);
154 return true;
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);
163 if (!hash) {
164 JS_ReportOutOfMemory(cx);
165 return false;
167 kidp->setHash(hash);
168 child->setParent(parent);
169 return true;
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);
177 return false;
179 child->setParent(parent);
180 return true;
183 void
184 PropertyTree::removeChild(Shape *child)
186 JS_ASSERT(!child->inDictionary());
188 Shape *parent = child->parent;
189 JS_ASSERT(parent);
190 JS_ASSERT(!JSID_IS_VOID(parent->id));
192 KidsPointer *kidp = &parent->kids;
193 if (kidp->isShape()) {
194 Shape *kid = kidp->toShape();
195 if (kid == child)
196 parent->kids.setNull();
197 return;
200 kidp->toHash()->remove(child);
203 Shape *
204 PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child)
206 Shape *shape;
208 JS_ASSERT(parent);
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))
223 return shape;
224 } else if (kidp->isHash()) {
225 shape = *kidp->toHash()->lookup(&child);
226 if (shape)
227 return shape;
228 } else {
229 /* If kidp->isNull(), we always insert. */
232 shape = newShape(cx);
233 if (!shape)
234 return NULL;
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))
240 return NULL;
242 return shape;
245 #ifdef DEBUG
247 void
248 KidsPointer::checkConsistency(const Shape *aKid) const
250 if (isShape()) {
251 JS_ASSERT(toShape() == aKid);
252 } else {
253 JS_ASSERT(isHash());
254 KidsHash *hash = toHash();
255 KidsHash::Ptr ptr = hash->lookup(aKid);
256 JS_ASSERT(*ptr == aKid);
260 void
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>");
269 } else {
270 JSLinearString *str;
271 if (JSID_IS_ATOM(id)) {
272 str = JSID_TO_ATOM(id);
273 } else {
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;
279 if (!str)
280 fputs("<error>", fp);
281 else
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),
288 slot, attrs);
289 if (attrs) {
290 int first = 1;
291 fputs("(", fp);
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);
299 #undef DUMP_ATTR
300 fputs(") ", fp);
303 fprintf(fp, "flags %x ", flags);
304 if (flags) {
305 int first = 1;
306 fputs("(", fp);
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);
314 #undef DUMP_FLAG
315 fputs(") ", fp);
318 fprintf(fp, "shortid %d\n", shortid);
321 static void
322 MeterKidCount(JSBasicStats *bs, uintN nkids)
324 JS_BASIC_STATS_ACCUM(bs, nkids);
327 void
328 js::PropertyTree::meter(JSBasicStats *bs, Shape *node)
330 uintN nkids = 0;
331 const KidsPointer &kidp = node->kids;
332 if (kidp.isShape()) {
333 meter(bs, kidp.toShape());
334 nkids = 1;
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();
340 meter(bs, kid);
341 nkids++;
345 MeterKidCount(bs, nkids);
348 void
349 Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const
351 if (!parent) {
352 JS_ASSERT(level == 0);
353 JS_ASSERT(JSID_IS_EMPTY(id));
354 fprintf(fp, "class %s emptyShape %u\n", clasp->name, shape);
355 } else {
356 fprintf(fp, "%*sid ", level, "");
357 dump(cx, fp);
360 if (!kids.isNull()) {
361 ++level;
362 if (kids.isShape()) {
363 Shape *kid = kids.toShape();
364 JS_ASSERT(kid->parent == this);
365 kid->dumpSubtree(cx, level, fp);
366 } else {
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);
378 #endif /* DEBUG */
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);
392 kid->parent = NULL;
394 } else {
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);
401 kid->parent = NULL;
405 hash->~KidsHash();
406 js_free(hash);
409 kidp->setNull();
412 void
413 js::PropertyTree::sweepShapes(JSContext *cx)
415 JSRuntime *rt = compartment->rt;
417 #ifdef DEBUG
418 JSBasicStats bs;
419 uint32 livePropCapacity = 0, totalLiveCount = 0;
420 static FILE *logfp;
421 if (!logfp) {
422 if (const char *filename = rt->propTreeStatFilename)
423 logfp = fopen(filename, "w");
426 if (logfp) {
427 JS_BASIC_STATS_INIT(&bs);
429 uint32 empties;
431 typedef JSCompartment::EmptyShapeSet HS;
433 HS &h = compartment->emptyShapes;
434 empties = h.count();
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);
446 nodes -= empties;
448 double sigma;
449 double mean = JS_MeanAndStdDevBS(&bs, &sigma);
451 fprintf(logfp,
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);
457 #endif
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;
467 uintN liveCount = 0;
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))
472 continue;
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()) {
483 shape->clearMark();
484 if (rt->gcRegenShapes) {
485 if (shape->hasRegenFlag())
486 shape->clearRegenFlag();
487 else
488 shape->shape = js_RegenerateShapeForGC(rt);
490 liveCount++;
491 continue;
494 #ifdef DEBUG
495 if ((shape->flags & Shape::SHARED_EMPTY) &&
496 rt->meterEmptyShapes()) {
497 compartment->emptyShapes.remove((EmptyShape *) shape);
499 #endif
501 if (shape->inDictionary()) {
502 JS_COMPARTMENT_METER(compartment->liveDictModeNodes--);
503 } else {
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.
516 if (shape->parent)
517 removeChild(shape);
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++)
535 shape->removeFree();
536 JS_ARENA_DESTROY(&arenaPool, a, ap);
537 } else {
538 #ifdef DEBUG
539 livePropCapacity += limit - (Shape *) a->base;
540 totalLiveCount += liveCount;
541 #endif
542 ap = &a->next;
546 #ifdef DEBUG
547 if (logfp) {
548 fprintf(logfp,
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
555 : 0.0);
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) {
561 fprintf(logfp,
562 "Scope search stats:\n"
563 " searches: %6u\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"
577 " adds: %6u\n"
578 " addFails: %6u\n"
579 " puts: %6u\n"
580 " redundantPuts: %6u\n"
581 " putFails: %6u\n"
582 " changes: %6u\n"
583 " changeFails: %6u\n"
584 " compresses: %6u\n"
585 " grows: %6u\n"
586 " removes: %6u\n"
587 " removeFrees: %6u\n"
588 " uselessRemoves: %6u\n"
589 " shrinks: %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,
604 js_scope_stats.adds,
605 js_scope_stats.addFails,
606 js_scope_stats.puts,
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);
619 #undef RATE
621 fflush(logfp);
623 #endif /* DEBUG */
626 bool
627 js::PropertyTree::checkShapesAllUnmarked(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))
636 continue;
638 if (shape->marked())
639 return false;
641 ap = &a->next;
644 return true;
647 void
648 js::PropertyTree::dumpShapes(JSContext *cx)
650 #ifdef DEBUG
651 JSRuntime *rt = cx->runtime;
653 if (const char *filename = rt->propTreeDumpFilename) {
654 char pathname[1024];
655 JS_snprintf(pathname, sizeof pathname, "%s.%lu",
656 filename, (unsigned long)rt->gcNumber);
657 FILE *dumpfp = fopen(pathname, "w");
658 if (dumpfp) {
659 typedef JSCompartment::EmptyShapeSet HS;
661 for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
662 if (rt->gcCurrentCompartment != NULL && rt->gcCurrentCompartment != *c)
663 continue;
665 fprintf(dumpfp, "*** Compartment %p ***\n", (void *)*c);
667 HS &h = (*c)->emptyShapes;
668 for (HS::Range r = h.all(); !r.empty(); r.popFront()) {
669 Shape *empty = r.front();
670 empty->dumpSubtree(cx, 0, dumpfp);
671 putc('\n', dumpfp);
675 fclose(dumpfp);
678 #endif