Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jspropertytree.cpp
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
5  *
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/
10  *
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.
15  *
16  * The Original Code is the Mozilla SpiderMonkey property tree implementation
17  *
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.
22  *
23  * Contributor(s):
24  *   Brendan Eich <brendan@mozilla.org>
25  *
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.
37  *
38  * ***** END LICENSE BLOCK ***** */
39
40 #include <new>
41
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"
50
51 #include "jsobjinlines.h"
52 #include "jsscopeinlines.h"
53
54 using namespace js;
55
56 inline HashNumber
57 ShapeHasher::hash(const Lookup l)
58 {
59     return l->hash();
60 }
61
62 inline bool
63 ShapeHasher::match(const Key k, const Lookup l)
64 {
65     return l->matches(k);
66 }
67
68 bool
69 PropertyTree::init()
70 {
71     JS_InitArenaPool(&arenaPool, "properties",
72                      256 * sizeof(Shape), sizeof(void *), NULL);
73     return true;
74 }
75
76 void
77 PropertyTree::finish()
78 {
79     JS_FinishArenaPool(&arenaPool);
80 }
81
82 /* On failure, returns NULL. Does not report out of memory. */
83 Shape *
84 PropertyTree::newShapeUnchecked()
85 {
86     Shape *shape;
87
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;
95     }
96
97 #ifdef DEBUG
98     shape->compartment = compartment;
99 #endif
100
101     JS_COMPARTMENT_METER(compartment->livePropTreeNodes++);
102     JS_COMPARTMENT_METER(compartment->totalPropTreeNodes++);
103     return shape;
104 }
105
106 Shape *
107 PropertyTree::newShape(JSContext *cx)
108 {
109     Shape *shape = newShapeUnchecked();
110     if (!shape)
111         JS_ReportOutOfMemory(cx);
112     return shape;
113 }
114
115 static KidsHash *
116 HashChildren(Shape *kid1, Shape *kid2)
117 {
118     void *mem = js_malloc(sizeof(KidsHash));
119     if (!mem)
120         return NULL;
121
122     KidsHash *hash = new (mem) KidsHash();
123     if (!hash->init(2)) {
124         js_free(hash);
125         return NULL;
126     }
127
128     KidsHash::AddPtr addPtr = hash->lookupForAdd(kid1);
129     JS_ALWAYS_TRUE(hash->add(addPtr, kid1));
130
131     addPtr = hash->lookupForAdd(kid2);
132     JS_ASSERT(!addPtr.found());
133     JS_ALWAYS_TRUE(hash->add(addPtr, kid2));
134
135     return hash;
136 }
137
138 bool
139 PropertyTree::insertChild(JSContext *cx, Shape *parent, Shape *child)
140 {
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);
148
149     KidsPointer *kidp = &parent->kids;
150
151     if (kidp->isNull()) {
152         child->setParent(parent);
153         kidp->setShape(child);
154         return true;
155     }
156
157     if (kidp->isShape()) {
158         Shape *shape = kidp->toShape();
159         JS_ASSERT(shape != child);
160         JS_ASSERT(!shape->matches(child));
161
162         KidsHash *hash = HashChildren(shape, child);
163         if (!hash) {
164             JS_ReportOutOfMemory(cx);
165             return false;
166         }
167         kidp->setHash(hash);
168         child->setParent(parent);
169         return true;
170     }
171
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;
178     }
179     child->setParent(parent);
180     return true;
181 }
182
183 void
184 PropertyTree::removeChild(Shape *child)
185 {
186     JS_ASSERT(!child->inDictionary());
187
188     Shape *parent = child->parent;
189     JS_ASSERT(parent);
190     JS_ASSERT(!JSID_IS_VOID(parent->id));
191
192     KidsPointer *kidp = &parent->kids;
193     if (kidp->isShape()) {
194         Shape *kid = kidp->toShape();
195         if (kid == child)
196             parent->kids.setNull();
197         return;
198     }
199
200     kidp->toHash()->remove(child);
201 }
202
203 Shape *
204 PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child)
205 {
206     Shape *shape;
207
208     JS_ASSERT(parent);
209     JS_ASSERT(!JSID_IS_VOID(parent->id));
210
211     /*
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.
218      */
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. */
230     }
231
232     shape = newShape(cx);
233     if (!shape)
234         return NULL;
235
236     new (shape) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs,
237                       child.flags, child.shortid, js_GenerateShape(cx));
238
239     if (!insertChild(cx, parent, shape))
240         return NULL;
241
242     return shape;
243 }
244
245 #ifdef DEBUG
246
247 void
248 KidsPointer::checkConsistency(const Shape *aKid) const
249 {
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);
257     }
258 }
259
260 void
261 Shape::dump(JSContext *cx, FILE *fp) const
262 {
263     JS_ASSERT(!JSID_IS_VOID(id));
264
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;
278         }
279         if (!str)
280             fputs("<error>", fp);
281         else
282             FileEscapedString(fp, str, '"');
283     }
284
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);
301     }
302
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);
316     }
317
318     fprintf(fp, "shortid %d\n", shortid);
319 }
320
321 static void
322 MeterKidCount(JSBasicStats *bs, uintN nkids)
323 {
324     JS_BASIC_STATS_ACCUM(bs, nkids);
325 }
326
327 void
328 js::PropertyTree::meter(JSBasicStats *bs, Shape *node)
329 {
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();
339             
340             meter(bs, kid);
341             nkids++;
342         }
343     }
344
345     MeterKidCount(bs, nkids);
346 }
347
348 void
349 Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const
350 {
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);
358     }
359
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();
370
371                 JS_ASSERT(kid->parent == this);
372                 kid->dumpSubtree(cx, level, fp);
373             }
374         }
375     }
376 }
377
378 #endif /* DEBUG */
379
380 JS_ALWAYS_INLINE void
381 js::PropertyTree::orphanChildren(Shape *shape)
382 {
383     KidsPointer *kidp = &shape->kids;
384
385     JS_ASSERT(!kidp->isNull());
386
387     if (kidp->isShape()) {
388         Shape *kid = kidp->toShape();
389
390         if (!JSID_IS_VOID(kid->id)) {
391             JS_ASSERT(kid->parent == shape);
392             kid->parent = NULL;
393         }
394     } else {
395         KidsHash *hash = kidp->toHash();
396
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;
402             }
403         }
404
405         hash->~KidsHash();
406         js_free(hash);
407     }
408
409     kidp->setNull();
410 }
411
412 void
413 js::PropertyTree::sweepShapes(JSContext *cx)
414 {
415     JSRuntime *rt = compartment->rt;
416
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");
424     }
425
426     if (logfp) {
427         JS_BASIC_STATS_INIT(&bs);
428
429         uint32 empties;
430         {
431             typedef JSCompartment::EmptyShapeSet HS;
432
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());
438         }
439
440         double props = rt->liveObjectPropsPreSweep;
441         double nodes = compartment->livePropTreeNodes;
442         double dicts = compartment->liveDictModeNodes;
443
444         /* Empty scope nodes are never hashed, so subtract them from nodes. */
445         JS_ASSERT(nodes - dicts == bs.sum);
446         nodes -= empties;
447
448         double sigma;
449         double mean = JS_MeanAndStdDevBS(&bs, &sigma);
450
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);
454
455         JS_DumpHistogram(&bs, logfp);
456     }
457 #endif
458
459     /*
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.
463      */
464     JSArena **ap = &arenaPool.first.next;
465     while (JSArena *a = *ap) {
466         Shape *limit = (Shape *) a->avail;
467         uintN liveCount = 0;
468
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;
473
474             /*
475              * If the mark bit is set, shape is alive, so clear the mark bit
476              * and continue the while loop.
477              *
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.
481              */
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);
489                 }
490                 liveCount++;
491                 continue;
492             }
493
494 #ifdef DEBUG
495             if ((shape->flags & Shape::SHARED_EMPTY) &&
496                 rt->meterEmptyShapes()) {
497                 compartment->emptyShapes.remove((EmptyShape *) shape);
498             }
499 #endif
500
501             if (shape->inDictionary()) {
502                 JS_COMPARTMENT_METER(compartment->liveDictModeNodes--);
503             } else {
504                 /*
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.
508                  *
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.
515                  */
516                 if (shape->parent)
517                     removeChild(shape);
518
519                 if (!shape->kids.isNull())
520                     orphanChildren(shape);
521             }
522
523             /*
524              * Note that Shape::insertFree nulls shape->id so we know that
525              * shape is on the freelist.
526              */
527             shape->freeTable(cx);
528             shape->insertFree(&freeList);
529             JS_COMPARTMENT_METER(compartment->livePropTreeNodes--);
530         }
531
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;
543         }
544     }
545
546 #ifdef DEBUG
547     if (logfp) {
548         fprintf(logfp,
549                 "\nProperty tree stats for gcNumber %lu\n",
550                 (unsigned long) rt->gcNumber);
551
552         fprintf(logfp, "arenautil %g%%\n",
553                 (totalLiveCount && livePropCapacity)
554                 ? (totalLiveCount * 100.0) / livePropCapacity
555                 : 0.0);
556
557 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
558
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);
617         }
618
619 #undef RATE
620
621         fflush(logfp);
622     }
623 #endif /* DEBUG */
624 }
625
626 void
627 js::PropertyTree::unmarkShapes(JSContext *cx)
628 {
629     JSArena **ap = &arenaPool.first.next;
630     while (JSArena *a = *ap) {
631         Shape *limit = (Shape *) a->avail;
632
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;
637
638             shape->clearMark();
639         }
640         ap = &a->next;
641     }
642 }
643
644 void
645 js::PropertyTree::dumpShapes(JSContext *cx)
646 {
647 #ifdef DEBUG
648     JSRuntime *rt = cx->runtime;
649
650     if (const char *filename = rt->propTreeDumpFilename) {
651         char pathname[1024];
652         JS_snprintf(pathname, sizeof pathname, "%s.%lu",
653                     filename, (unsigned long)rt->gcNumber);
654         FILE *dumpfp = fopen(pathname, "w");
655         if (dumpfp) {
656             typedef JSCompartment::EmptyShapeSet HS;
657
658             for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
659                 if (rt->gcCurrentCompartment != NULL && rt->gcCurrentCompartment != *c)
660                     continue;
661
662                 fprintf(dumpfp, "*** Compartment %p ***\n", (void *)*c);
663
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);
668                     putc('\n', dumpfp);
669                 }
670             }
671
672             fclose(dumpfp);
673         }
674     }
675 #endif
676 }