Fix bogus assertion (537854, r=mrbkap).
[mozilla-central.git] / js / src / jsscope.cpp
blobd54c82932873b5ab9d04e2f5935042bf364156fb
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
17 * The Original Code is Mozilla Communicator client code, released
18 * March 31, 1998.
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
25 * Contributor(s):
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
42 * JS symbol tables.
44 #include <new>
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsstdint.h"
49 #include "jsarena.h"
50 #include "jsbit.h"
51 #include "jsclist.h"
52 #include "jsdhash.h"
53 #include "jsutil.h" /* Added by JSIFY */
54 #include "jsapi.h"
55 #include "jsatom.h"
56 #include "jscntxt.h"
57 #include "jsdbgapi.h"
58 #include "jslock.h"
59 #include "jsnum.h"
60 #include "jsscope.h"
61 #include "jsstr.h"
62 #include "jstracer.h"
64 #include "jsscopeinlines.h"
66 using namespace js;
68 uint32
69 js_GenerateShape(JSContext *cx, bool gcLocked)
71 JSRuntime *rt;
72 uint32 shape;
74 rt = cx->runtime;
75 shape = JS_ATOMIC_INCREMENT(&rt->shapeGen);
76 JS_ASSERT(shape != 0);
77 if (shape >= SHAPE_OVERFLOW_BIT) {
79 * FIXME bug 440834: The shape id space has overflowed. Currently we
80 * cope badly with this and schedule the GC on the every call. But
81 * first we make sure that increments from other threads would not
82 * have a chance to wrap around shapeGen to zero.
84 rt->shapeGen = SHAPE_OVERFLOW_BIT;
85 shape = SHAPE_OVERFLOW_BIT;
86 js_TriggerGC(cx, gcLocked);
88 return shape;
91 JSScope *
92 js_GetMutableScope(JSContext *cx, JSObject *obj)
94 JSScope *scope, *newscope;
95 JSClass *clasp;
96 uint32 freeslot;
98 scope = OBJ_SCOPE(obj);
99 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, scope));
100 if (!scope->isSharedEmpty())
101 return scope;
104 * Compile-time block objects each have their own scope, created at
105 * birth, and runtime clone of a block objects are never mutated.
107 JS_ASSERT(STOBJ_GET_CLASS(obj) != &js_BlockClass);
108 newscope = JSScope::create(cx, scope->ops, obj->getClass(), obj, scope->shape);
109 if (!newscope)
110 return NULL;
112 /* The newly allocated scope is single-threaded and, as such, is locked. */
113 JS_ASSERT(CX_OWNS_SCOPE_TITLE(cx, newscope));
114 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, newscope));
115 obj->map = newscope;
117 JS_ASSERT(newscope->freeslot == JSSLOT_FREE(STOBJ_GET_CLASS(obj)));
118 clasp = STOBJ_GET_CLASS(obj);
119 if (clasp->reserveSlots) {
121 * FIXME: Here we change OBJ_SCOPE(obj)->freeslot without changing
122 * OBJ_SHAPE(obj). If we strengthen the shape guarantees to cover
123 * freeslot, we can eliminate a check in JSOP_SETPROP and in
124 * js_AddProperty. See bug 535416.
126 freeslot = JSSLOT_FREE(clasp) + clasp->reserveSlots(cx, obj);
127 if (freeslot > STOBJ_NSLOTS(obj))
128 freeslot = STOBJ_NSLOTS(obj);
129 if (newscope->freeslot < freeslot)
130 newscope->freeslot = freeslot;
132 JS_DROP_ALL_EMPTY_SCOPE_LOCKS(cx, scope);
133 static_cast<JSEmptyScope *>(scope)->drop(cx);
134 return newscope;
138 * JSScope uses multiplicative hashing, _a la_ jsdhash.[ch], but specialized
139 * to minimize footprint. But if a scope has fewer than SCOPE_HASH_THRESHOLD
140 * entries, we use linear search and avoid allocating scope->table.
142 #define SCOPE_HASH_THRESHOLD 6
143 #define MIN_SCOPE_SIZE_LOG2 4
144 #define MIN_SCOPE_SIZE JS_BIT(MIN_SCOPE_SIZE_LOG2)
145 #define SCOPE_TABLE_NBYTES(n) ((n) * sizeof(JSScopeProperty *))
147 void
148 JSScope::initMinimal(JSContext *cx, uint32 newShape)
150 shape = newShape;
151 emptyScope = NULL;
152 hashShift = JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2;
153 entryCount = removedCount = 0;
154 table = NULL;
155 lastProp = NULL;
158 #ifdef DEBUG
159 JS_FRIEND_DATA(JSScopeStats) js_scope_stats = {0};
161 # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x)
162 #else
163 # define METER(x) /* nothing */
164 #endif
166 bool
167 JSScope::createTable(JSContext *cx, bool report)
169 int sizeLog2;
170 JSScopeProperty *sprop, **spp;
172 JS_ASSERT(!table);
173 JS_ASSERT(lastProp);
175 if (entryCount > SCOPE_HASH_THRESHOLD) {
177 * Either we're creating a table for a large scope that was populated
178 * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or
179 * JSOP_SETPROP; or else calloc failed at least once already. In any
180 * event, let's try to grow, overallocating to hold at least twice the
181 * current population.
183 sizeLog2 = JS_CeilingLog2(2 * entryCount);
184 hashShift = JS_DHASH_BITS - sizeLog2;
185 } else {
186 JS_ASSERT(hashShift == JS_DHASH_BITS - MIN_SCOPE_SIZE_LOG2);
187 sizeLog2 = MIN_SCOPE_SIZE_LOG2;
190 table = (JSScopeProperty **) js_calloc(JS_BIT(sizeLog2) * sizeof(JSScopeProperty *));
191 if (!table) {
192 if (report)
193 JS_ReportOutOfMemory(cx);
194 METER(tableAllocFails);
195 return false;
197 cx->updateMallocCounter(JS_BIT(sizeLog2) * sizeof(JSScopeProperty *));
199 hashShift = JS_DHASH_BITS - sizeLog2;
200 for (sprop = lastProp; sprop; sprop = sprop->parent) {
201 spp = search(sprop->id, true);
202 SPROP_STORE_PRESERVING_COLLISION(spp, sprop);
204 return true;
207 JSScope *
208 JSScope::create(JSContext *cx, const JSObjectOps *ops, JSClass *clasp,
209 JSObject *obj, uint32 shape)
211 JS_ASSERT(ops->isNative());
212 JS_ASSERT(obj);
214 JSScope *scope = cx->create<JSScope>(ops, obj);
215 if (!scope)
216 return NULL;
218 scope->freeslot = JSSLOT_FREE(clasp);
219 scope->flags = cx->runtime->gcRegenShapesScopeFlag;
220 scope->initMinimal(cx, shape);
222 #ifdef JS_THREADSAFE
223 js_InitTitle(cx, &scope->title);
224 #endif
225 JS_RUNTIME_METER(cx->runtime, liveScopes);
226 JS_RUNTIME_METER(cx->runtime, totalScopes);
227 return scope;
230 JSEmptyScope::JSEmptyScope(JSContext *cx, const JSObjectOps *ops,
231 JSClass *clasp)
232 : JSScope(ops, NULL), clasp(clasp)
235 * This scope holds a reference to the new empty scope. Our only caller,
236 * getEmptyScope, also promises to incref on behalf of its caller.
238 nrefs = 2;
239 freeslot = JSSLOT_FREE(clasp);
240 flags = OWN_SHAPE | cx->runtime->gcRegenShapesScopeFlag;
241 initMinimal(cx, js_GenerateShape(cx, false));
243 #ifdef JS_THREADSAFE
244 js_InitTitle(cx, &title);
245 #endif
246 JS_RUNTIME_METER(cx->runtime, liveScopes);
247 JS_RUNTIME_METER(cx->runtime, totalScopes);
250 #ifdef DEBUG
251 # include "jsprf.h"
252 # define LIVE_SCOPE_METER(cx,expr) JS_LOCK_RUNTIME_VOID(cx->runtime,expr)
253 #else
254 # define LIVE_SCOPE_METER(cx,expr) /* nothing */
255 #endif
257 void
258 JSScope::destroy(JSContext *cx)
260 #ifdef JS_THREADSAFE
261 js_FinishTitle(cx, &title);
262 #endif
263 if (table)
264 cx->free(table);
267 * The scopes containing empty scopes are only destroyed from the GC
268 * thread.
270 if (emptyScope)
271 emptyScope->dropFromGC(cx);
273 LIVE_SCOPE_METER(cx, cx->runtime->liveScopeProps -= entryCount);
274 JS_RUNTIME_UNMETER(cx->runtime, liveScopes);
275 cx->free(this);
278 /* static */
279 bool
280 JSScope::initRuntimeState(JSContext *cx)
282 cx->runtime->emptyBlockScope = cx->create<JSEmptyScope>(cx, &js_ObjectOps,
283 &js_BlockClass);
284 JS_ASSERT(cx->runtime->emptyBlockScope->nrefs == 2);
285 cx->runtime->emptyBlockScope->nrefs = 1;
286 return !!cx->runtime->emptyBlockScope;
289 /* static */
290 void
291 JSScope::finishRuntimeState(JSContext *cx)
293 JSRuntime *rt = cx->runtime;
294 if (rt->emptyBlockScope) {
295 rt->emptyBlockScope->drop(cx);
296 rt->emptyBlockScope = NULL;
300 JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4);
301 JS_STATIC_ASSERT(sizeof(jsid) == JS_BYTES_PER_WORD);
303 #if JS_BYTES_PER_WORD == 4
304 # define HASH_ID(id) ((JSHashNumber)(id))
305 #elif JS_BYTES_PER_WORD == 8
306 # define HASH_ID(id) ((JSHashNumber)(id) ^ (JSHashNumber)((id) >> 32))
307 #else
308 # error "Unsupported configuration"
309 #endif
312 * Double hashing needs the second hash code to be relatively prime to table
313 * size, so we simply make hash2 odd. The inputs to multiplicative hash are
314 * the golden ratio, expressed as a fixed-point 32 bit fraction, and the id
315 * itself.
317 #define SCOPE_HASH0(id) (HASH_ID(id) * JS_GOLDEN_RATIO)
318 #define SCOPE_HASH1(hash0,shift) ((hash0) >> (shift))
319 #define SCOPE_HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
321 JSScopeProperty **
322 JSScope::searchTable(jsid id, bool adding)
324 JSHashNumber hash0, hash1, hash2;
325 int sizeLog2;
326 JSScopeProperty *stored, *sprop, **spp, **firstRemoved;
327 uint32 sizeMask;
329 JS_ASSERT(table);
330 JS_ASSERT(!JSVAL_IS_NULL(id));
332 /* Compute the primary hash address. */
333 METER(hashes);
334 hash0 = SCOPE_HASH0(id);
335 hash1 = SCOPE_HASH1(hash0, hashShift);
336 spp = table + hash1;
338 /* Miss: return space for a new entry. */
339 stored = *spp;
340 if (SPROP_IS_FREE(stored)) {
341 METER(misses);
342 return spp;
345 /* Hit: return entry. */
346 sprop = SPROP_CLEAR_COLLISION(stored);
347 if (sprop && sprop->id == id) {
348 METER(hits);
349 return spp;
352 /* Collision: double hash. */
353 sizeLog2 = JS_DHASH_BITS - hashShift;
354 hash2 = SCOPE_HASH2(hash0, sizeLog2, hashShift);
355 sizeMask = JS_BITMASK(sizeLog2);
357 #ifdef DEBUG
358 jsuword collision_flag = SPROP_COLLISION;
359 #endif
361 /* Save the first removed entry pointer so we can recycle it if adding. */
362 if (SPROP_IS_REMOVED(stored)) {
363 firstRemoved = spp;
364 } else {
365 firstRemoved = NULL;
366 if (adding && !SPROP_HAD_COLLISION(stored))
367 SPROP_FLAG_COLLISION(spp, sprop);
368 #ifdef DEBUG
369 collision_flag &= jsuword(*spp) & SPROP_COLLISION;
370 #endif
373 for (;;) {
374 METER(steps);
375 hash1 -= hash2;
376 hash1 &= sizeMask;
377 spp = table + hash1;
379 stored = *spp;
380 if (SPROP_IS_FREE(stored)) {
381 METER(stepMisses);
382 return (adding && firstRemoved) ? firstRemoved : spp;
385 sprop = SPROP_CLEAR_COLLISION(stored);
386 if (sprop && sprop->id == id) {
387 METER(stepHits);
388 JS_ASSERT(collision_flag);
389 return spp;
392 if (SPROP_IS_REMOVED(stored)) {
393 if (!firstRemoved)
394 firstRemoved = spp;
395 } else {
396 if (adding && !SPROP_HAD_COLLISION(stored))
397 SPROP_FLAG_COLLISION(spp, sprop);
398 #ifdef DEBUG
399 collision_flag &= jsuword(*spp) & SPROP_COLLISION;
400 #endif
404 /* NOTREACHED */
405 return NULL;
408 bool
409 JSScope::changeTable(JSContext *cx, int change)
411 int oldlog2, newlog2;
412 uint32 oldsize, newsize, nbytes;
413 JSScopeProperty **newtable, **oldtable, **spp, **oldspp, *sprop;
415 if (!table)
416 return createTable(cx, true);
418 /* Grow, shrink, or compress by changing this->table. */
419 oldlog2 = JS_DHASH_BITS - hashShift;
420 newlog2 = oldlog2 + change;
421 oldsize = JS_BIT(oldlog2);
422 newsize = JS_BIT(newlog2);
423 nbytes = SCOPE_TABLE_NBYTES(newsize);
424 newtable = (JSScopeProperty **) cx->calloc(nbytes);
425 if (!newtable) {
426 METER(tableAllocFails);
427 return false;
430 /* Now that we have newtable allocated, update members. */
431 hashShift = JS_DHASH_BITS - newlog2;
432 removedCount = 0;
433 oldtable = table;
434 table = newtable;
436 /* Treat the above calloc as a JS_malloc, to match CreateScopeTable. */
437 cx->updateMallocCounter(nbytes);
439 /* Copy only live entries, leaving removed and free ones behind. */
440 for (oldspp = oldtable; oldsize != 0; oldspp++) {
441 sprop = SPROP_FETCH(oldspp);
442 if (sprop) {
443 spp = search(sprop->id, true);
444 JS_ASSERT(SPROP_IS_FREE(*spp));
445 *spp = sprop;
447 oldsize--;
450 /* Finally, free the old table storage. */
451 cx->free(oldtable);
452 return true;
455 static JSDHashNumber
456 js_HashScopeProperty(JSDHashTable *table, const void *key)
458 const JSScopeProperty *sprop = (const JSScopeProperty *)key;
459 return sprop->hash();
462 static JSBool
463 js_MatchScopeProperty(JSDHashTable *table,
464 const JSDHashEntryHdr *hdr,
465 const void *key)
467 const JSPropertyTreeEntry *entry = (const JSPropertyTreeEntry *)hdr;
468 const JSScopeProperty *sprop = entry->child;
469 const JSScopeProperty *kprop = (const JSScopeProperty *)key;
471 return sprop->matches(kprop);
474 static const JSDHashTableOps PropertyTreeHashOps = {
475 JS_DHashAllocTable,
476 JS_DHashFreeTable,
477 js_HashScopeProperty,
478 js_MatchScopeProperty,
479 JS_DHashMoveEntryStub,
480 JS_DHashClearEntryStub,
481 JS_DHashFinalizeStub,
482 NULL
486 * A property tree node on rt->propertyFreeList overlays the following prefix
487 * struct on JSScopeProperty.
489 typedef struct FreeNode {
490 jsid id;
491 JSScopeProperty *next;
492 JSScopeProperty **prevp;
493 } FreeNode;
495 #define FREENODE(sprop) ((FreeNode *) (sprop))
497 #define FREENODE_INSERT(list, sprop) \
498 JS_BEGIN_MACRO \
499 FREENODE(sprop)->next = (list); \
500 FREENODE(sprop)->prevp = &(list); \
501 if (list) \
502 FREENODE(list)->prevp = &FREENODE(sprop)->next; \
503 (list) = (sprop); \
504 JS_END_MACRO
506 #define FREENODE_REMOVE(sprop) \
507 JS_BEGIN_MACRO \
508 *FREENODE(sprop)->prevp = FREENODE(sprop)->next; \
509 if (FREENODE(sprop)->next) \
510 FREENODE(FREENODE(sprop)->next)->prevp = FREENODE(sprop)->prevp; \
511 JS_END_MACRO
513 /* NB: Called with rt->gcLock held. */
514 static JSScopeProperty *
515 NewScopeProperty(JSRuntime *rt)
517 JSScopeProperty *sprop;
519 sprop = rt->propertyFreeList;
520 if (sprop) {
521 FREENODE_REMOVE(sprop);
522 } else {
523 JS_ARENA_ALLOCATE_CAST(sprop, JSScopeProperty *,
524 &rt->propertyArenaPool,
525 sizeof(JSScopeProperty));
526 if (!sprop)
527 return NULL;
530 JS_RUNTIME_METER(rt, livePropTreeNodes);
531 JS_RUNTIME_METER(rt, totalPropTreeNodes);
532 return sprop;
535 #define CHUNKY_KIDS_TAG ((jsuword)1)
536 #define KIDS_IS_CHUNKY(kids) ((jsuword)(kids) & CHUNKY_KIDS_TAG)
537 #define KIDS_TO_CHUNK(kids) ((PropTreeKidsChunk *) \
538 ((jsuword)(kids) & ~CHUNKY_KIDS_TAG))
539 #define CHUNK_TO_KIDS(chunk) ((JSScopeProperty *) \
540 ((jsuword)(chunk) | CHUNKY_KIDS_TAG))
541 #define MAX_KIDS_PER_CHUNK 10
542 #define CHUNK_HASH_THRESHOLD 30
544 typedef struct PropTreeKidsChunk PropTreeKidsChunk;
546 struct PropTreeKidsChunk {
547 JSScopeProperty *kids[MAX_KIDS_PER_CHUNK];
548 JSDHashTable *table;
549 PropTreeKidsChunk *next;
552 static PropTreeKidsChunk *
553 NewPropTreeKidsChunk(JSRuntime *rt)
555 PropTreeKidsChunk *chunk;
557 chunk = (PropTreeKidsChunk *) js_calloc(sizeof *chunk);
558 if (!chunk)
559 return NULL;
560 JS_ASSERT(((jsuword)chunk & CHUNKY_KIDS_TAG) == 0);
561 JS_RUNTIME_METER(rt, propTreeKidsChunks);
562 return chunk;
565 static void
566 DestroyPropTreeKidsChunk(JSRuntime *rt, PropTreeKidsChunk *chunk)
568 JS_RUNTIME_UNMETER(rt, propTreeKidsChunks);
569 if (chunk->table)
570 JS_DHashTableDestroy(chunk->table);
571 js_free(chunk);
574 /* NB: Called with rt->gcLock held. */
575 static bool
576 InsertPropertyTreeChild(JSRuntime *rt, JSScopeProperty *parent,
577 JSScopeProperty *child, PropTreeKidsChunk *sweptChunk)
579 JSDHashTable *table;
580 JSPropertyTreeEntry *entry;
581 JSScopeProperty **childp, *kids, *sprop;
582 PropTreeKidsChunk *chunk, **chunkp;
583 uintN i;
585 JS_ASSERT(!parent || child->parent != parent);
586 JS_ASSERT(!JSVAL_IS_NULL(child->id));
588 if (!parent) {
589 table = &rt->propertyTreeHash;
590 entry = (JSPropertyTreeEntry *)
591 JS_DHashTableOperate(table, child, JS_DHASH_ADD);
592 if (!entry)
593 return false;
594 childp = &entry->child;
595 sprop = *childp;
596 if (!sprop) {
597 *childp = child;
598 } else {
600 * A "Duplicate child" case.
602 * We can't do away with child, as at least one live scope entry
603 * still points at it. What's more, that scope's lastProp chains
604 * through an ancestor line to reach child, and js_Enumerate and
605 * others count on this linkage. We must leave child out of the
606 * hash table, and not require it to be there when we eventually
607 * GC it (see RemovePropertyTreeChild, below).
609 * It is necessary to leave the duplicate child out of the hash
610 * table to preserve entry uniqueness. It is safe to leave the
611 * child out of the hash table (unlike the duplicate child cases
612 * below), because the child's parent link will be null, which
613 * can't dangle.
615 JS_ASSERT(sprop != child && sprop->matches(child));
616 JS_RUNTIME_METER(rt, duplicatePropTreeNodes);
618 } else {
619 JS_ASSERT(!JSVAL_IS_NULL(parent->id));
620 childp = &parent->kids;
621 kids = *childp;
622 if (kids) {
623 if (KIDS_IS_CHUNKY(kids)) {
624 chunk = KIDS_TO_CHUNK(kids);
626 table = chunk->table;
627 if (table) {
628 entry = (JSPropertyTreeEntry *)
629 JS_DHashTableOperate(table, child, JS_DHASH_ADD);
630 if (!entry)
631 return false;
632 if (!entry->child) {
633 entry->child = child;
634 while (chunk->next)
635 chunk = chunk->next;
636 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
637 childp = &chunk->kids[i];
638 sprop = *childp;
639 if (!sprop)
640 goto insert;
642 chunkp = &chunk->next;
643 goto new_chunk;
647 do {
648 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
649 childp = &chunk->kids[i];
650 sprop = *childp;
651 if (!sprop)
652 goto insert;
654 JS_ASSERT(sprop != child);
655 if (sprop->matches(child)) {
657 * Duplicate child, see comment above. In this
658 * case, we must let the duplicate be inserted at
659 * this level in the tree, so we keep iterating,
660 * looking for an empty slot in which to insert.
662 JS_ASSERT(sprop != child);
663 JS_RUNTIME_METER(rt, duplicatePropTreeNodes);
666 chunkp = &chunk->next;
667 } while ((chunk = *chunkp) != NULL);
669 new_chunk:
670 if (sweptChunk) {
671 chunk = sweptChunk;
672 } else {
673 chunk = NewPropTreeKidsChunk(rt);
674 if (!chunk)
675 return false;
677 *chunkp = chunk;
678 childp = &chunk->kids[0];
679 } else {
680 sprop = kids;
681 JS_ASSERT(sprop != child);
682 if (sprop->matches(child)) {
684 * Duplicate child, see comment above. Once again, we
685 * must let duplicates created by deletion pile up in a
686 * kids-chunk-list, in order to find them when sweeping
687 * and thereby avoid dangling parent pointers.
689 JS_RUNTIME_METER(rt, duplicatePropTreeNodes);
691 if (sweptChunk) {
692 chunk = sweptChunk;
693 } else {
694 chunk = NewPropTreeKidsChunk(rt);
695 if (!chunk)
696 return false;
698 parent->kids = CHUNK_TO_KIDS(chunk);
699 chunk->kids[0] = sprop;
700 childp = &chunk->kids[1];
703 insert:
704 *childp = child;
707 child->parent = parent;
708 return true;
711 /* NB: Called with rt->gcLock held. */
712 static PropTreeKidsChunk *
713 RemovePropertyTreeChild(JSRuntime *rt, JSScopeProperty *child)
715 PropTreeKidsChunk *freeChunk;
716 JSScopeProperty *parent, *kids, *kid;
717 JSDHashTable *table;
718 PropTreeKidsChunk *list, *chunk, **chunkp, *lastChunk;
719 uintN i, j;
720 JSPropertyTreeEntry *entry;
722 freeChunk = NULL;
723 parent = child->parent;
724 if (!parent) {
726 * Don't remove child if it is not in rt->propertyTreeHash, but only
727 * matches a root child in the table that has compatible members. See
728 * the "Duplicate child" comments in InsertPropertyTreeChild, above.
730 table = &rt->propertyTreeHash;
731 } else {
732 JS_ASSERT(!JSVAL_IS_NULL(parent->id));
733 kids = parent->kids;
734 if (KIDS_IS_CHUNKY(kids)) {
735 list = chunk = KIDS_TO_CHUNK(kids);
736 chunkp = &list;
737 table = chunk->table;
739 do {
740 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
741 if (chunk->kids[i] == child) {
742 lastChunk = chunk;
743 if (!lastChunk->next) {
744 j = i + 1;
745 } else {
746 j = 0;
747 do {
748 chunkp = &lastChunk->next;
749 lastChunk = *chunkp;
750 } while (lastChunk->next);
752 for (; j < MAX_KIDS_PER_CHUNK; j++) {
753 if (!lastChunk->kids[j])
754 break;
756 --j;
757 if (chunk != lastChunk || j > i)
758 chunk->kids[i] = lastChunk->kids[j];
759 lastChunk->kids[j] = NULL;
760 if (j == 0) {
761 *chunkp = NULL;
762 if (!list)
763 parent->kids = NULL;
764 freeChunk = lastChunk;
766 goto out;
770 chunkp = &chunk->next;
771 } while ((chunk = *chunkp) != NULL);
772 } else {
773 table = NULL;
774 kid = kids;
775 if (kid == child)
776 parent->kids = NULL;
780 out:
781 if (table) {
782 entry = (JSPropertyTreeEntry *)
783 JS_DHashTableOperate(table, child, JS_DHASH_LOOKUP);
785 if (entry->child == child)
786 JS_DHashTableRawRemove(table, &entry->hdr);
788 return freeChunk;
791 static JSDHashTable *
792 HashChunks(PropTreeKidsChunk *chunk, uintN n)
794 JSDHashTable *table;
795 uintN i;
796 JSScopeProperty *sprop;
797 JSPropertyTreeEntry *entry;
799 table = JS_NewDHashTable(&PropertyTreeHashOps, NULL,
800 sizeof(JSPropertyTreeEntry),
801 JS_DHASH_DEFAULT_CAPACITY(n + 1));
802 if (!table)
803 return NULL;
804 do {
805 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
806 sprop = chunk->kids[i];
807 if (!sprop)
808 break;
809 entry = (JSPropertyTreeEntry *)
810 JS_DHashTableOperate(table, sprop, JS_DHASH_ADD);
811 entry->child = sprop;
813 } while ((chunk = chunk->next) != NULL);
814 return table;
818 * Called without cx->runtime->gcLock held. This function acquires that lock
819 * only when inserting a new child. Thus there may be races to find or add a
820 * node that result in duplicates. We expect such races to be rare!
822 * We use rt->gcLock, not rt->rtLock, to avoid nesting the former inside the
823 * latter in js_GenerateShape below.
825 JSScopeProperty *
826 js_GetPropertyTreeChild(JSContext *cx, JSScopeProperty *parent,
827 const JSScopeProperty &child)
829 JSRuntime *rt;
830 JSDHashTable *table;
831 JSPropertyTreeEntry *entry;
832 JSScopeProperty *sprop;
833 PropTreeKidsChunk *chunk;
834 uintN i, n;
836 rt = cx->runtime;
837 if (!parent) {
838 JS_LOCK_GC(rt);
840 table = &rt->propertyTreeHash;
841 entry = (JSPropertyTreeEntry *)
842 JS_DHashTableOperate(table, &child, JS_DHASH_ADD);
843 if (!entry)
844 goto out_of_memory;
846 sprop = entry->child;
847 if (sprop)
848 goto out;
849 } else {
850 JS_ASSERT(!JSVAL_IS_NULL(parent->id));
853 * Because chunks are appended at the end and never deleted except by
854 * the GC, we can search without taking the runtime's GC lock. We may
855 * miss a matching sprop added by another thread, and make a duplicate
856 * one, but that is an unlikely, therefore small, cost. The property
857 * tree has extremely low fan-out below its root in popular embeddings
858 * with real-world workloads.
860 * Patterns such as defining closures that capture a constructor's
861 * environment as getters or setters on the new object that is passed
862 * in as |this| can significantly increase fan-out below the property
863 * tree root -- see bug 335700 for details.
865 entry = NULL;
866 sprop = parent->kids;
867 if (sprop) {
868 if (KIDS_IS_CHUNKY(sprop)) {
869 chunk = KIDS_TO_CHUNK(sprop);
871 table = chunk->table;
872 if (table) {
873 JS_LOCK_GC(rt);
874 entry = (JSPropertyTreeEntry *)
875 JS_DHashTableOperate(table, &child, JS_DHASH_LOOKUP);
876 sprop = entry->child;
877 if (sprop)
878 goto out;
879 goto locked_not_found;
882 n = 0;
883 do {
884 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
885 sprop = chunk->kids[i];
886 if (!sprop) {
887 n += i;
888 if (n >= CHUNK_HASH_THRESHOLD) {
889 chunk = KIDS_TO_CHUNK(parent->kids);
890 if (!chunk->table) {
891 table = HashChunks(chunk, n);
892 JS_LOCK_GC(rt);
893 if (!table)
894 goto out_of_memory;
895 if (chunk->table)
896 JS_DHashTableDestroy(table);
897 else
898 chunk->table = table;
899 goto locked_not_found;
902 goto not_found;
905 if (sprop->matches(&child))
906 return sprop;
908 n += MAX_KIDS_PER_CHUNK;
909 } while ((chunk = chunk->next) != NULL);
910 } else {
911 if (sprop->matches(&child))
912 return sprop;
916 not_found:
917 JS_LOCK_GC(rt);
920 locked_not_found:
921 sprop = NewScopeProperty(rt);
922 if (!sprop)
923 goto out_of_memory;
925 sprop->id = child.id;
926 sprop->getter = child.getter;
927 sprop->setter = child.setter;
928 sprop->slot = child.slot;
929 sprop->attrs = child.attrs;
930 sprop->flags = child.flags;
931 sprop->shortid = child.shortid;
932 sprop->parent = sprop->kids = NULL;
933 sprop->shape = js_GenerateShape(cx, true);
935 if (!parent) {
936 entry->child = sprop;
937 } else {
938 if (!InsertPropertyTreeChild(rt, parent, sprop, NULL))
939 goto out_of_memory;
942 out:
943 JS_UNLOCK_GC(rt);
944 return sprop;
946 out_of_memory:
947 JS_UNLOCK_GC(rt);
948 JS_ReportOutOfMemory(cx);
949 return NULL;
953 * Get or create a property-tree or dictionary child property of parent, which
954 * must be lastProp if inDictionaryMode(), else parent must be one of lastProp
955 * or lastProp->parent.
957 JSScopeProperty *
958 JSScope::getChildProperty(JSContext *cx, JSScopeProperty *parent,
959 JSScopeProperty &child)
961 JS_ASSERT(!JSVAL_IS_NULL(child.id));
962 JS_ASSERT(!child.inDictionary());
965 * Aliases share another property's slot, passed in the |slot| parameter.
966 * Shared properties have no slot. Unshared properties that do not alias
967 * another property's slot allocate a slot here, but may lose it due to a
968 * JS_ClearScope call.
970 if (!child.isAlias()) {
971 if (child.attrs & JSPROP_SHARED) {
972 child.slot = SPROP_INVALID_SLOT;
973 } else {
975 * We may have set slot from a nearly-matching sprop, above.
976 * If so, we're overwriting that nearly-matching sprop, so we
977 * can reuse its slot -- we don't need to allocate a new one.
978 * Similarly, we use a specific slot if provided by the caller.
980 if (child.slot == SPROP_INVALID_SLOT &&
981 !js_AllocSlot(cx, object, &child.slot)) {
982 return NULL;
987 if (inDictionaryMode()) {
988 JS_ASSERT(parent == lastProp);
989 if (newDictionaryProperty(cx, child, &lastProp)) {
990 updateShape(cx);
991 return lastProp;
993 return NULL;
996 JSScopeProperty *sprop = js_GetPropertyTreeChild(cx, parent, child);
997 if (sprop) {
998 JS_ASSERT(sprop->parent == parent);
999 if (parent == lastProp) {
1000 extend(cx, sprop);
1001 } else {
1002 JS_ASSERT(parent == lastProp->parent);
1003 setLastProperty(sprop);
1004 updateShape(cx);
1007 return sprop;
1010 #ifdef DEBUG_notbrendan
1011 #define CHECK_ANCESTOR_LINE(scope, sparse) \
1012 JS_BEGIN_MACRO \
1013 if ((scope)->table) CheckAncestorLine(scope); \
1014 JS_END_MACRO
1016 static void
1017 CheckAncestorLine(JSScope *scope)
1019 uint32 size;
1020 JSScopeProperty **spp, **start, **end, *ancestorLine, *sprop, *aprop;
1021 uint32 entryCount, ancestorCount;
1023 ancestorLine = scope->lastProperty();
1024 if (ancestorLine)
1025 JS_ASSERT(scope->hasProperty(ancestorLine));
1027 entryCount = 0;
1028 size = SCOPE_CAPACITY(scope);
1029 start = scope->table;
1030 for (spp = start, end = start + size; spp < end; spp++) {
1031 sprop = SPROP_FETCH(spp);
1032 if (sprop) {
1033 ++entryCount;
1034 for (aprop = ancestorLine; aprop; aprop = aprop->parent) {
1035 if (aprop == sprop)
1036 break;
1038 JS_ASSERT(aprop);
1041 JS_ASSERT(entryCount == scope->entryCount);
1043 ancestorCount = 0;
1044 for (sprop = ancestorLine; sprop; sprop = sprop->parent)
1045 ancestorCount++;
1046 JS_ASSERT(ancestorCount == scope->entryCount);
1048 #else
1049 #define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */
1050 #endif
1052 void
1053 JSScope::reportReadOnlyScope(JSContext *cx)
1055 JSString *str;
1056 const char *bytes;
1058 str = js_ValueToString(cx, OBJECT_TO_JSVAL(object));
1059 if (!str)
1060 return;
1061 bytes = js_GetStringBytes(cx, str);
1062 if (!bytes)
1063 return;
1064 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_READ_ONLY, bytes);
1067 void
1068 JSScope::generateOwnShape(JSContext *cx)
1070 #ifdef JS_TRACER
1071 if (object) {
1072 LeaveTraceIfGlobalObject(cx, object);
1075 * The JIT must have arranged to re-guard after any unpredictable shape
1076 * change, so if we are on trace here, we should already be prepared to
1077 * bail off trace.
1079 JS_ASSERT_IF(JS_ON_TRACE(cx), cx->bailExit);
1082 * If we are recording, here is where we forget already-guarded shapes.
1083 * Any subsequent property operation upon object on the trace currently
1084 * being recorded will re-guard (and re-memoize).
1086 TraceMonitor *tm = &JS_TRACE_MONITOR(cx);
1087 if (TraceRecorder *tr = tm->recorder)
1088 tr->forgetGuardedShapesForObject(object);
1090 #endif
1092 shape = js_GenerateShape(cx, false);
1093 setOwnShape();
1096 JSScopeProperty *
1097 JSScope::newDictionaryProperty(JSContext *cx, const JSScopeProperty &child,
1098 JSScopeProperty **childp)
1100 JSScopeProperty *dprop = NewScopeProperty(cx->runtime);
1101 if (!dprop) {
1102 JS_ReportOutOfMemory(cx);
1103 return NULL;
1106 dprop->id = child.id;
1107 dprop->getter = child.getter;
1108 dprop->setter = child.setter;
1109 dprop->slot = child.slot;
1110 dprop->attrs = child.attrs;
1111 dprop->flags = child.flags | JSScopeProperty::IN_DICTIONARY;
1112 dprop->shortid = child.shortid;
1113 dprop->shape = js_GenerateShape(cx, false);
1115 dprop->childp = NULL;
1116 insertDictionaryProperty(dprop, childp);
1117 updateFlags(dprop);
1118 return dprop;
1121 bool
1122 JSScope::toDictionaryMode(JSContext *cx, JSScopeProperty *&aprop)
1124 JS_ASSERT(!inDictionaryMode());
1126 JSScopeProperty **oldTable = table;
1127 uint32 saveRemovedCount = removedCount;
1128 if (oldTable) {
1129 int sizeLog2 = JS_DHASH_BITS - hashShift;
1130 JSScopeProperty **newTable = (JSScopeProperty **)
1131 js_calloc(JS_BIT(sizeLog2) * sizeof(JSScopeProperty *));
1133 if (!newTable) {
1134 JS_ReportOutOfMemory(cx);
1135 METER(toDictFails);
1136 return false;
1138 table = newTable;
1139 removedCount = 0;
1143 * We are committed from here on. If we fail due to OOM in the loop below,
1144 * we'll restore saveEntryCount, oldTable, oldLastProp.
1146 JSScopeProperty *oldLastProp = lastProp;
1147 lastProp = NULL;
1150 * Clear entryCount because JSScope::insertDictionaryProperty called from
1151 * JSScope::newDictionaryProperty bumps it.
1153 uint32 saveEntryCount = entryCount;
1154 entryCount = 0;
1156 for (JSScopeProperty *sprop = oldLastProp, **childp = &lastProp; sprop; sprop = sprop->parent) {
1157 JSScopeProperty *dprop = newDictionaryProperty(cx, *sprop, childp);
1158 if (!dprop) {
1159 entryCount = saveEntryCount;
1160 removedCount = saveRemovedCount;
1161 if (table)
1162 js_free(table);
1163 table = oldTable;
1164 lastProp = oldLastProp;
1165 METER(toDictFails);
1166 return false;
1169 if (table) {
1170 JSScopeProperty **spp = search(dprop->id, true);
1171 JS_ASSERT(!SPROP_FETCH(spp));
1172 SPROP_STORE_PRESERVING_COLLISION(spp, dprop);
1175 if (aprop == sprop)
1176 aprop = dprop;
1177 childp = &dprop->parent;
1180 if (oldTable)
1181 js_free(oldTable);
1182 setDictionaryMode();
1183 clearOwnShape();
1185 if (lastProp) {
1187 * This scope may get OWN_SHAPE set again, but for now its shape must
1188 * be the shape of its lastProp. If it is empty, its initial shape is
1189 * still valid. See JSScope::updateShape's definition in jsscope.h.
1191 shape = lastProp->shape;
1193 return true;
1196 JSScopeProperty *
1197 JSScope::addProperty(JSContext *cx, jsid id,
1198 JSPropertyOp getter, JSPropertyOp setter,
1199 uint32 slot, uintN attrs,
1200 uintN flags, intN shortid)
1202 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, this));
1203 CHECK_ANCESTOR_LINE(this, true);
1205 JS_ASSERT(!JSVAL_IS_NULL(id));
1206 JS_ASSERT_IF(attrs & JSPROP_GETTER, getter);
1207 JS_ASSERT_IF(attrs & JSPROP_SETTER, setter);
1208 JS_ASSERT_IF(!cx->runtime->gcRegenShapes,
1209 hasRegenFlag(cx->runtime->gcRegenShapesScopeFlag));
1212 * You can't add properties to a sealed scope. But note well that you can
1213 * change property attributes in a sealed scope, even though that replaces
1214 * a JSScopeProperty * in the scope's hash table -- but no id is added, so
1215 * the scope remains sealed.
1217 if (sealed()) {
1218 reportReadOnlyScope(cx);
1219 return NULL;
1222 /* Search for id with adding = true in order to claim its entry. */
1223 JSScopeProperty **spp = search(id, true);
1224 JS_ASSERT(!SPROP_FETCH(spp));
1225 return addPropertyHelper(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
1229 * Normalize stub getter and setter values for faster is-stub testing in the
1230 * SPROP_CALL_[GS]ETTER macros.
1232 static inline bool
1233 NormalizeGetterAndSetter(JSContext *cx, JSScope *scope,
1234 jsid id, uintN attrs, uintN flags,
1235 JSPropertyOp &getter,
1236 JSPropertyOp &setter)
1238 if (setter == JS_PropertyStub)
1239 setter = NULL;
1240 if (flags & JSScopeProperty::METHOD) {
1241 /* Here, getter is the method, a function object reference. */
1242 JS_ASSERT(getter);
1243 JS_ASSERT(!setter || setter == js_watch_set);
1244 JS_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
1245 } else {
1246 if (getter == JS_PropertyStub)
1247 getter = NULL;
1251 * Check for a watchpoint on a deleted property; if one exists, change
1252 * setter to js_watch_set or js_watch_set_wrapper.
1253 * XXXbe this could get expensive with lots of watchpoints...
1255 if (!JS_CLIST_IS_EMPTY(&cx->runtime->watchPointList) &&
1256 js_FindWatchPoint(cx->runtime, scope, id)) {
1257 setter = js_WrapWatchedSetter(cx, id, attrs, setter);
1258 if (!setter) {
1259 METER(wrapWatchFails);
1260 return false;
1263 return true;
1266 JSScopeProperty *
1267 JSScope::addPropertyHelper(JSContext *cx, jsid id,
1268 JSPropertyOp getter, JSPropertyOp setter,
1269 uint32 slot, uintN attrs,
1270 uintN flags, intN shortid,
1271 JSScopeProperty **spp)
1273 NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
1275 /* Check whether we need to grow, if the load factor is >= .75. */
1276 uint32 size = SCOPE_CAPACITY(this);
1277 if (entryCount + removedCount >= size - (size >> 2)) {
1278 int change = removedCount < size >> 2;
1279 if (!change)
1280 METER(compresses);
1281 else
1282 METER(grows);
1283 if (!changeTable(cx, change) && entryCount + removedCount == size - 1)
1284 return NULL;
1285 spp = search(id, true);
1286 JS_ASSERT(!SPROP_FETCH(spp));
1289 /* Find or create a property tree node labeled by our arguments. */
1290 JSScopeProperty *sprop;
1292 JSScopeProperty child;
1294 child.id = id;
1295 child.getter = getter;
1296 child.setter = setter;
1297 child.slot = slot;
1298 child.attrs = attrs;
1299 child.flags = flags;
1300 child.shortid = shortid;
1301 sprop = getChildProperty(cx, lastProp, child);
1304 if (sprop) {
1305 /* Store the tree node pointer in the table entry for id. */
1306 if (table)
1307 SPROP_STORE_PRESERVING_COLLISION(spp, sprop);
1308 CHECK_ANCESTOR_LINE(this, false);
1309 #ifdef DEBUG
1310 LIVE_SCOPE_METER(cx, ++cx->runtime->liveScopeProps);
1311 JS_RUNTIME_METER(cx->runtime, totalScopeProps);
1312 #endif
1315 * If we reach the hashing threshold, try to allocate this->table.
1316 * If we can't (a rare event, preceded by swapping to death on most
1317 * modern OSes), stick with linear search rather than whining about
1318 * this little set-back. Therefore we must test !this->table and
1319 * this->entryCount >= SCOPE_HASH_THRESHOLD, not merely whether the
1320 * entry count just reached the threshold.
1322 if (!table && entryCount >= SCOPE_HASH_THRESHOLD)
1323 (void) createTable(cx, false);
1325 METER(adds);
1326 return sprop;
1329 METER(addFails);
1330 return NULL;
1333 JSScopeProperty *
1334 JSScope::putProperty(JSContext *cx, jsid id,
1335 JSPropertyOp getter, JSPropertyOp setter,
1336 uint32 slot, uintN attrs,
1337 uintN flags, intN shortid)
1339 JSScopeProperty **spp, *sprop, *overwriting;
1341 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, this));
1342 CHECK_ANCESTOR_LINE(this, true);
1344 JS_ASSERT(!JSVAL_IS_NULL(id));
1345 JS_ASSERT_IF(attrs & JSPROP_GETTER, getter);
1346 JS_ASSERT_IF(attrs & JSPROP_SETTER, setter);
1348 JS_ASSERT_IF(!cx->runtime->gcRegenShapes,
1349 hasRegenFlag(cx->runtime->gcRegenShapesScopeFlag));
1351 if (sealed()) {
1352 reportReadOnlyScope(cx);
1353 return NULL;
1356 /* Search for id in order to claim its entry if table has been allocated. */
1357 spp = search(id, true);
1358 sprop = SPROP_FETCH(spp);
1359 if (!sprop)
1360 return addPropertyHelper(cx, id, getter, setter, slot, attrs, flags, shortid, spp);
1362 /* Property exists: JSScope::search must have returned a valid *spp. */
1363 JS_ASSERT(!SPROP_IS_REMOVED(*spp));
1364 overwriting = sprop;
1366 NormalizeGetterAndSetter(cx, this, id, attrs, flags, getter, setter);
1369 * If all property members match, this is a redundant add and we can
1370 * return early. If the caller wants to allocate a slot, but doesn't
1371 * care which slot, copy sprop->slot into slot so we can match sprop,
1372 * if all other members match.
1374 if (!(attrs & JSPROP_SHARED) &&
1375 slot == SPROP_INVALID_SLOT &&
1376 SPROP_HAS_VALID_SLOT(sprop, this)) {
1377 slot = sprop->slot;
1379 if (sprop->matchesParamsAfterId(getter, setter, slot, attrs, flags, shortid)) {
1380 METER(redundantPuts);
1381 return sprop;
1385 * If we are clearing sprop to force the existing property that it
1386 * describes to be overwritten, then we have to unlink sprop from the
1387 * ancestor line at this->lastProp.
1389 * If sprop is not lastProp and this scope is not in dictionary mode,
1390 * we must switch to dictionary mode so we can unlink the non-terminal
1391 * sprop without breaking anyone sharing the property lineage via the
1392 * runtime's property tree.
1394 if (sprop == lastProp && !inDictionaryMode()) {
1395 removeLastProperty();
1396 } else {
1397 if (!inDictionaryMode()) {
1398 if (!toDictionaryMode(cx, sprop))
1399 return NULL;
1400 spp = search(id, false);
1402 removeDictionaryProperty(sprop);
1406 * If we fail later on trying to find or create a new sprop, we will
1407 * restore *spp from |overwriting|. Note that we don't bother to keep
1408 * this->removedCount in sync, because we will fix up both *spp and
1409 * this->entryCount shortly.
1411 if (table)
1412 SPROP_STORE_PRESERVING_COLLISION(spp, NULL);
1413 CHECK_ANCESTOR_LINE(this, true);
1416 JSScopeProperty child;
1418 /* Find or create a property tree node labeled by our arguments. */
1419 child.id = id;
1420 child.getter = getter;
1421 child.setter = setter;
1422 child.slot = slot;
1423 child.attrs = attrs;
1424 child.flags = flags;
1425 child.shortid = shortid;
1426 sprop = getChildProperty(cx, lastProp, child);
1429 if (sprop) {
1430 CHECK_ANCESTOR_LINE(this, false);
1432 if (table) {
1433 /* Store the tree node pointer in the table entry for id. */
1434 SPROP_STORE_PRESERVING_COLLISION(spp, sprop);
1435 } else if (entryCount >= SCOPE_HASH_THRESHOLD) {
1436 /* See comment in JSScope::addPropertyHelper about ignoring OOM here. */
1437 (void) createTable(cx, false);
1440 METER(puts);
1441 return sprop;
1444 if (table)
1445 SPROP_STORE_PRESERVING_COLLISION(spp, overwriting);
1446 ++entryCount;
1447 CHECK_ANCESTOR_LINE(this, true);
1448 METER(putFails);
1449 return NULL;
1452 JSScopeProperty *
1453 JSScope::changeProperty(JSContext *cx, JSScopeProperty *sprop,
1454 uintN attrs, uintN mask,
1455 JSPropertyOp getter, JSPropertyOp setter)
1457 JSScopeProperty child, *newsprop;
1459 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, this));
1460 CHECK_ANCESTOR_LINE(this, true);
1462 JS_ASSERT(!JSVAL_IS_NULL(sprop->id));
1463 JS_ASSERT(hasProperty(sprop));
1465 attrs |= sprop->attrs & mask;
1467 /* Allow only shared (slot-less) => unshared (slot-full) transition. */
1468 JS_ASSERT(!((attrs ^ sprop->attrs) & JSPROP_SHARED) ||
1469 !(attrs & JSPROP_SHARED));
1471 /* Don't allow method properties to be changed to have a getter. */
1472 JS_ASSERT_IF(getter != sprop->getter, !sprop->isMethod());
1474 if (getter == JS_PropertyStub)
1475 getter = NULL;
1476 if (setter == JS_PropertyStub)
1477 setter = NULL;
1478 if (sprop->attrs == attrs &&
1479 sprop->getter == getter &&
1480 sprop->setter == setter) {
1481 return sprop;
1484 child.id = sprop->id;
1485 child.getter = getter;
1486 child.setter = setter;
1487 child.slot = sprop->slot;
1488 child.attrs = attrs;
1489 child.flags = sprop->flags;
1490 child.shortid = sprop->shortid;
1492 if (inDictionaryMode()) {
1493 removeDictionaryProperty(sprop);
1494 newsprop = newDictionaryProperty(cx, child, &lastProp);
1495 if (newsprop) {
1496 if (table) {
1497 JSScopeProperty **spp = search(sprop->id, false);
1498 SPROP_STORE_PRESERVING_COLLISION(spp, newsprop);
1500 updateShape(cx);
1502 } else if (sprop == lastProp) {
1503 newsprop = getChildProperty(cx, sprop->parent, child);
1504 if (newsprop) {
1505 if (table) {
1506 JSScopeProperty **spp = search(sprop->id, false);
1507 JS_ASSERT(SPROP_FETCH(spp) == sprop);
1508 SPROP_STORE_PRESERVING_COLLISION(spp, newsprop);
1510 CHECK_ANCESTOR_LINE(this, true);
1512 } else {
1514 * Let JSScope::putProperty handle this |overwriting| case, including
1515 * the conservation of sprop->slot (if it's valid). We must not call
1516 * JSScope::removeProperty because it will free a valid sprop->slot and
1517 * JSScope::putProperty won't re-allocate it.
1519 newsprop = putProperty(cx, child.id, child.getter, child.setter, child.slot,
1520 child.attrs, child.flags, child.shortid);
1523 #ifdef DEBUG
1524 if (newsprop)
1525 METER(changes);
1526 else
1527 METER(changeFails);
1528 #endif
1529 return newsprop;
1532 bool
1533 JSScope::removeProperty(JSContext *cx, jsid id)
1535 JSScopeProperty **spp, *sprop;
1536 uint32 size;
1538 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx, this));
1539 CHECK_ANCESTOR_LINE(this, true);
1540 if (sealed()) {
1541 reportReadOnlyScope(cx);
1542 return false;
1545 spp = search(id, false);
1546 sprop = SPROP_CLEAR_COLLISION(*spp);
1547 if (!sprop) {
1548 METER(uselessRemoves);
1549 return true;
1552 /* If sprop is not the last property added, switch to dictionary mode. */
1553 if (sprop != lastProp) {
1554 if (!inDictionaryMode()) {
1555 if (!toDictionaryMode(cx, sprop))
1556 return false;
1557 spp = search(id, false);
1559 JS_ASSERT(SPROP_FETCH(spp) == sprop);
1562 /* First, if sprop is unshared and not cleared, free its slot number. */
1563 if (SPROP_HAS_VALID_SLOT(sprop, this)) {
1564 js_FreeSlot(cx, object, sprop->slot);
1565 JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
1568 /* Next, remove id by setting its entry to a removed or free sentinel. */
1569 if (SPROP_HAD_COLLISION(*spp)) {
1570 JS_ASSERT(table);
1571 *spp = SPROP_REMOVED;
1572 ++removedCount;
1573 } else {
1574 METER(removeFrees);
1575 if (table) {
1576 *spp = NULL;
1577 #ifdef DEBUG
1579 * Check the consistency of the table but limit the number of
1580 * checks not to alter significantly the complexity of the delete
1581 * in debug builds, see bug 534493.
1583 JSScopeProperty *aprop = lastProp;
1584 for (unsigned n = 50; aprop && n != 0; aprop = aprop->parent, --n)
1585 JS_ASSERT_IF(aprop != sprop, hasProperty(aprop));
1586 #endif
1589 LIVE_SCOPE_METER(cx, --cx->runtime->liveScopeProps);
1591 if (inDictionaryMode()) {
1593 * Remove sprop from its scope-owned doubly linked list, setting this
1594 * scope's OWN_SHAPE flag first if sprop is lastProp so updateShape(cx)
1595 * after this if-else will generate a fresh shape for this scope.
1597 if (sprop != lastProp)
1598 setOwnShape();
1599 removeDictionaryProperty(sprop);
1600 } else {
1601 JS_ASSERT(sprop == lastProp);
1602 removeLastProperty();
1604 updateShape(cx);
1605 CHECK_ANCESTOR_LINE(this, true);
1607 /* Last, consider shrinking this->table if its load factor is <= .25. */
1608 size = SCOPE_CAPACITY(this);
1609 if (size > MIN_SCOPE_SIZE && entryCount <= size >> 2) {
1610 METER(shrinks);
1611 (void) changeTable(cx, -1);
1614 METER(removes);
1615 return true;
1618 void
1619 JSScope::clear(JSContext *cx)
1621 CHECK_ANCESTOR_LINE(this, true);
1622 LIVE_SCOPE_METER(cx, cx->runtime->liveScopeProps -= entryCount);
1624 if (table)
1625 js_free(table);
1626 clearDictionaryMode();
1627 clearOwnShape();
1628 LeaveTraceIfGlobalObject(cx, object);
1630 JSClass *clasp = object->getClass();
1631 JSObject *proto = object->getProto();
1632 JSEmptyScope *emptyScope;
1633 uint32 newShape;
1634 if (proto &&
1635 OBJ_IS_NATIVE(proto) &&
1636 (emptyScope = OBJ_SCOPE(proto)->emptyScope) &&
1637 emptyScope->clasp == clasp) {
1638 newShape = emptyScope->shape;
1639 } else {
1640 newShape = js_GenerateShape(cx, false);
1642 initMinimal(cx, newShape);
1644 JS_ATOMIC_INCREMENT(&cx->runtime->propertyRemovals);
1647 void
1648 JSScope::brandingShapeChange(JSContext *cx, uint32 slot, jsval v)
1650 generateOwnShape(cx);
1653 void
1654 JSScope::deletingShapeChange(JSContext *cx, JSScopeProperty *sprop)
1656 JS_ASSERT(!JSVAL_IS_NULL(sprop->id));
1657 generateOwnShape(cx);
1660 bool
1661 JSScope::methodShapeChange(JSContext *cx, JSScopeProperty *sprop, jsval toval)
1663 JS_ASSERT(!JSVAL_IS_NULL(sprop->id));
1664 if (sprop->isMethod()) {
1665 #ifdef DEBUG
1666 jsval prev = LOCKED_OBJ_GET_SLOT(object, sprop->slot);
1667 JS_ASSERT(sprop->methodValue() == prev);
1668 JS_ASSERT(hasMethodBarrier());
1669 JS_ASSERT(object->getClass() == &js_ObjectClass);
1670 JS_ASSERT(!sprop->setter || sprop->setter == js_watch_set);
1671 #endif
1674 * Pass null to make a stub getter, but pass along sprop->setter to
1675 * preserve watchpoints. Clear JSScopeProperty::METHOD from flags as we
1676 * are despecializing from a method memoized in the property tree to a
1677 * plain old function-valued property.
1679 sprop = putProperty(cx, sprop->id, NULL, sprop->setter, sprop->slot,
1680 sprop->attrs,
1681 sprop->getFlags() & ~JSScopeProperty::METHOD,
1682 sprop->shortid);
1683 if (!sprop)
1684 return false;
1687 generateOwnShape(cx);
1688 return true;
1691 bool
1692 JSScope::methodShapeChange(JSContext *cx, uint32 slot, jsval toval)
1694 if (!hasMethodBarrier()) {
1695 generateOwnShape(cx);
1696 } else {
1697 for (JSScopeProperty *sprop = lastProp; sprop; sprop = sprop->parent) {
1698 JS_ASSERT(!JSVAL_IS_NULL(sprop->id));
1699 if (sprop->slot == slot)
1700 return methodShapeChange(cx, sprop, toval);
1703 return true;
1706 void
1707 JSScope::protoShapeChange(JSContext *cx)
1709 generateOwnShape(cx);
1712 void
1713 JSScope::sealingShapeChange(JSContext *cx)
1715 generateOwnShape(cx);
1718 void
1719 JSScope::shadowingShapeChange(JSContext *cx, JSScopeProperty *sprop)
1721 JS_ASSERT(!JSVAL_IS_NULL(sprop->id));
1722 generateOwnShape(cx);
1725 void
1726 js_TraceId(JSTracer *trc, jsid id)
1728 jsval v;
1730 v = ID_TO_VALUE(id);
1731 JS_CALL_VALUE_TRACER(trc, v, "id");
1734 #ifdef DEBUG
1735 static void
1736 PrintPropertyGetterOrSetter(JSTracer *trc, char *buf, size_t bufsize)
1738 JSScopeProperty *sprop;
1739 jsid id;
1740 size_t n;
1741 const char *name;
1743 JS_ASSERT(trc->debugPrinter == PrintPropertyGetterOrSetter);
1744 sprop = (JSScopeProperty *)trc->debugPrintArg;
1745 id = sprop->id;
1746 JS_ASSERT(!JSVAL_IS_NULL(id));
1747 name = trc->debugPrintIndex ? js_setter_str : js_getter_str;
1749 if (JSID_IS_ATOM(id)) {
1750 n = js_PutEscapedString(buf, bufsize - 1,
1751 ATOM_TO_STRING(JSID_TO_ATOM(id)), 0);
1752 if (n < bufsize - 1)
1753 JS_snprintf(buf + n, bufsize - n, " %s", name);
1754 } else if (JSID_IS_INT(sprop->id)) {
1755 JS_snprintf(buf, bufsize, "%d %s", JSID_TO_INT(id), name);
1756 } else {
1757 JS_snprintf(buf, bufsize, "<object> %s", name);
1761 static void
1762 PrintPropertyMethod(JSTracer *trc, char *buf, size_t bufsize)
1764 JSScopeProperty *sprop;
1765 jsid id;
1766 size_t n;
1768 JS_ASSERT(trc->debugPrinter == PrintPropertyMethod);
1769 sprop = (JSScopeProperty *)trc->debugPrintArg;
1770 id = sprop->id;
1771 JS_ASSERT(!JSVAL_IS_NULL(id));
1773 JS_ASSERT(JSID_IS_ATOM(id));
1774 n = js_PutEscapedString(buf, bufsize - 1, ATOM_TO_STRING(JSID_TO_ATOM(id)), 0);
1775 if (n < bufsize - 1)
1776 JS_snprintf(buf + n, bufsize - n, " method");
1778 #endif
1780 void
1781 JSScopeProperty::trace(JSTracer *trc)
1783 if (IS_GC_MARKING_TRACER(trc))
1784 mark();
1785 js_TraceId(trc, id);
1787 #if JS_HAS_GETTER_SETTER
1788 if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
1789 if ((attrs & JSPROP_GETTER) && getter) {
1790 JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 0);
1791 js_CallGCMarker(trc, getterObject(), JSTRACE_OBJECT);
1793 if ((attrs & JSPROP_SETTER) && setter) {
1794 JS_SET_TRACING_DETAILS(trc, PrintPropertyGetterOrSetter, this, 1);
1795 js_CallGCMarker(trc, setterObject(), JSTRACE_OBJECT);
1798 #endif /* JS_HAS_GETTER_SETTER */
1800 if (isMethod()) {
1801 JS_SET_TRACING_DETAILS(trc, PrintPropertyMethod, this, 0);
1802 js_CallGCMarker(trc, methodObject(), JSTRACE_OBJECT);
1806 #ifdef DEBUG
1808 static void
1809 MeterKidCount(JSBasicStats *bs, uintN nkids)
1811 JS_BASIC_STATS_ACCUM(bs, nkids);
1812 bs->hist[JS_MIN(nkids, 10)]++;
1815 static void
1816 MeterPropertyTree(JSBasicStats *bs, JSScopeProperty *node)
1818 uintN i, nkids;
1819 JSScopeProperty *kids, *kid;
1820 PropTreeKidsChunk *chunk;
1822 nkids = 0;
1823 kids = node->kids;
1824 if (kids) {
1825 if (KIDS_IS_CHUNKY(kids)) {
1826 for (chunk = KIDS_TO_CHUNK(kids); chunk; chunk = chunk->next) {
1827 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
1828 kid = chunk->kids[i];
1829 if (!kid)
1830 break;
1831 MeterPropertyTree(bs, kid);
1832 nkids++;
1835 } else {
1836 MeterPropertyTree(bs, kids);
1837 nkids = 1;
1841 MeterKidCount(bs, nkids);
1844 static JSDHashOperator
1845 js_MeterPropertyTree(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
1846 void *arg)
1848 JSPropertyTreeEntry *entry = (JSPropertyTreeEntry *)hdr;
1849 JSBasicStats *bs = (JSBasicStats *)arg;
1851 MeterPropertyTree(bs, entry->child);
1852 return JS_DHASH_NEXT;
1855 void
1856 JSScopeProperty::dump(JSContext *cx, FILE *fp)
1858 JS_ASSERT(!JSVAL_IS_NULL(id));
1860 jsval idval = ID_TO_VALUE(id);
1861 if (JSVAL_IS_INT(idval)) {
1862 fprintf(fp, "[%ld]", (long) JSVAL_TO_INT(idval));
1863 } else {
1864 JSString *str;
1865 if (JSVAL_IS_STRING(idval)) {
1866 str = JSVAL_TO_STRING(idval);
1867 } else {
1868 JS_ASSERT(JSVAL_IS_OBJECT(idval));
1869 str = js_ValueToString(cx, idval);
1870 fputs("object ", fp);
1872 if (!str)
1873 fputs("<error>", fp);
1874 else
1875 js_FileEscapedString(fp, str, '"');
1878 fprintf(fp, " g/s %p/%p slot %u attrs %x ",
1879 JS_FUNC_TO_DATA_PTR(void *, getter),
1880 JS_FUNC_TO_DATA_PTR(void *, setter),
1881 slot, attrs);
1882 if (attrs) {
1883 int first = 1;
1884 fputs("(", fp);
1885 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(" " #display + first, fp), first = 0
1886 DUMP_ATTR(ENUMERATE, enumerate);
1887 DUMP_ATTR(READONLY, readonly);
1888 DUMP_ATTR(PERMANENT, permanent);
1889 DUMP_ATTR(GETTER, getter);
1890 DUMP_ATTR(SETTER, setter);
1891 DUMP_ATTR(SHARED, shared);
1892 #undef DUMP_ATTR
1893 fputs(") ", fp);
1896 fprintf(fp, "flags %x ", flags);
1897 if (flags) {
1898 int first = 1;
1899 fputs("(", fp);
1900 #define DUMP_FLAG(name, display) if (flags & name) fputs(" " #display + first, fp), first = 0
1901 DUMP_FLAG(ALIAS, alias);
1902 DUMP_FLAG(HAS_SHORTID, has_shortid);
1903 DUMP_FLAG(METHOD, method);
1904 DUMP_FLAG(MARK, mark);
1905 DUMP_FLAG(SHAPE_REGEN, shape_regen);
1906 DUMP_FLAG(IN_DICTIONARY, in_dictionary);
1907 #undef DUMP_FLAG
1908 fputs(") ", fp);
1911 fprintf(fp, "shortid %d\n", shortid);
1914 void
1915 JSScopeProperty::dumpSubtree(JSContext *cx, int level, FILE *fp)
1917 fprintf(fp, "%*sid ", level, "");
1918 dump(cx, fp);
1920 if (kids) {
1921 ++level;
1922 if (KIDS_IS_CHUNKY(kids)) {
1923 PropTreeKidsChunk *chunk = KIDS_TO_CHUNK(kids);
1924 do {
1925 for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
1926 JSScopeProperty *kid = chunk->kids[i];
1927 if (!kid)
1928 break;
1929 JS_ASSERT(kid->parent == this);
1930 kid->dumpSubtree(cx, level, fp);
1932 } while ((chunk = chunk->next) != NULL);
1933 } else {
1934 JSScopeProperty *kid = kids;
1935 JS_ASSERT(kid->parent == this);
1936 kid->dumpSubtree(cx, level, fp);
1941 #endif /* DEBUG */
1943 void
1944 js_SweepScopeProperties(JSContext *cx)
1946 JSRuntime *rt = cx->runtime;
1947 JSArena **ap, *a;
1948 JSScopeProperty *limit, *sprop, *parent, *kids, *kid;
1949 uintN liveCount;
1950 PropTreeKidsChunk *chunk, *nextChunk, *freeChunk;
1951 uintN i;
1953 #ifdef DEBUG
1954 JSBasicStats bs;
1955 uint32 livePropCapacity = 0, totalLiveCount = 0;
1956 static FILE *logfp;
1957 if (!logfp) {
1958 if (const char *filename = getenv("JS_PROPTREE_STATFILE"))
1959 logfp = fopen(filename, "w");
1962 if (logfp) {
1963 JS_BASIC_STATS_INIT(&bs);
1964 MeterKidCount(&bs, rt->propertyTreeHash.entryCount);
1965 JS_DHashTableEnumerate(&rt->propertyTreeHash, js_MeterPropertyTree, &bs);
1967 double props, nodes, mean, sigma;
1969 props = rt->liveScopePropsPreSweep;
1970 nodes = rt->livePropTreeNodes;
1971 JS_ASSERT(nodes == bs.sum);
1972 mean = JS_MeanAndStdDevBS(&bs, &sigma);
1974 fprintf(logfp,
1975 "props %g nodes %g beta %g meankids %g sigma %g max %u\n",
1976 props, nodes, nodes / props, mean, sigma, bs.max);
1978 JS_DumpHistogram(&bs, logfp);
1980 #endif
1982 ap = &rt->propertyArenaPool.first.next;
1983 while ((a = *ap) != NULL) {
1984 limit = (JSScopeProperty *) a->avail;
1985 liveCount = 0;
1986 for (sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++) {
1987 /* If the id is null, sprop is already on the freelist. */
1988 if (sprop->id == JSVAL_NULL)
1989 continue;
1992 * If the mark bit is set, sprop is alive, so clear the mark bit
1993 * and continue the while loop.
1995 * Regenerate sprop->shape if it hasn't already been refreshed
1996 * during the mark phase, when live scopes' lastProp members are
1997 * followed to update both scope->shape and lastProp->shape.
1999 if (sprop->marked()) {
2000 sprop->clearMark();
2001 if (rt->gcRegenShapes) {
2002 if (sprop->hasRegenFlag())
2003 sprop->clearRegenFlag();
2004 else
2005 sprop->shape = js_RegenerateShapeForGC(cx);
2007 liveCount++;
2008 continue;
2011 if (!sprop->inDictionary()) {
2012 /* Ok, sprop is garbage to collect: unlink it from its parent. */
2013 freeChunk = RemovePropertyTreeChild(rt, sprop);
2016 * Take care to reparent all sprop's kids to their grandparent.
2017 * InsertPropertyTreeChild can potentially fail for two reasons:
2019 * 1. If parent is null, insertion into the root property hash
2020 * table may fail. We are forced to leave the kid out of the
2021 * table (as can already happen with duplicates) but ensure
2022 * that the kid's parent pointer is set to null.
2024 * 2. If parent is non-null, allocation of a new KidsChunk can
2025 * fail. To prevent this from happening, we allow sprops's own
2026 * chunks to be reused by the grandparent, which removes the
2027 * need for InsertPropertyTreeChild to malloc a new KidsChunk.
2029 * If sprop does not have chunky kids, then we rely on the
2030 * RemovePropertyTreeChild call above (which removed sprop from
2031 * its parent) either leaving one free entry, or else returning
2032 * the now-unused chunk to us so we can reuse it.
2034 * We also require the grandparent to have either no kids or else
2035 * chunky kids. A single non-chunky kid would force a new chunk to
2036 * be malloced in some cases (if sprop had a single non-chunky
2037 * kid, or a multiple of MAX_KIDS_PER_CHUNK kids). Note that
2038 * RemovePropertyTreeChild never converts a single-entry chunky
2039 * kid back to a non-chunky kid, so we are assured of correct
2040 * behaviour.
2042 kids = sprop->kids;
2043 if (kids) {
2044 sprop->kids = NULL;
2045 parent = sprop->parent;
2047 /* The grandparent must have either no kids or chunky kids. */
2048 JS_ASSERT(!parent || !parent->kids ||
2049 KIDS_IS_CHUNKY(parent->kids));
2050 if (KIDS_IS_CHUNKY(kids)) {
2051 chunk = KIDS_TO_CHUNK(kids);
2052 do {
2053 nextChunk = chunk->next;
2054 chunk->next = NULL;
2055 for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
2056 kid = chunk->kids[i];
2057 if (!kid)
2058 break;
2059 JS_ASSERT(kid->parent == sprop);
2062 * Clear a space in the kids array for possible
2063 * re-use by InsertPropertyTreeChild.
2065 chunk->kids[i] = NULL;
2066 if (!InsertPropertyTreeChild(rt, parent, kid, chunk)) {
2068 * This can happen only if we failed to add an
2069 * entry to the root property hash table.
2071 JS_ASSERT(!parent);
2072 kid->parent = NULL;
2075 if (!chunk->kids[0]) {
2076 /* The chunk wasn't reused, so we must free it. */
2077 DestroyPropTreeKidsChunk(rt, chunk);
2079 } while ((chunk = nextChunk) != NULL);
2080 } else {
2081 kid = kids;
2082 if (!InsertPropertyTreeChild(rt, parent, kid, freeChunk)) {
2084 * This can happen only if we failed to add an entry
2085 * to the root property hash table.
2087 JS_ASSERT(!parent);
2088 kid->parent = NULL;
2093 if (freeChunk && !freeChunk->kids[0]) {
2094 /* The chunk wasn't reused, so we must free it. */
2095 DestroyPropTreeKidsChunk(rt, freeChunk);
2099 /* Clear id so we know (above) that sprop is on the freelist. */
2100 sprop->id = JSVAL_NULL;
2101 FREENODE_INSERT(rt->propertyFreeList, sprop);
2102 JS_RUNTIME_UNMETER(rt, livePropTreeNodes);
2105 /* If a contains no live properties, return it to the malloc heap. */
2106 if (liveCount == 0) {
2107 for (sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++)
2108 FREENODE_REMOVE(sprop);
2109 JS_ARENA_DESTROY(&rt->propertyArenaPool, a, ap);
2110 } else {
2111 #ifdef DEBUG
2112 livePropCapacity += limit - (JSScopeProperty *) a->base;
2113 totalLiveCount += liveCount;
2114 #endif
2115 ap = &a->next;
2119 #ifdef DEBUG
2120 if (logfp) {
2121 fprintf(logfp,
2122 "\nProperty tree stats for gcNumber %lu\n",
2123 (unsigned long) rt->gcNumber);
2125 fprintf(logfp, "arenautil %g%%\n",
2126 (totalLiveCount && livePropCapacity)
2127 ? (totalLiveCount * 100.0) / livePropCapacity
2128 : 0.0);
2130 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
2132 fprintf(logfp,
2133 "Scope search stats:\n"
2134 " searches: %6u\n"
2135 " hits: %6u %5.2f%% of searches\n"
2136 " misses: %6u %5.2f%%\n"
2137 " hashes: %6u %5.2f%%\n"
2138 " steps: %6u %5.2f%% %5.2f%% of hashes\n"
2139 " stepHits: %6u %5.2f%% %5.2f%%\n"
2140 " stepMisses: %6u %5.2f%% %5.2f%%\n"
2141 " tableAllocFails %6u\n"
2142 " toDictFails %6u\n"
2143 " wrapWatchFails %6u\n"
2144 " adds: %6u\n"
2145 " addFails: %6u\n"
2146 " puts: %6u\n"
2147 " redundantPuts: %6u\n"
2148 " putFails: %6u\n"
2149 " changes: %6u\n"
2150 " changeFails: %6u\n"
2151 " compresses: %6u\n"
2152 " grows: %6u\n"
2153 " removes: %6u\n"
2154 " removeFrees: %6u\n"
2155 " uselessRemoves: %6u\n"
2156 " shrinks: %6u\n",
2157 js_scope_stats.searches,
2158 js_scope_stats.hits, RATE(hits, searches),
2159 js_scope_stats.misses, RATE(misses, searches),
2160 js_scope_stats.hashes, RATE(hashes, searches),
2161 js_scope_stats.steps, RATE(steps, searches), RATE(steps, hashes),
2162 js_scope_stats.stepHits,
2163 RATE(stepHits, searches), RATE(stepHits, hashes),
2164 js_scope_stats.stepMisses,
2165 RATE(stepMisses, searches), RATE(stepMisses, hashes),
2166 js_scope_stats.tableAllocFails,
2167 js_scope_stats.toDictFails,
2168 js_scope_stats.wrapWatchFails,
2169 js_scope_stats.adds,
2170 js_scope_stats.addFails,
2171 js_scope_stats.puts,
2172 js_scope_stats.redundantPuts,
2173 js_scope_stats.putFails,
2174 js_scope_stats.changes,
2175 js_scope_stats.changeFails,
2176 js_scope_stats.compresses,
2177 js_scope_stats.grows,
2178 js_scope_stats.removes,
2179 js_scope_stats.removeFrees,
2180 js_scope_stats.uselessRemoves,
2181 js_scope_stats.shrinks);
2183 #undef RATE
2185 fflush(logfp);
2188 if (const char *filename = getenv("JS_PROPTREE_DUMPFILE")) {
2189 char pathname[1024];
2190 JS_snprintf(pathname, sizeof pathname, "%s.%lu", filename, (unsigned long)rt->gcNumber);
2191 FILE *dumpfp = fopen(pathname, "w");
2192 if (dumpfp) {
2193 JSPropertyTreeEntry *pte, *end;
2195 pte = (JSPropertyTreeEntry *) rt->propertyTreeHash.entryStore;
2196 end = pte + JS_DHASH_TABLE_SIZE(&rt->propertyTreeHash);
2197 while (pte < end) {
2198 if (pte->child)
2199 pte->child->dumpSubtree(cx, 0, dumpfp);
2200 pte++;
2202 fclose(dumpfp);
2205 #endif /* DEBUG */
2208 bool
2209 js_InitPropertyTree(JSRuntime *rt)
2211 if (!JS_DHashTableInit(&rt->propertyTreeHash, &PropertyTreeHashOps, NULL,
2212 sizeof(JSPropertyTreeEntry), JS_DHASH_MIN_SIZE)) {
2213 rt->propertyTreeHash.ops = NULL;
2214 return false;
2216 JS_InitArenaPool(&rt->propertyArenaPool, "properties",
2217 256 * sizeof(JSScopeProperty), sizeof(void *), NULL);
2218 return true;
2221 void
2222 js_FinishPropertyTree(JSRuntime *rt)
2224 if (rt->propertyTreeHash.ops) {
2225 JS_DHashTableFinish(&rt->propertyTreeHash);
2226 rt->propertyTreeHash.ops = NULL;
2228 JS_FinishArenaPool(&rt->propertyArenaPool);