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
17 * The Original Code is Mozilla Communicator client code, released
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.
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 ***** */
52 #include "jsutil.h" /* Added by JSIFY */
63 js_GetMutableScope(JSContext
*cx
, JSObject
*obj
)
65 JSScope
*scope
, *newscope
;
69 scope
= OBJ_SCOPE(obj
);
70 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx
, scope
));
71 if (scope
->object
== obj
)
73 newscope
= js_NewScope(cx
, 0, scope
->map
.ops
, LOCKED_OBJ_GET_CLASS(obj
),
77 JS_LOCK_SCOPE(cx
, newscope
);
78 obj
->map
= js_HoldObjectMap(cx
, &newscope
->map
);
79 JS_ASSERT(newscope
->map
.freeslot
== JSSLOT_FREE(STOBJ_GET_CLASS(obj
)));
80 clasp
= STOBJ_GET_CLASS(obj
);
81 if (clasp
->reserveSlots
) {
82 freeslot
= JSSLOT_FREE(clasp
) + clasp
->reserveSlots(cx
, obj
);
83 if (freeslot
> STOBJ_NSLOTS(obj
))
84 freeslot
= STOBJ_NSLOTS(obj
);
85 if (newscope
->map
.freeslot
< freeslot
)
86 newscope
->map
.freeslot
= freeslot
;
88 scope
= (JSScope
*) js_DropObjectMap(cx
, &scope
->map
, obj
);
89 JS_TRANSFER_SCOPE_LOCK(cx
, scope
, newscope
);
94 * JSScope uses multiplicative hashing, _a la_ jsdhash.[ch], but specialized
95 * to minimize footprint. But if a scope has fewer than SCOPE_HASH_THRESHOLD
96 * entries, we use linear search and avoid allocating scope->table.
98 #define SCOPE_HASH_THRESHOLD 6
99 #define MIN_SCOPE_SIZE_LOG2 4
100 #define MIN_SCOPE_SIZE JS_BIT(MIN_SCOPE_SIZE_LOG2)
101 #define SCOPE_TABLE_NBYTES(n) ((n) * sizeof(JSScopeProperty *))
104 InitMinimalScope(JSScope
*scope
)
107 scope
->hashShift
= JS_DHASH_BITS
- MIN_SCOPE_SIZE_LOG2
;
108 scope
->entryCount
= scope
->removedCount
= 0;
110 scope
->lastProp
= NULL
;
114 CreateScopeTable(JSContext
*cx
, JSScope
*scope
, JSBool report
)
117 JSScopeProperty
*sprop
, **spp
;
119 JS_ASSERT(!scope
->table
);
120 JS_ASSERT(scope
->lastProp
);
122 if (scope
->entryCount
> SCOPE_HASH_THRESHOLD
) {
124 * Either we're creating a table for a large scope that was populated
125 * via property cache hit logic under JSOP_INITPROP, JSOP_SETNAME, or
126 * JSOP_SETPROP; or else calloc failed at least once already. In any
127 * event, let's try to grow, overallocating to hold at least twice the
128 * current population.
130 sizeLog2
= JS_CeilingLog2(2 * scope
->entryCount
);
131 scope
->hashShift
= JS_DHASH_BITS
- sizeLog2
;
133 JS_ASSERT(scope
->hashShift
== JS_DHASH_BITS
- MIN_SCOPE_SIZE_LOG2
);
134 sizeLog2
= MIN_SCOPE_SIZE_LOG2
;
137 scope
->table
= (JSScopeProperty
**)
138 calloc(JS_BIT(sizeLog2
), sizeof(JSScopeProperty
*));
141 JS_ReportOutOfMemory(cx
);
144 js_UpdateMallocCounter(cx
, JS_BIT(sizeLog2
) * sizeof(JSScopeProperty
*));
146 scope
->hashShift
= JS_DHASH_BITS
- sizeLog2
;
147 for (sprop
= scope
->lastProp
; sprop
; sprop
= sprop
->parent
) {
148 spp
= js_SearchScope(scope
, sprop
->id
, JS_TRUE
);
149 SPROP_STORE_PRESERVING_COLLISION(spp
, sprop
);
155 js_NewScope(JSContext
*cx
, jsrefcount nrefs
, JSObjectOps
*ops
, JSClass
*clasp
,
160 scope
= (JSScope
*) JS_malloc(cx
, sizeof(JSScope
));
164 js_InitObjectMap(&scope
->map
, nrefs
, ops
, clasp
);
167 InitMinimalScope(scope
);
170 js_InitTitle(cx
, &scope
->title
);
172 JS_RUNTIME_METER(cx
->runtime
, liveScopes
);
173 JS_RUNTIME_METER(cx
->runtime
, totalScopes
);
177 #ifdef DEBUG_SCOPE_COUNT
179 js_unlog_scope(JSScope
*scope
);
182 #if defined DEBUG || defined JS_DUMP_PROPTREE_STATS
184 # define LIVE_SCOPE_METER(cx,expr) JS_LOCK_RUNTIME_VOID(cx->runtime,expr)
186 # define LIVE_SCOPE_METER(cx,expr) /* nothing */
190 js_DestroyScope(JSContext
*cx
, JSScope
*scope
)
192 #ifdef DEBUG_SCOPE_COUNT
193 js_unlog_scope(scope
);
197 js_FinishTitle(cx
, &scope
->title
);
200 JS_free(cx
, scope
->table
);
202 LIVE_SCOPE_METER(cx
, cx
->runtime
->liveScopeProps
-= scope
->entryCount
);
203 JS_RUNTIME_UNMETER(cx
->runtime
, liveScopes
);
207 #ifdef JS_DUMP_PROPTREE_STATS
208 typedef struct JSScopeStats
{
215 jsrefcount stepMisses
;
217 jsrefcount redundantAdds
;
218 jsrefcount addFailures
;
219 jsrefcount changeFailures
;
220 jsrefcount compresses
;
223 jsrefcount removeFrees
;
224 jsrefcount uselessRemoves
;
228 JS_FRIEND_DATA(JSScopeStats
) js_scope_stats
= {0};
230 # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x)
232 # define METER(x) /* nothing */
235 JS_STATIC_ASSERT(sizeof(JSHashNumber
) == 4);
236 JS_STATIC_ASSERT(sizeof(jsid
) == JS_BYTES_PER_WORD
);
238 #if JS_BYTES_PER_WORD == 4
239 # define HASH_ID(id) ((JSHashNumber)(id))
240 #elif JS_BYTES_PER_WORD == 8
241 # define HASH_ID(id) ((JSHashNumber)(id) ^ (JSHashNumber)((id) >> 32))
243 # error "Unsupported configuration"
247 * Double hashing needs the second hash code to be relatively prime to table
248 * size, so we simply make hash2 odd. The inputs to multiplicative hash are
249 * the golden ratio, expressed as a fixed-point 32 bit fraction, and the id
252 #define SCOPE_HASH0(id) (HASH_ID(id) * JS_GOLDEN_RATIO)
253 #define SCOPE_HASH1(hash0,shift) ((hash0) >> (shift))
254 #define SCOPE_HASH2(hash0,log2,shift) ((((hash0) << (log2)) >> (shift)) | 1)
256 JS_FRIEND_API(JSScopeProperty
**)
257 js_SearchScope(JSScope
*scope
, jsid id
, JSBool adding
)
259 JSHashNumber hash0
, hash1
, hash2
;
260 int hashShift
, sizeLog2
;
261 JSScopeProperty
*stored
, *sprop
, **spp
, **firstRemoved
;
266 /* Not enough properties to justify hashing: search from lastProp. */
267 JS_ASSERT(!SCOPE_HAD_MIDDLE_DELETE(scope
));
268 for (spp
= &scope
->lastProp
; (sprop
= *spp
); spp
= &sprop
->parent
) {
269 if (sprop
->id
== id
) {
278 /* Compute the primary hash address. */
280 hash0
= SCOPE_HASH0(id
);
281 hashShift
= scope
->hashShift
;
282 hash1
= SCOPE_HASH1(hash0
, hashShift
);
283 spp
= scope
->table
+ hash1
;
285 /* Miss: return space for a new entry. */
287 if (SPROP_IS_FREE(stored
)) {
292 /* Hit: return entry. */
293 sprop
= SPROP_CLEAR_COLLISION(stored
);
294 if (sprop
&& sprop
->id
== id
) {
299 /* Collision: double hash. */
300 sizeLog2
= JS_DHASH_BITS
- hashShift
;
301 hash2
= SCOPE_HASH2(hash0
, sizeLog2
, hashShift
);
302 sizeMask
= JS_BITMASK(sizeLog2
);
304 /* Save the first removed entry pointer so we can recycle it if adding. */
305 if (SPROP_IS_REMOVED(stored
)) {
309 if (adding
&& !SPROP_HAD_COLLISION(stored
))
310 SPROP_FLAG_COLLISION(spp
, sprop
);
317 spp
= scope
->table
+ hash1
;
320 if (SPROP_IS_FREE(stored
)) {
322 return (adding
&& firstRemoved
) ? firstRemoved
: spp
;
325 sprop
= SPROP_CLEAR_COLLISION(stored
);
326 if (sprop
&& sprop
->id
== id
) {
331 if (SPROP_IS_REMOVED(stored
)) {
335 if (adding
&& !SPROP_HAD_COLLISION(stored
))
336 SPROP_FLAG_COLLISION(spp
, sprop
);
345 ChangeScope(JSContext
*cx
, JSScope
*scope
, int change
)
347 int oldlog2
, newlog2
;
348 uint32 oldsize
, newsize
, nbytes
;
349 JSScopeProperty
**table
, **oldtable
, **spp
, **oldspp
, *sprop
;
352 return CreateScopeTable(cx
, scope
, JS_TRUE
);
354 /* Grow, shrink, or compress by changing scope->table. */
355 oldlog2
= JS_DHASH_BITS
- scope
->hashShift
;
356 newlog2
= oldlog2
+ change
;
357 oldsize
= JS_BIT(oldlog2
);
358 newsize
= JS_BIT(newlog2
);
359 nbytes
= SCOPE_TABLE_NBYTES(newsize
);
360 table
= (JSScopeProperty
**) calloc(nbytes
, 1);
362 JS_ReportOutOfMemory(cx
);
366 /* Now that we have a new table allocated, update scope members. */
367 scope
->hashShift
= JS_DHASH_BITS
- newlog2
;
368 scope
->removedCount
= 0;
369 oldtable
= scope
->table
;
370 scope
->table
= table
;
372 /* Treat the above calloc as a JS_malloc, to match CreateScopeTable. */
373 cx
->runtime
->gcMallocBytes
+= nbytes
;
375 /* Copy only live entries, leaving removed and free ones behind. */
376 for (oldspp
= oldtable
; oldsize
!= 0; oldspp
++) {
377 sprop
= SPROP_FETCH(oldspp
);
379 spp
= js_SearchScope(scope
, sprop
->id
, JS_TRUE
);
380 JS_ASSERT(SPROP_IS_FREE(*spp
));
386 /* Finally, free the old table storage. */
387 JS_free(cx
, oldtable
);
392 * Take care to exclude the mark bits in case we're called from the GC.
394 #define SPROP_FLAGS_NOT_MATCHED (SPROP_MARK | SPROP_FLAG_SHAPE_REGEN)
397 js_HashScopeProperty(JSDHashTable
*table
, const void *key
)
399 const JSScopeProperty
*sprop
= (const JSScopeProperty
*)key
;
403 /* Accumulate from least to most random so the low bits are most random. */
405 gsop
= sprop
->getter
;
407 hash
= JS_ROTATE_LEFT32(hash
, 4) ^ (jsword
)gsop
;
408 gsop
= sprop
->setter
;
410 hash
= JS_ROTATE_LEFT32(hash
, 4) ^ (jsword
)gsop
;
412 hash
= JS_ROTATE_LEFT32(hash
, 4)
413 ^ (sprop
->flags
& ~SPROP_FLAGS_NOT_MATCHED
);
415 hash
= JS_ROTATE_LEFT32(hash
, 4) ^ sprop
->attrs
;
416 hash
= JS_ROTATE_LEFT32(hash
, 4) ^ sprop
->shortid
;
417 hash
= JS_ROTATE_LEFT32(hash
, 4) ^ sprop
->slot
;
418 hash
= JS_ROTATE_LEFT32(hash
, 4) ^ sprop
->id
;
422 #define SPROP_MATCH(sprop, child) \
423 SPROP_MATCH_PARAMS(sprop, (child)->id, (child)->getter, (child)->setter, \
424 (child)->slot, (child)->attrs, (child)->flags, \
427 #define SPROP_MATCH_PARAMS(sprop, aid, agetter, asetter, aslot, aattrs, \
429 ((sprop)->id == (aid) && \
430 SPROP_MATCH_PARAMS_AFTER_ID(sprop, agetter, asetter, aslot, aattrs, \
433 #define SPROP_MATCH_PARAMS_AFTER_ID(sprop, agetter, asetter, aslot, aattrs, \
435 ((sprop)->getter == (agetter) && \
436 (sprop)->setter == (asetter) && \
437 (sprop)->slot == (aslot) && \
438 (sprop)->attrs == (aattrs) && \
439 (((sprop)->flags ^ (aflags)) & ~SPROP_FLAGS_NOT_MATCHED) == 0 && \
440 (sprop)->shortid == (ashortid))
443 js_MatchScopeProperty(JSDHashTable
*table
,
444 const JSDHashEntryHdr
*hdr
,
447 const JSPropertyTreeEntry
*entry
= (const JSPropertyTreeEntry
*)hdr
;
448 const JSScopeProperty
*sprop
= entry
->child
;
449 const JSScopeProperty
*kprop
= (const JSScopeProperty
*)key
;
451 return SPROP_MATCH(sprop
, kprop
);
454 static const JSDHashTableOps PropertyTreeHashOps
= {
457 js_HashScopeProperty
,
458 js_MatchScopeProperty
,
459 JS_DHashMoveEntryStub
,
460 JS_DHashClearEntryStub
,
461 JS_DHashFinalizeStub
,
466 * A property tree node on rt->propertyFreeList overlays the following prefix
467 * struct on JSScopeProperty.
469 typedef struct FreeNode
{
471 JSScopeProperty
*next
;
472 JSScopeProperty
**prevp
;
475 #define FREENODE(sprop) ((FreeNode *) (sprop))
477 #define FREENODE_INSERT(list, sprop) \
479 FREENODE(sprop)->next = (list); \
480 FREENODE(sprop)->prevp = &(list); \
482 FREENODE(list)->prevp = &FREENODE(sprop)->next; \
486 #define FREENODE_REMOVE(sprop) \
488 *FREENODE(sprop)->prevp = FREENODE(sprop)->next; \
489 if (FREENODE(sprop)->next) \
490 FREENODE(FREENODE(sprop)->next)->prevp = FREENODE(sprop)->prevp; \
493 /* NB: Called with rt->gcLock held. */
494 static JSScopeProperty
*
495 NewScopeProperty(JSRuntime
*rt
)
497 JSScopeProperty
*sprop
;
499 sprop
= rt
->propertyFreeList
;
501 FREENODE_REMOVE(sprop
);
503 JS_ARENA_ALLOCATE_CAST(sprop
, JSScopeProperty
*,
504 &rt
->propertyArenaPool
,
505 sizeof(JSScopeProperty
));
510 JS_RUNTIME_METER(rt
, livePropTreeNodes
);
511 JS_RUNTIME_METER(rt
, totalPropTreeNodes
);
515 #define CHUNKY_KIDS_TAG ((jsuword)1)
516 #define KIDS_IS_CHUNKY(kids) ((jsuword)(kids) & CHUNKY_KIDS_TAG)
517 #define KIDS_TO_CHUNK(kids) ((PropTreeKidsChunk *) \
518 ((jsuword)(kids) & ~CHUNKY_KIDS_TAG))
519 #define CHUNK_TO_KIDS(chunk) ((JSScopeProperty *) \
520 ((jsuword)(chunk) | CHUNKY_KIDS_TAG))
521 #define MAX_KIDS_PER_CHUNK 10
522 #define CHUNK_HASH_THRESHOLD 30
524 typedef struct PropTreeKidsChunk PropTreeKidsChunk
;
526 struct PropTreeKidsChunk
{
527 JSScopeProperty
*kids
[MAX_KIDS_PER_CHUNK
];
529 PropTreeKidsChunk
*next
;
532 static PropTreeKidsChunk
*
533 NewPropTreeKidsChunk(JSRuntime
*rt
)
535 PropTreeKidsChunk
*chunk
;
537 chunk
= (PropTreeKidsChunk
*) calloc(1, sizeof *chunk
);
540 JS_ASSERT(((jsuword
)chunk
& CHUNKY_KIDS_TAG
) == 0);
541 JS_RUNTIME_METER(rt
, propTreeKidsChunks
);
546 DestroyPropTreeKidsChunk(JSRuntime
*rt
, PropTreeKidsChunk
*chunk
)
548 JS_RUNTIME_UNMETER(rt
, propTreeKidsChunks
);
550 JS_DHashTableDestroy(chunk
->table
);
554 /* NB: Called with rt->gcLock held. */
556 InsertPropertyTreeChild(JSRuntime
*rt
, JSScopeProperty
*parent
,
557 JSScopeProperty
*child
, PropTreeKidsChunk
*sweptChunk
)
560 JSPropertyTreeEntry
*entry
;
561 JSScopeProperty
**childp
, *kids
, *sprop
;
562 PropTreeKidsChunk
*chunk
, **chunkp
;
565 JS_ASSERT(!parent
|| child
->parent
!= parent
);
568 table
= &rt
->propertyTreeHash
;
569 entry
= (JSPropertyTreeEntry
*)
570 JS_DHashTableOperate(table
, child
, JS_DHASH_ADD
);
573 childp
= &entry
->child
;
579 * A "Duplicate child" case.
581 * We can't do away with child, as at least one live scope entry
582 * still points at it. What's more, that scope's lastProp chains
583 * through an ancestor line to reach child, and js_Enumerate and
584 * others count on this linkage. We must leave child out of the
585 * hash table, and not require it to be there when we eventually
586 * GC it (see RemovePropertyTreeChild, below).
588 * It is necessary to leave the duplicate child out of the hash
589 * table to preserve entry uniqueness. It is safe to leave the
590 * child out of the hash table (unlike the duplicate child cases
591 * below), because the child's parent link will be null, which
594 JS_ASSERT(sprop
!= child
&& SPROP_MATCH(sprop
, child
));
595 JS_RUNTIME_METER(rt
, duplicatePropTreeNodes
);
598 childp
= &parent
->kids
;
601 if (KIDS_IS_CHUNKY(kids
)) {
602 chunk
= KIDS_TO_CHUNK(kids
);
604 table
= chunk
->table
;
606 entry
= (JSPropertyTreeEntry
*)
607 JS_DHashTableOperate(table
, child
, JS_DHASH_ADD
);
611 entry
->child
= child
;
614 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
615 childp
= &chunk
->kids
[i
];
620 chunkp
= &chunk
->next
;
626 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
627 childp
= &chunk
->kids
[i
];
632 JS_ASSERT(sprop
!= child
);
633 if (SPROP_MATCH(sprop
, child
)) {
635 * Duplicate child, see comment above. In this
636 * case, we must let the duplicate be inserted at
637 * this level in the tree, so we keep iterating,
638 * looking for an empty slot in which to insert.
640 JS_ASSERT(sprop
!= child
);
641 JS_RUNTIME_METER(rt
, duplicatePropTreeNodes
);
644 chunkp
= &chunk
->next
;
645 } while ((chunk
= *chunkp
) != NULL
);
651 chunk
= NewPropTreeKidsChunk(rt
);
656 childp
= &chunk
->kids
[0];
659 JS_ASSERT(sprop
!= child
);
660 if (SPROP_MATCH(sprop
, child
)) {
662 * Duplicate child, see comment above. Once again, we
663 * must let duplicates created by deletion pile up in a
664 * kids-chunk-list, in order to find them when sweeping
665 * and thereby avoid dangling parent pointers.
667 JS_RUNTIME_METER(rt
, duplicatePropTreeNodes
);
672 chunk
= NewPropTreeKidsChunk(rt
);
676 parent
->kids
= CHUNK_TO_KIDS(chunk
);
677 chunk
->kids
[0] = sprop
;
678 childp
= &chunk
->kids
[1];
685 child
->parent
= parent
;
689 /* NB: Called with rt->gcLock held. */
690 static PropTreeKidsChunk
*
691 RemovePropertyTreeChild(JSRuntime
*rt
, JSScopeProperty
*child
)
693 PropTreeKidsChunk
*freeChunk
;
694 JSScopeProperty
*parent
, *kids
, *kid
;
696 PropTreeKidsChunk
*list
, *chunk
, **chunkp
, *lastChunk
;
698 JSPropertyTreeEntry
*entry
;
701 parent
= child
->parent
;
704 * Don't remove child if it is not in rt->propertyTreeHash, but only
705 * matches a root child in the table that has compatible members. See
706 * the "Duplicate child" comments in InsertPropertyTreeChild, above.
708 table
= &rt
->propertyTreeHash
;
711 if (KIDS_IS_CHUNKY(kids
)) {
712 list
= chunk
= KIDS_TO_CHUNK(kids
);
714 table
= chunk
->table
;
717 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
718 if (chunk
->kids
[i
] == child
) {
720 if (!lastChunk
->next
) {
725 chunkp
= &lastChunk
->next
;
727 } while (lastChunk
->next
);
729 for (; j
< MAX_KIDS_PER_CHUNK
; j
++) {
730 if (!lastChunk
->kids
[j
])
734 if (chunk
!= lastChunk
|| j
> i
)
735 chunk
->kids
[i
] = lastChunk
->kids
[j
];
736 lastChunk
->kids
[j
] = NULL
;
741 freeChunk
= lastChunk
;
747 chunkp
= &chunk
->next
;
748 } while ((chunk
= *chunkp
) != NULL
);
759 entry
= (JSPropertyTreeEntry
*)
760 JS_DHashTableOperate(table
, child
, JS_DHASH_LOOKUP
);
762 if (entry
->child
== child
)
763 JS_DHashTableRawRemove(table
, &entry
->hdr
);
768 static JSDHashTable
*
769 HashChunks(PropTreeKidsChunk
*chunk
, uintN n
)
773 JSScopeProperty
*sprop
;
774 JSPropertyTreeEntry
*entry
;
776 table
= JS_NewDHashTable(&PropertyTreeHashOps
, NULL
,
777 sizeof(JSPropertyTreeEntry
),
778 JS_DHASH_DEFAULT_CAPACITY(n
+ 1));
782 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
783 sprop
= chunk
->kids
[i
];
786 entry
= (JSPropertyTreeEntry
*)
787 JS_DHashTableOperate(table
, sprop
, JS_DHASH_ADD
);
788 entry
->child
= sprop
;
790 } while ((chunk
= chunk
->next
) != NULL
);
795 * Called without cx->runtime->gcLock held. This function acquires that lock
796 * only when inserting a new child. Thus there may be races to find or add a
797 * node that result in duplicates. We expect such races to be rare!
799 * We use rt->gcLock, not rt->rtLock, to allow the GC potentially to nest here
800 * under js_GenerateShape.
802 static JSScopeProperty
*
803 GetPropertyTreeChild(JSContext
*cx
, JSScopeProperty
*parent
,
804 JSScopeProperty
*child
)
808 JSPropertyTreeEntry
*entry
;
809 JSScopeProperty
*sprop
;
810 PropTreeKidsChunk
*chunk
;
818 table
= &rt
->propertyTreeHash
;
819 entry
= (JSPropertyTreeEntry
*)
820 JS_DHashTableOperate(table
, child
, JS_DHASH_ADD
);
824 sprop
= entry
->child
;
829 * Because chunks are appended at the end and never deleted except by
830 * the GC, we can search without taking the runtime's GC lock. We may
831 * miss a matching sprop added by another thread, and make a duplicate
832 * one, but that is an unlikely, therefore small, cost. The property
833 * tree has extremely low fan-out below its root in popular embeddings
834 * with real-world workloads.
836 * Patterns such as defining closures that capture a constructor's
837 * environment as getters or setters on the new object that is passed
838 * in as |this| can significantly increase fan-out below the property
839 * tree root -- see bug 335700 for details.
842 sprop
= parent
->kids
;
844 if (KIDS_IS_CHUNKY(sprop
)) {
845 chunk
= KIDS_TO_CHUNK(sprop
);
847 table
= chunk
->table
;
850 entry
= (JSPropertyTreeEntry
*)
851 JS_DHashTableOperate(table
, child
, JS_DHASH_LOOKUP
);
852 sprop
= entry
->child
;
857 goto locked_not_found
;
862 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
863 sprop
= chunk
->kids
[i
];
866 if (n
>= CHUNK_HASH_THRESHOLD
) {
867 chunk
= KIDS_TO_CHUNK(parent
->kids
);
869 table
= HashChunks(chunk
, n
);
874 JS_DHashTableDestroy(table
);
876 chunk
->table
= table
;
877 goto locked_not_found
;
883 if (SPROP_MATCH(sprop
, child
))
886 n
+= MAX_KIDS_PER_CHUNK
;
887 } while ((chunk
= chunk
->next
) != NULL
);
889 if (SPROP_MATCH(sprop
, child
))
900 * Call js_GenerateShape before the allocation to prevent collecting the
901 * new property when the shape generation triggers the GC.
903 shape
= js_GenerateShape(cx
, JS_TRUE
, NULL
);
905 sprop
= NewScopeProperty(rt
);
909 sprop
->id
= child
->id
;
910 sprop
->getter
= child
->getter
;
911 sprop
->setter
= child
->setter
;
912 sprop
->slot
= child
->slot
;
913 sprop
->attrs
= child
->attrs
;
914 sprop
->flags
= child
->flags
;
915 sprop
->shortid
= child
->shortid
;
916 sprop
->parent
= sprop
->kids
= NULL
;
917 sprop
->shape
= shape
;
920 entry
->child
= sprop
;
922 if (!InsertPropertyTreeChild(rt
, parent
, sprop
, NULL
))
932 JS_ReportOutOfMemory(cx
);
936 #ifdef DEBUG_notbrendan
937 #define CHECK_ANCESTOR_LINE(scope, sparse) \
939 if ((scope)->table) CheckAncestorLine(scope, sparse); \
943 CheckAncestorLine(JSScope
*scope
, JSBool sparse
)
946 JSScopeProperty
**spp
, **start
, **end
, *ancestorLine
, *sprop
, *aprop
;
947 uint32 entryCount
, ancestorCount
;
949 ancestorLine
= SCOPE_LAST_PROP(scope
);
951 JS_ASSERT(SCOPE_HAS_PROPERTY(scope
, ancestorLine
));
954 size
= SCOPE_CAPACITY(scope
);
955 start
= scope
->table
;
956 for (spp
= start
, end
= start
+ size
; spp
< end
; spp
++) {
957 sprop
= SPROP_FETCH(spp
);
960 for (aprop
= ancestorLine
; aprop
; aprop
= aprop
->parent
) {
967 JS_ASSERT(entryCount
== scope
->entryCount
);
970 for (sprop
= ancestorLine
; sprop
; sprop
= sprop
->parent
) {
971 if (SCOPE_HAD_MIDDLE_DELETE(scope
) &&
972 !SCOPE_HAS_PROPERTY(scope
, sprop
)) {
978 JS_ASSERT(ancestorCount
== scope
->entryCount
);
981 #define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */
985 ReportReadOnlyScope(JSContext
*cx
, JSScope
*scope
)
990 str
= js_ValueToString(cx
, OBJECT_TO_JSVAL(scope
->object
));
993 bytes
= js_GetStringBytes(cx
, str
);
996 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_READ_ONLY
, bytes
);
1000 js_AddScopeProperty(JSContext
*cx
, JSScope
*scope
, jsid id
,
1001 JSPropertyOp getter
, JSPropertyOp setter
, uint32 slot
,
1002 uintN attrs
, uintN flags
, intN shortid
)
1004 JSScopeProperty
**spp
, *sprop
, *overwriting
, **spvec
, **spp2
, child
;
1005 uint32 size
, splen
, i
;
1007 JSTempValueRooter tvr
;
1009 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx
, scope
));
1010 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1013 * You can't add properties to a sealed scope. But note well that you can
1014 * change property attributes in a sealed scope, even though that replaces
1015 * a JSScopeProperty * in the scope's hash table -- but no id is added, so
1016 * the scope remains sealed.
1018 if (SCOPE_IS_SEALED(scope
)) {
1019 ReportReadOnlyScope(cx
, scope
);
1024 * Normalize stub getter and setter values for faster is-stub testing in
1025 * the SPROP_CALL_[GS]ETTER macros.
1027 if (getter
== JS_PropertyStub
)
1029 if (setter
== JS_PropertyStub
)
1033 * Search for id in order to claim its entry, allocating a property tree
1034 * node if one doesn't already exist for our parameters.
1036 spp
= js_SearchScope(scope
, id
, JS_TRUE
);
1037 sprop
= overwriting
= SPROP_FETCH(spp
);
1039 JS_COUNT_OPERATION(cx
, JSOW_NEW_PROPERTY
);
1041 /* Check whether we need to grow, if the load factor is >= .75. */
1042 size
= SCOPE_CAPACITY(scope
);
1043 if (scope
->entryCount
+ scope
->removedCount
>= size
- (size
>> 2)) {
1044 if (scope
->removedCount
>= size
>> 2) {
1051 if (!ChangeScope(cx
, scope
, change
) &&
1052 scope
->entryCount
+ scope
->removedCount
== size
- 1) {
1056 spp
= js_SearchScope(scope
, id
, JS_TRUE
);
1057 JS_ASSERT(!SPROP_FETCH(spp
));
1060 /* Property exists: js_SearchScope must have returned a valid entry. */
1061 JS_ASSERT(!SPROP_IS_REMOVED(*spp
));
1064 * If all property members match, this is a redundant add and we can
1065 * return early. If the caller wants to allocate a slot, but doesn't
1066 * care which slot, copy sprop->slot into slot so we can match sprop,
1067 * if all other members match.
1069 if (!(attrs
& JSPROP_SHARED
) &&
1070 slot
== SPROP_INVALID_SLOT
&&
1071 SPROP_HAS_VALID_SLOT(sprop
, scope
)) {
1074 if (SPROP_MATCH_PARAMS_AFTER_ID(sprop
, getter
, setter
, slot
, attrs
,
1076 METER(redundantAdds
);
1081 * If we are clearing sprop to force an existing property to be
1082 * overwritten (apart from a duplicate formal parameter), we must
1083 * unlink it from the ancestor line at scope->lastProp, lazily if
1084 * sprop is not lastProp. And we must remove the entry at *spp,
1085 * precisely so the lazy "middle delete" fixup code further below
1086 * won't find sprop in scope->table, in spite of sprop being on
1087 * the ancestor line.
1089 * When we finally succeed in finding or creating a new sprop
1090 * and storing its pointer at *spp, we'll use the |overwriting|
1091 * local saved when we first looked up id to decide whether we're
1092 * indeed creating a new entry, or merely overwriting an existing
1095 if (sprop
== SCOPE_LAST_PROP(scope
)) {
1097 SCOPE_REMOVE_LAST_PROP(scope
);
1098 if (!SCOPE_HAD_MIDDLE_DELETE(scope
))
1100 sprop
= SCOPE_LAST_PROP(scope
);
1101 } while (sprop
&& !SCOPE_HAS_PROPERTY(scope
, sprop
));
1102 } else if (!SCOPE_HAD_MIDDLE_DELETE(scope
)) {
1104 * If we have no hash table yet, we need one now. The middle
1105 * delete code is simple-minded that way!
1107 if (!scope
->table
) {
1108 if (!CreateScopeTable(cx
, scope
, JS_TRUE
))
1110 spp
= js_SearchScope(scope
, id
, JS_TRUE
);
1111 sprop
= overwriting
= SPROP_FETCH(spp
);
1113 SCOPE_SET_MIDDLE_DELETE(scope
);
1115 SCOPE_MAKE_UNIQUE_SHAPE(cx
, scope
);
1118 * If we fail later on trying to find or create a new sprop, we will
1119 * goto fail_overwrite and restore *spp from |overwriting|. Note that
1120 * we don't bother to keep scope->removedCount in sync, because we'll
1121 * fix up *spp and scope->entryCount shortly, no matter how control
1122 * flow returns from this function.
1125 SPROP_STORE_PRESERVING_COLLISION(spp
, NULL
);
1126 scope
->entryCount
--;
1127 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1133 * If properties were deleted from the middle of the list starting at
1134 * scope->lastProp, we may need to fork the property tree and squeeze
1135 * all deleted properties out of scope's ancestor line. Otherwise we
1136 * risk adding a node with the same id as a "middle" node, violating
1137 * the rule that properties along an ancestor line have distinct ids.
1139 if (SCOPE_HAD_MIDDLE_DELETE(scope
)) {
1140 JS_ASSERT(scope
->table
);
1141 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1143 splen
= scope
->entryCount
;
1145 JS_ASSERT(scope
->lastProp
== NULL
);
1148 * Enumerate live entries in scope->table using a temporary
1149 * vector, by walking the (possibly sparse, due to deletions)
1150 * ancestor line from scope->lastProp.
1152 spvec
= (JSScopeProperty
**)
1153 JS_malloc(cx
, SCOPE_TABLE_NBYTES(splen
));
1155 goto fail_overwrite
;
1157 sprop
= SCOPE_LAST_PROP(scope
);
1161 * NB: test SCOPE_GET_PROPERTY, not SCOPE_HAS_PROPERTY --
1162 * the latter insists that sprop->id maps to sprop, while
1163 * the former simply tests whether sprop->id is bound in
1164 * scope. We must allow for duplicate formal parameters
1165 * along the ancestor line, and fork them as needed.
1167 if (!SCOPE_GET_PROPERTY(scope
, sprop
->id
))
1170 JS_ASSERT(sprop
!= overwriting
);
1173 * If our original splen estimate, scope->entryCount,
1174 * is less than the ancestor line height, there must
1175 * be duplicate formal parameters in this (function
1176 * object) scope. Count remaining ancestors in order
1179 JSScopeProperty
*tmp
= sprop
;
1181 if (SCOPE_GET_PROPERTY(scope
, tmp
->id
))
1183 } while ((tmp
= tmp
->parent
) != NULL
);
1184 spp2
= (JSScopeProperty
**)
1185 JS_realloc(cx
, spvec
, SCOPE_TABLE_NBYTES(splen
+i
));
1188 goto fail_overwrite
;
1192 memmove(spvec
+ i
, spvec
, SCOPE_TABLE_NBYTES(splen
));
1197 } while ((sprop
= sprop
->parent
) != NULL
);
1201 * Now loop forward through spvec, forking the property tree
1202 * whenever we see a "parent gap" due to deletions from scope.
1203 * NB: sprop is null on first entry to the loop body.
1206 if (spvec
[i
]->parent
== sprop
) {
1209 sprop
= GetPropertyTreeChild(cx
, sprop
, spvec
[i
]);
1212 goto fail_overwrite
;
1215 spp2
= js_SearchScope(scope
, sprop
->id
, JS_FALSE
);
1216 JS_ASSERT(SPROP_FETCH(spp2
) == spvec
[i
]);
1217 SPROP_STORE_PRESERVING_COLLISION(spp2
, sprop
);
1219 } while (++i
< splen
);
1223 * Now sprop points to the last property in scope, where the
1224 * ancestor line from sprop to the root is dense w.r.t. scope:
1225 * it contains no nodes not mapped by scope->table, apart from
1226 * any stinking ECMA-mandated duplicate formal parameters.
1228 scope
->lastProp
= sprop
;
1229 CHECK_ANCESTOR_LINE(scope
, JS_FALSE
);
1230 JS_RUNTIME_METER(cx
->runtime
, middleDeleteFixups
);
1233 SCOPE_CLR_MIDDLE_DELETE(scope
);
1237 * Aliases share another property's slot, passed in the |slot| param.
1238 * Shared properties have no slot. Unshared properties that do not
1239 * alias another property's slot get one here, but may lose it due to
1240 * a JS_ClearScope call.
1242 if (!(flags
& SPROP_IS_ALIAS
)) {
1243 if (attrs
& JSPROP_SHARED
) {
1244 slot
= SPROP_INVALID_SLOT
;
1247 * We may have set slot from a nearly-matching sprop, above.
1248 * If so, we're overwriting that nearly-matching sprop, so we
1249 * can reuse its slot -- we don't need to allocate a new one.
1250 * Similarly, we use a specific slot if provided by the caller.
1252 if (slot
== SPROP_INVALID_SLOT
&&
1253 !js_AllocSlot(cx
, scope
->object
, &slot
)) {
1254 goto fail_overwrite
;
1260 * Check for a watchpoint on a deleted property; if one exists, change
1261 * setter to js_watch_set.
1262 * XXXbe this could get expensive with lots of watchpoints...
1264 if (!JS_CLIST_IS_EMPTY(&cx
->runtime
->watchPointList
) &&
1265 js_FindWatchPoint(cx
->runtime
, scope
, id
)) {
1267 JS_PUSH_TEMP_ROOT_SPROP(cx
, overwriting
, &tvr
);
1268 setter
= js_WrapWatchedSetter(cx
, id
, attrs
, setter
);
1270 JS_POP_TEMP_ROOT(cx
, &tvr
);
1272 goto fail_overwrite
;
1275 /* Find or create a property tree node labeled by our arguments. */
1277 child
.getter
= getter
;
1278 child
.setter
= setter
;
1280 child
.attrs
= attrs
;
1281 child
.flags
= flags
;
1282 child
.shortid
= shortid
;
1283 sprop
= GetPropertyTreeChild(cx
, scope
->lastProp
, &child
);
1285 goto fail_overwrite
;
1288 * The scope's shape defaults to its last property's shape, but may
1289 * be regenerated later as the scope diverges (from the property cache
1290 * point of view) from the structural type associated with sprop.
1292 SCOPE_EXTEND_SHAPE(cx
, scope
, sprop
);
1294 /* Store the tree node pointer in the table entry for id. */
1296 SPROP_STORE_PRESERVING_COLLISION(spp
, sprop
);
1297 scope
->entryCount
++;
1298 scope
->lastProp
= sprop
;
1299 CHECK_ANCESTOR_LINE(scope
, JS_FALSE
);
1302 LIVE_SCOPE_METER(cx
, ++cx
->runtime
->liveScopeProps
);
1303 JS_RUNTIME_METER(cx
->runtime
, totalScopeProps
);
1308 * If we reach the hashing threshold, try to allocate scope->table.
1309 * If we can't (a rare event, preceded by swapping to death on most
1310 * modern OSes), stick with linear search rather than whining about
1311 * this little set-back. Therefore we must test !scope->table and
1312 * scope->entryCount >= SCOPE_HASH_THRESHOLD, not merely whether the
1313 * entry count just reached the threshold.
1315 if (!scope
->table
&& scope
->entryCount
>= SCOPE_HASH_THRESHOLD
)
1316 (void) CreateScopeTable(cx
, scope
, JS_FALSE
);
1325 * We may or may not have forked overwriting out of scope's ancestor
1326 * line, so we must check (the alternative is to set a flag above, but
1327 * that hurts the common, non-error case). If we did fork overwriting
1328 * out, we'll add it back at scope->lastProp. This means enumeration
1329 * order can change due to a failure to overwrite an id.
1330 * XXXbe very minor incompatibility
1332 for (sprop
= SCOPE_LAST_PROP(scope
); ; sprop
= sprop
->parent
) {
1334 sprop
= SCOPE_LAST_PROP(scope
);
1335 if (overwriting
->parent
== sprop
) {
1336 scope
->lastProp
= overwriting
;
1338 sprop
= GetPropertyTreeChild(cx
, sprop
, overwriting
);
1340 JS_ASSERT(sprop
!= overwriting
);
1341 scope
->lastProp
= sprop
;
1343 overwriting
= sprop
;
1347 if (sprop
== overwriting
)
1352 SPROP_STORE_PRESERVING_COLLISION(spp
, overwriting
);
1353 scope
->entryCount
++;
1355 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1362 js_ChangeScopePropertyAttrs(JSContext
*cx
, JSScope
*scope
,
1363 JSScopeProperty
*sprop
, uintN attrs
, uintN mask
,
1364 JSPropertyOp getter
, JSPropertyOp setter
)
1366 JSScopeProperty child
, *newsprop
, **spp
;
1368 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1370 /* Allow only shared (slot-less) => unshared (slot-full) transition. */
1371 attrs
|= sprop
->attrs
& mask
;
1372 JS_ASSERT(!((attrs
^ sprop
->attrs
) & JSPROP_SHARED
) ||
1373 !(attrs
& JSPROP_SHARED
));
1374 if (getter
== JS_PropertyStub
)
1376 if (setter
== JS_PropertyStub
)
1378 if (sprop
->attrs
== attrs
&&
1379 sprop
->getter
== getter
&&
1380 sprop
->setter
== setter
) {
1384 child
.id
= sprop
->id
;
1385 child
.getter
= getter
;
1386 child
.setter
= setter
;
1387 child
.slot
= sprop
->slot
;
1388 child
.attrs
= attrs
;
1389 child
.flags
= sprop
->flags
;
1390 child
.shortid
= sprop
->shortid
;
1392 if (SCOPE_LAST_PROP(scope
) == sprop
) {
1394 * Optimize the case where the last property added to scope is changed
1395 * to have a different attrs, getter, or setter. In the last property
1396 * case, we need not fork the property tree. But since we do not call
1397 * js_AddScopeProperty, we may need to allocate a new slot directly.
1399 if ((sprop
->attrs
& JSPROP_SHARED
) && !(attrs
& JSPROP_SHARED
)) {
1400 JS_ASSERT(child
.slot
== SPROP_INVALID_SLOT
);
1401 if (!js_AllocSlot(cx
, scope
->object
, &child
.slot
))
1405 newsprop
= GetPropertyTreeChild(cx
, sprop
->parent
, &child
);
1407 spp
= js_SearchScope(scope
, sprop
->id
, JS_FALSE
);
1408 JS_ASSERT(SPROP_FETCH(spp
) == sprop
);
1411 SPROP_STORE_PRESERVING_COLLISION(spp
, newsprop
);
1412 scope
->lastProp
= newsprop
;
1413 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1417 * Let js_AddScopeProperty handle this |overwriting| case, including
1418 * the conservation of sprop->slot (if it's valid). We must not call
1419 * js_RemoveScopeProperty here, it will free a valid sprop->slot and
1420 * js_AddScopeProperty won't re-allocate it.
1422 newsprop
= js_AddScopeProperty(cx
, scope
, child
.id
,
1423 child
.getter
, child
.setter
, child
.slot
,
1424 child
.attrs
, child
.flags
, child
.shortid
);
1428 if (scope
->shape
== sprop
->shape
)
1429 scope
->shape
= newsprop
->shape
;
1431 SCOPE_MAKE_UNIQUE_SHAPE(cx
, scope
);
1433 #ifdef JS_DUMP_PROPTREE_STATS
1435 METER(changeFailures
);
1441 js_RemoveScopeProperty(JSContext
*cx
, JSScope
*scope
, jsid id
)
1443 JSScopeProperty
**spp
, *stored
, *sprop
;
1446 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx
, scope
));
1447 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1448 if (SCOPE_IS_SEALED(scope
)) {
1449 ReportReadOnlyScope(cx
, scope
);
1454 spp
= js_SearchScope(scope
, id
, JS_FALSE
);
1456 sprop
= SPROP_CLEAR_COLLISION(stored
);
1458 METER(uselessRemoves
);
1462 /* Convert from a list to a hash so we can handle "middle deletes". */
1463 if (!scope
->table
&& sprop
!= scope
->lastProp
) {
1464 if (!CreateScopeTable(cx
, scope
, JS_TRUE
))
1466 spp
= js_SearchScope(scope
, id
, JS_FALSE
);
1468 sprop
= SPROP_CLEAR_COLLISION(stored
);
1471 /* First, if sprop is unshared and not cleared, free its slot number. */
1472 if (SPROP_HAS_VALID_SLOT(sprop
, scope
)) {
1473 js_FreeSlot(cx
, scope
->object
, sprop
->slot
);
1474 JS_ATOMIC_INCREMENT(&cx
->runtime
->propertyRemovals
);
1477 /* Next, remove id by setting its entry to a removed or free sentinel. */
1478 if (SPROP_HAD_COLLISION(stored
)) {
1479 JS_ASSERT(scope
->table
);
1480 *spp
= SPROP_REMOVED
;
1481 scope
->removedCount
++;
1487 scope
->entryCount
--;
1488 LIVE_SCOPE_METER(cx
, --cx
->runtime
->liveScopeProps
);
1490 /* Update scope->lastProp directly, or set its deferred update flag. */
1491 if (sprop
== SCOPE_LAST_PROP(scope
)) {
1493 SCOPE_REMOVE_LAST_PROP(scope
);
1494 if (!SCOPE_HAD_MIDDLE_DELETE(scope
))
1496 sprop
= SCOPE_LAST_PROP(scope
);
1497 } while (sprop
&& !SCOPE_HAS_PROPERTY(scope
, sprop
));
1498 } else if (!SCOPE_HAD_MIDDLE_DELETE(scope
)) {
1499 SCOPE_SET_MIDDLE_DELETE(scope
);
1501 SCOPE_MAKE_UNIQUE_SHAPE(cx
, scope
);
1502 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1504 /* Last, consider shrinking scope's table if its load factor is <= .25. */
1505 size
= SCOPE_CAPACITY(scope
);
1506 if (size
> MIN_SCOPE_SIZE
&& scope
->entryCount
<= size
>> 2) {
1508 (void) ChangeScope(cx
, scope
, -1);
1515 js_ClearScope(JSContext
*cx
, JSScope
*scope
)
1517 CHECK_ANCESTOR_LINE(scope
, JS_TRUE
);
1518 LIVE_SCOPE_METER(cx
, cx
->runtime
->liveScopeProps
-= scope
->entryCount
);
1522 SCOPE_CLR_MIDDLE_DELETE(scope
);
1523 InitMinimalScope(scope
);
1524 JS_ATOMIC_INCREMENT(&cx
->runtime
->propertyRemovals
);
1528 js_TraceId(JSTracer
*trc
, jsid id
)
1532 v
= ID_TO_VALUE(id
);
1533 JS_CALL_VALUE_TRACER(trc
, v
, "id");
1538 PrintPropertyGetterOrSetter(JSTracer
*trc
, char *buf
, size_t bufsize
)
1540 JSScopeProperty
*sprop
;
1545 JS_ASSERT(trc
->debugPrinter
== PrintPropertyGetterOrSetter
);
1546 sprop
= (JSScopeProperty
*)trc
->debugPrintArg
;
1548 name
= trc
->debugPrintIndex
? js_setter_str
: js_getter_str
;
1550 if (JSID_IS_ATOM(id
)) {
1551 n
= js_PutEscapedString(buf
, bufsize
- 1,
1552 ATOM_TO_STRING(JSID_TO_ATOM(id
)), 0);
1553 if (n
< bufsize
- 1)
1554 JS_snprintf(buf
+ n
, bufsize
- n
, " %s", name
);
1555 } else if (JSID_IS_INT(sprop
->id
)) {
1556 JS_snprintf(buf
, bufsize
, "%d %s", JSID_TO_INT(id
), name
);
1558 JS_snprintf(buf
, bufsize
, "<object> %s", name
);
1565 js_TraceScopeProperty(JSTracer
*trc
, JSScopeProperty
*sprop
)
1567 if (IS_GC_MARKING_TRACER(trc
))
1568 sprop
->flags
|= SPROP_MARK
;
1569 TRACE_ID(trc
, sprop
->id
);
1571 #if JS_HAS_GETTER_SETTER
1572 if (sprop
->attrs
& (JSPROP_GETTER
| JSPROP_SETTER
)) {
1573 if (sprop
->attrs
& JSPROP_GETTER
) {
1574 JS_ASSERT(JSVAL_IS_OBJECT((jsval
) sprop
->getter
));
1575 JS_SET_TRACING_DETAILS(trc
, PrintPropertyGetterOrSetter
, sprop
, 0);
1576 JS_CallTracer(trc
, JSVAL_TO_OBJECT((jsval
) sprop
->getter
),
1579 if (sprop
->attrs
& JSPROP_SETTER
) {
1580 JS_ASSERT(JSVAL_IS_OBJECT((jsval
) sprop
->setter
));
1581 JS_SET_TRACING_DETAILS(trc
, PrintPropertyGetterOrSetter
, sprop
, 1);
1582 JS_CallTracer(trc
, JSVAL_TO_OBJECT((jsval
) sprop
->setter
),
1586 #endif /* JS_HAS_GETTER_SETTER */
1589 #ifdef JS_DUMP_PROPTREE_STATS
1594 MeterKidCount(JSBasicStats
*bs
, uintN nkids
)
1596 JS_BASIC_STATS_ACCUM(bs
, nkids
);
1597 bs
->hist
[JS_MIN(nkids
, 10)]++;
1601 MeterPropertyTree(JSBasicStats
*bs
, JSScopeProperty
*node
)
1604 JSScopeProperty
*kids
, *kid
;
1605 PropTreeKidsChunk
*chunk
;
1610 if (KIDS_IS_CHUNKY(kids
)) {
1611 for (chunk
= KIDS_TO_CHUNK(kids
); chunk
; chunk
= chunk
->next
) {
1612 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
1613 kid
= chunk
->kids
[i
];
1616 MeterPropertyTree(bs
, kid
);
1621 MeterPropertyTree(bs
, kids
);
1626 MeterKidCount(bs
, nkids
);
1629 static JSDHashOperator
1630 js_MeterPropertyTree(JSDHashTable
*table
, JSDHashEntryHdr
*hdr
, uint32 number
,
1633 JSPropertyTreeEntry
*entry
= (JSPropertyTreeEntry
*)hdr
;
1634 JSBasicStats
*bs
= (JSBasicStats
*)arg
;
1636 MeterPropertyTree(bs
, entry
->child
);
1637 return JS_DHASH_NEXT
;
1641 DumpSubtree(JSContext
*cx
, JSScopeProperty
*sprop
, int level
, FILE *fp
)
1645 JSScopeProperty
*kids
, *kid
;
1646 PropTreeKidsChunk
*chunk
;
1649 fprintf(fp
, "%*sid ", level
, "");
1650 v
= ID_TO_VALUE(sprop
->id
);
1651 if (JSID_IS_INT(sprop
->id
)) {
1652 fprintf(fp
, "%d", JSVAL_TO_INT(v
));
1654 if (JSID_IS_ATOM(sprop
->id
)) {
1655 str
= JSVAL_TO_STRING(v
);
1657 JS_ASSERT(JSID_IS_OBJECT(sprop
->id
));
1658 str
= js_ValueToString(cx
, v
);
1659 fputs("object ", fp
);
1662 fputs("<error>", fp
);
1664 js_FileEscapedString(fp
, str
, '"');
1667 fprintf(fp
, " g/s %p/%p slot %u attrs %x flags %x shortid %d\n",
1668 (void *) sprop
->getter
, (void *) sprop
->setter
, sprop
->slot
,
1669 sprop
->attrs
, sprop
->flags
, sprop
->shortid
);
1673 if (KIDS_IS_CHUNKY(kids
)) {
1674 chunk
= KIDS_TO_CHUNK(kids
);
1676 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
1677 kid
= chunk
->kids
[i
];
1680 JS_ASSERT(kid
->parent
== sprop
);
1681 DumpSubtree(cx
, kid
, level
, fp
);
1683 } while ((chunk
= chunk
->next
) != NULL
);
1686 DumpSubtree(cx
, kid
, level
, fp
);
1691 #endif /* JS_DUMP_PROPTREE_STATS */
1694 js_SweepScopeProperties(JSContext
*cx
)
1696 JSRuntime
*rt
= cx
->runtime
;
1698 JSScopeProperty
*limit
, *sprop
, *parent
, *kids
, *kid
;
1700 PropTreeKidsChunk
*chunk
, *nextChunk
, *freeChunk
;
1703 #ifdef JS_DUMP_PROPTREE_STATS
1705 uint32 livePropCapacity
= 0, totalLiveCount
= 0;
1708 logfp
= fopen("/tmp/proptree.stats", "w");
1710 JS_BASIC_STATS_INIT(&bs
);
1711 MeterKidCount(&bs
, rt
->propertyTreeHash
.entryCount
);
1712 JS_DHashTableEnumerate(&rt
->propertyTreeHash
, js_MeterPropertyTree
, &bs
);
1715 double props
, nodes
, mean
, sigma
;
1717 props
= rt
->liveScopePropsPreSweep
;
1718 nodes
= rt
->livePropTreeNodes
;
1719 JS_ASSERT(nodes
== bs
.sum
);
1720 mean
= JS_MeanAndStdDevBS(&bs
, &sigma
);
1723 "props %g nodes %g beta %g meankids %g sigma %g max %u\n",
1724 props
, nodes
, nodes
/ props
, mean
, sigma
, bs
.max
);
1727 JS_DumpHistogram(&bs
, logfp
);
1730 ap
= &rt
->propertyArenaPool
.first
.next
;
1731 while ((a
= *ap
) != NULL
) {
1732 limit
= (JSScopeProperty
*) a
->avail
;
1734 for (sprop
= (JSScopeProperty
*) a
->base
; sprop
< limit
; sprop
++) {
1735 /* If the id is null, sprop is already on the freelist. */
1736 if (sprop
->id
== JSVAL_NULL
)
1740 * If the mark bit is set, sprop is alive, so clear the mark bit
1741 * and continue the while loop.
1743 * Regenerate sprop->shape if it hasn't already been refreshed
1744 * during the mark phase, when live scopes' lastProp members are
1745 * followed to update both scope->shape and lastProp->shape.
1747 if (sprop
->flags
& SPROP_MARK
) {
1748 sprop
->flags
&= ~SPROP_MARK
;
1749 if (sprop
->flags
& SPROP_FLAG_SHAPE_REGEN
) {
1750 sprop
->flags
&= ~SPROP_FLAG_SHAPE_REGEN
;
1752 sprop
->shape
= ++cx
->runtime
->shapeGen
;
1753 JS_ASSERT(sprop
->shape
!= 0);
1759 /* Ok, sprop is garbage to collect: unlink it from its parent. */
1760 freeChunk
= RemovePropertyTreeChild(rt
, sprop
);
1763 * Take care to reparent all sprop's kids to their grandparent.
1764 * InsertPropertyTreeChild can potentially fail for two reasons:
1766 * 1. If parent is null, insertion into the root property hash
1767 * table may fail. We are forced to leave the kid out of the
1768 * table (as can already happen with duplicates) but ensure
1769 * that the kid's parent pointer is set to null.
1771 * 2. If parent is non-null, allocation of a new KidsChunk can
1772 * fail. To prevent this from happening, we allow sprops's own
1773 * chunks to be reused by the grandparent, which removes the
1774 * need for InsertPropertyTreeChild to malloc a new KidsChunk.
1776 * If sprop does not have chunky kids, then we rely on the
1777 * RemovePropertyTreeChild call above (which removed sprop from
1778 * its parent) either leaving one free entry, or else returning
1779 * the now-unused chunk to us so we can reuse it.
1781 * We also require the grandparent to have either no kids or else
1782 * chunky kids. A single non-chunky kid would force a new chunk to
1783 * be malloced in some cases (if sprop had a single non-chunky
1784 * kid, or a multiple of MAX_KIDS_PER_CHUNK kids). Note that
1785 * RemovePropertyTreeChild never converts a single-entry chunky
1786 * kid back to a non-chunky kid, so we are assured of correct
1792 parent
= sprop
->parent
;
1794 /* Assert that grandparent has no kids or chunky kids. */
1795 JS_ASSERT(!parent
|| !parent
->kids
||
1796 KIDS_IS_CHUNKY(parent
->kids
));
1797 if (KIDS_IS_CHUNKY(kids
)) {
1798 chunk
= KIDS_TO_CHUNK(kids
);
1800 nextChunk
= chunk
->next
;
1802 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
1803 kid
= chunk
->kids
[i
];
1806 JS_ASSERT(kid
->parent
== sprop
);
1809 * Clear a space in the kids array for possible
1810 * re-use by InsertPropertyTreeChild.
1812 chunk
->kids
[i
] = NULL
;
1813 if (!InsertPropertyTreeChild(rt
, parent
, kid
,
1816 * This can happen only if we failed to add an
1817 * entry to the root property hash table.
1823 if (!chunk
->kids
[0]) {
1824 /* The chunk wasn't reused, so we must free it. */
1825 DestroyPropTreeKidsChunk(rt
, chunk
);
1827 } while ((chunk
= nextChunk
) != NULL
);
1830 if (!InsertPropertyTreeChild(rt
, parent
, kid
, freeChunk
)) {
1832 * This can happen only if we failed to add an entry
1833 * to the root property hash table.
1841 if (freeChunk
&& !freeChunk
->kids
[0]) {
1842 /* The chunk wasn't reused, so we must free it. */
1843 DestroyPropTreeKidsChunk(rt
, freeChunk
);
1846 /* Clear id so we know (above) that sprop is on the freelist. */
1847 sprop
->id
= JSVAL_NULL
;
1848 FREENODE_INSERT(rt
->propertyFreeList
, sprop
);
1849 JS_RUNTIME_UNMETER(rt
, livePropTreeNodes
);
1852 /* If a contains no live properties, return it to the malloc heap. */
1853 if (liveCount
== 0) {
1854 for (sprop
= (JSScopeProperty
*) a
->base
; sprop
< limit
; sprop
++)
1855 FREENODE_REMOVE(sprop
);
1856 JS_ARENA_DESTROY(&rt
->propertyArenaPool
, a
, ap
);
1858 #ifdef JS_DUMP_PROPTREE_STATS
1859 livePropCapacity
+= limit
- (JSScopeProperty
*) a
->base
;
1860 totalLiveCount
+= liveCount
;
1866 #ifdef JS_DUMP_PROPTREE_STATS
1867 fprintf(logfp
, "arenautil %g%%\n",
1868 (totalLiveCount
&& livePropCapacity
)
1869 ? (totalLiveCount
* 100.0) / livePropCapacity
1872 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
1874 fprintf(logfp
, "Scope search stats:\n"
1876 " hits: %6u %5.2f%% of searches\n"
1877 " misses: %6u %5.2f%%\n"
1878 " hashes: %6u %5.2f%%\n"
1879 " steps: %6u %5.2f%% %5.2f%% of hashes\n"
1880 " stepHits: %6u %5.2f%% %5.2f%%\n"
1881 " stepMisses: %6u %5.2f%% %5.2f%%\n"
1883 " redundantAdds: %6u\n"
1884 " addFailures: %6u\n"
1885 " changeFailures: %6u\n"
1886 " compresses: %6u\n"
1889 " removeFrees: %6u\n"
1890 " uselessRemoves: %6u\n"
1892 js_scope_stats
.searches
,
1893 js_scope_stats
.hits
, RATE(hits
, searches
),
1894 js_scope_stats
.misses
, RATE(misses
, searches
),
1895 js_scope_stats
.hashes
, RATE(hashes
, searches
),
1896 js_scope_stats
.steps
, RATE(steps
, searches
), RATE(steps
, hashes
),
1897 js_scope_stats
.stepHits
,
1898 RATE(stepHits
, searches
), RATE(stepHits
, hashes
),
1899 js_scope_stats
.stepMisses
,
1900 RATE(stepMisses
, searches
), RATE(stepMisses
, hashes
),
1901 js_scope_stats
.adds
,
1902 js_scope_stats
.redundantAdds
,
1903 js_scope_stats
.addFailures
,
1904 js_scope_stats
.changeFailures
,
1905 js_scope_stats
.compresses
,
1906 js_scope_stats
.grows
,
1907 js_scope_stats
.removes
,
1908 js_scope_stats
.removeFrees
,
1909 js_scope_stats
.uselessRemoves
,
1910 js_scope_stats
.shrinks
);
1917 #ifdef DUMP_PROPERTY_TREE
1919 FILE *dumpfp
= fopen("/tmp/proptree.dump", "w");
1921 JSPropertyTreeEntry
*pte
, *end
;
1923 pte
= (JSPropertyTreeEntry
*) rt
->propertyTreeHash
.entryStore
;
1924 end
= pte
+ JS_DHASH_TABLE_SIZE(&rt
->propertyTreeHash
);
1927 DumpSubtree(cx
, pte
->child
, 0, dumpfp
);
1937 js_InitPropertyTree(JSRuntime
*rt
)
1939 if (!JS_DHashTableInit(&rt
->propertyTreeHash
, &PropertyTreeHashOps
, NULL
,
1940 sizeof(JSPropertyTreeEntry
), JS_DHASH_MIN_SIZE
)) {
1941 rt
->propertyTreeHash
.ops
= NULL
;
1944 JS_INIT_ARENA_POOL(&rt
->propertyArenaPool
, "properties",
1945 256 * sizeof(JSScopeProperty
), sizeof(void *), NULL
);
1950 js_FinishPropertyTree(JSRuntime
*rt
)
1952 if (rt
->propertyTreeHash
.ops
) {
1953 JS_DHashTableFinish(&rt
->propertyTreeHash
);
1954 rt
->propertyTreeHash
.ops
= NULL
;
1956 JS_FinishArenaPool(&rt
->propertyArenaPool
);