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::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
))
648 js::PropertyTree::dumpShapes(JSContext
*cx
)
651 JSRuntime
*rt
= cx
->runtime
;
653 if (const char *filename
= rt
->propTreeDumpFilename
) {
655 JS_snprintf(pathname
, sizeof pathname
, "%s.%lu",
656 filename
, (unsigned long)rt
->gcNumber
);
657 FILE *dumpfp
= fopen(pathname
, "w");
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
)
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
);