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 ***** */
53 #include "jsutil.h" /* Added by JSIFY */
64 #include "jsscopeinlines.h"
69 js_GenerateShape(JSContext
*cx
, bool gcLocked
)
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
);
92 js_GetMutableScope(JSContext
*cx
, JSObject
*obj
)
94 JSScope
*scope
, *newscope
;
98 scope
= OBJ_SCOPE(obj
);
99 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx
, scope
));
100 if (!scope
->isSharedEmpty())
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
);
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
));
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
);
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 *))
148 JSScope::initMinimal(JSContext
*cx
, uint32 newShape
)
152 hashShift
= JS_DHASH_BITS
- MIN_SCOPE_SIZE_LOG2
;
153 entryCount
= removedCount
= 0;
159 JS_FRIEND_DATA(JSScopeStats
) js_scope_stats
= {0};
161 # define METER(x) JS_ATOMIC_INCREMENT(&js_scope_stats.x)
163 # define METER(x) /* nothing */
167 JSScope::createTable(JSContext
*cx
, bool report
)
170 JSScopeProperty
*sprop
, **spp
;
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
;
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
*));
193 JS_ReportOutOfMemory(cx
);
194 METER(tableAllocFails
);
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
);
208 JSScope::create(JSContext
*cx
, const JSObjectOps
*ops
, JSClass
*clasp
,
209 JSObject
*obj
, uint32 shape
)
211 JS_ASSERT(ops
->isNative());
214 JSScope
*scope
= cx
->create
<JSScope
>(ops
, obj
);
218 scope
->freeslot
= JSSLOT_FREE(clasp
);
219 scope
->flags
= cx
->runtime
->gcRegenShapesScopeFlag
;
220 scope
->initMinimal(cx
, shape
);
223 js_InitTitle(cx
, &scope
->title
);
225 JS_RUNTIME_METER(cx
->runtime
, liveScopes
);
226 JS_RUNTIME_METER(cx
->runtime
, totalScopes
);
230 JSEmptyScope::JSEmptyScope(JSContext
*cx
, const JSObjectOps
*ops
,
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.
239 freeslot
= JSSLOT_FREE(clasp
);
240 flags
= OWN_SHAPE
| cx
->runtime
->gcRegenShapesScopeFlag
;
241 initMinimal(cx
, js_GenerateShape(cx
, false));
244 js_InitTitle(cx
, &title
);
246 JS_RUNTIME_METER(cx
->runtime
, liveScopes
);
247 JS_RUNTIME_METER(cx
->runtime
, totalScopes
);
252 # define LIVE_SCOPE_METER(cx,expr) JS_LOCK_RUNTIME_VOID(cx->runtime,expr)
254 # define LIVE_SCOPE_METER(cx,expr) /* nothing */
258 JSScope::destroy(JSContext
*cx
)
261 js_FinishTitle(cx
, &title
);
267 * The scopes containing empty scopes are only destroyed from the GC
271 emptyScope
->dropFromGC(cx
);
273 LIVE_SCOPE_METER(cx
, cx
->runtime
->liveScopeProps
-= entryCount
);
274 JS_RUNTIME_UNMETER(cx
->runtime
, liveScopes
);
280 JSScope::initRuntimeState(JSContext
*cx
)
282 cx
->runtime
->emptyBlockScope
= cx
->create
<JSEmptyScope
>(cx
, &js_ObjectOps
,
284 JS_ASSERT(cx
->runtime
->emptyBlockScope
->nrefs
== 2);
285 cx
->runtime
->emptyBlockScope
->nrefs
= 1;
286 return !!cx
->runtime
->emptyBlockScope
;
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))
308 # error "Unsupported configuration"
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
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)
322 JSScope::searchTable(jsid id
, bool adding
)
324 JSHashNumber hash0
, hash1
, hash2
;
326 JSScopeProperty
*stored
, *sprop
, **spp
, **firstRemoved
;
330 JS_ASSERT(!JSVAL_IS_NULL(id
));
332 /* Compute the primary hash address. */
334 hash0
= SCOPE_HASH0(id
);
335 hash1
= SCOPE_HASH1(hash0
, hashShift
);
338 /* Miss: return space for a new entry. */
340 if (SPROP_IS_FREE(stored
)) {
345 /* Hit: return entry. */
346 sprop
= SPROP_CLEAR_COLLISION(stored
);
347 if (sprop
&& sprop
->id
== id
) {
352 /* Collision: double hash. */
353 sizeLog2
= JS_DHASH_BITS
- hashShift
;
354 hash2
= SCOPE_HASH2(hash0
, sizeLog2
, hashShift
);
355 sizeMask
= JS_BITMASK(sizeLog2
);
358 jsuword collision_flag
= SPROP_COLLISION
;
361 /* Save the first removed entry pointer so we can recycle it if adding. */
362 if (SPROP_IS_REMOVED(stored
)) {
366 if (adding
&& !SPROP_HAD_COLLISION(stored
))
367 SPROP_FLAG_COLLISION(spp
, sprop
);
369 collision_flag
&= jsuword(*spp
) & SPROP_COLLISION
;
380 if (SPROP_IS_FREE(stored
)) {
382 return (adding
&& firstRemoved
) ? firstRemoved
: spp
;
385 sprop
= SPROP_CLEAR_COLLISION(stored
);
386 if (sprop
&& sprop
->id
== id
) {
388 JS_ASSERT(collision_flag
);
392 if (SPROP_IS_REMOVED(stored
)) {
396 if (adding
&& !SPROP_HAD_COLLISION(stored
))
397 SPROP_FLAG_COLLISION(spp
, sprop
);
399 collision_flag
&= jsuword(*spp
) & SPROP_COLLISION
;
409 JSScope::changeTable(JSContext
*cx
, int change
)
411 int oldlog2
, newlog2
;
412 uint32 oldsize
, newsize
, nbytes
;
413 JSScopeProperty
**newtable
, **oldtable
, **spp
, **oldspp
, *sprop
;
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
);
426 METER(tableAllocFails
);
430 /* Now that we have newtable allocated, update members. */
431 hashShift
= JS_DHASH_BITS
- newlog2
;
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
);
443 spp
= search(sprop
->id
, true);
444 JS_ASSERT(SPROP_IS_FREE(*spp
));
450 /* Finally, free the old table storage. */
456 js_HashScopeProperty(JSDHashTable
*table
, const void *key
)
458 const JSScopeProperty
*sprop
= (const JSScopeProperty
*)key
;
459 return sprop
->hash();
463 js_MatchScopeProperty(JSDHashTable
*table
,
464 const JSDHashEntryHdr
*hdr
,
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
= {
477 js_HashScopeProperty
,
478 js_MatchScopeProperty
,
479 JS_DHashMoveEntryStub
,
480 JS_DHashClearEntryStub
,
481 JS_DHashFinalizeStub
,
486 * A property tree node on rt->propertyFreeList overlays the following prefix
487 * struct on JSScopeProperty.
489 typedef struct FreeNode
{
491 JSScopeProperty
*next
;
492 JSScopeProperty
**prevp
;
495 #define FREENODE(sprop) ((FreeNode *) (sprop))
497 #define FREENODE_INSERT(list, sprop) \
499 FREENODE(sprop)->next = (list); \
500 FREENODE(sprop)->prevp = &(list); \
502 FREENODE(list)->prevp = &FREENODE(sprop)->next; \
506 #define FREENODE_REMOVE(sprop) \
508 *FREENODE(sprop)->prevp = FREENODE(sprop)->next; \
509 if (FREENODE(sprop)->next) \
510 FREENODE(FREENODE(sprop)->next)->prevp = FREENODE(sprop)->prevp; \
513 /* NB: Called with rt->gcLock held. */
514 static JSScopeProperty
*
515 NewScopeProperty(JSRuntime
*rt
)
517 JSScopeProperty
*sprop
;
519 sprop
= rt
->propertyFreeList
;
521 FREENODE_REMOVE(sprop
);
523 JS_ARENA_ALLOCATE_CAST(sprop
, JSScopeProperty
*,
524 &rt
->propertyArenaPool
,
525 sizeof(JSScopeProperty
));
530 JS_RUNTIME_METER(rt
, livePropTreeNodes
);
531 JS_RUNTIME_METER(rt
, totalPropTreeNodes
);
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
];
549 PropTreeKidsChunk
*next
;
552 static PropTreeKidsChunk
*
553 NewPropTreeKidsChunk(JSRuntime
*rt
)
555 PropTreeKidsChunk
*chunk
;
557 chunk
= (PropTreeKidsChunk
*) js_calloc(sizeof *chunk
);
560 JS_ASSERT(((jsuword
)chunk
& CHUNKY_KIDS_TAG
) == 0);
561 JS_RUNTIME_METER(rt
, propTreeKidsChunks
);
566 DestroyPropTreeKidsChunk(JSRuntime
*rt
, PropTreeKidsChunk
*chunk
)
568 JS_RUNTIME_UNMETER(rt
, propTreeKidsChunks
);
570 JS_DHashTableDestroy(chunk
->table
);
574 /* NB: Called with rt->gcLock held. */
576 InsertPropertyTreeChild(JSRuntime
*rt
, JSScopeProperty
*parent
,
577 JSScopeProperty
*child
, PropTreeKidsChunk
*sweptChunk
)
580 JSPropertyTreeEntry
*entry
;
581 JSScopeProperty
**childp
, *kids
, *sprop
;
582 PropTreeKidsChunk
*chunk
, **chunkp
;
585 JS_ASSERT(!parent
|| child
->parent
!= parent
);
586 JS_ASSERT(!JSVAL_IS_NULL(child
->id
));
589 table
= &rt
->propertyTreeHash
;
590 entry
= (JSPropertyTreeEntry
*)
591 JS_DHashTableOperate(table
, child
, JS_DHASH_ADD
);
594 childp
= &entry
->child
;
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
615 JS_ASSERT(sprop
!= child
&& sprop
->matches(child
));
616 JS_RUNTIME_METER(rt
, duplicatePropTreeNodes
);
619 JS_ASSERT(!JSVAL_IS_NULL(parent
->id
));
620 childp
= &parent
->kids
;
623 if (KIDS_IS_CHUNKY(kids
)) {
624 chunk
= KIDS_TO_CHUNK(kids
);
626 table
= chunk
->table
;
628 entry
= (JSPropertyTreeEntry
*)
629 JS_DHashTableOperate(table
, child
, JS_DHASH_ADD
);
633 entry
->child
= child
;
636 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
637 childp
= &chunk
->kids
[i
];
642 chunkp
= &chunk
->next
;
648 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
649 childp
= &chunk
->kids
[i
];
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
);
673 chunk
= NewPropTreeKidsChunk(rt
);
678 childp
= &chunk
->kids
[0];
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
);
694 chunk
= NewPropTreeKidsChunk(rt
);
698 parent
->kids
= CHUNK_TO_KIDS(chunk
);
699 chunk
->kids
[0] = sprop
;
700 childp
= &chunk
->kids
[1];
707 child
->parent
= parent
;
711 /* NB: Called with rt->gcLock held. */
712 static PropTreeKidsChunk
*
713 RemovePropertyTreeChild(JSRuntime
*rt
, JSScopeProperty
*child
)
715 PropTreeKidsChunk
*freeChunk
;
716 JSScopeProperty
*parent
, *kids
, *kid
;
718 PropTreeKidsChunk
*list
, *chunk
, **chunkp
, *lastChunk
;
720 JSPropertyTreeEntry
*entry
;
723 parent
= child
->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
;
732 JS_ASSERT(!JSVAL_IS_NULL(parent
->id
));
734 if (KIDS_IS_CHUNKY(kids
)) {
735 list
= chunk
= KIDS_TO_CHUNK(kids
);
737 table
= chunk
->table
;
740 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
741 if (chunk
->kids
[i
] == child
) {
743 if (!lastChunk
->next
) {
748 chunkp
= &lastChunk
->next
;
750 } while (lastChunk
->next
);
752 for (; j
< MAX_KIDS_PER_CHUNK
; j
++) {
753 if (!lastChunk
->kids
[j
])
757 if (chunk
!= lastChunk
|| j
> i
)
758 chunk
->kids
[i
] = lastChunk
->kids
[j
];
759 lastChunk
->kids
[j
] = NULL
;
764 freeChunk
= lastChunk
;
770 chunkp
= &chunk
->next
;
771 } while ((chunk
= *chunkp
) != NULL
);
782 entry
= (JSPropertyTreeEntry
*)
783 JS_DHashTableOperate(table
, child
, JS_DHASH_LOOKUP
);
785 if (entry
->child
== child
)
786 JS_DHashTableRawRemove(table
, &entry
->hdr
);
791 static JSDHashTable
*
792 HashChunks(PropTreeKidsChunk
*chunk
, uintN n
)
796 JSScopeProperty
*sprop
;
797 JSPropertyTreeEntry
*entry
;
799 table
= JS_NewDHashTable(&PropertyTreeHashOps
, NULL
,
800 sizeof(JSPropertyTreeEntry
),
801 JS_DHASH_DEFAULT_CAPACITY(n
+ 1));
805 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
806 sprop
= chunk
->kids
[i
];
809 entry
= (JSPropertyTreeEntry
*)
810 JS_DHashTableOperate(table
, sprop
, JS_DHASH_ADD
);
811 entry
->child
= sprop
;
813 } while ((chunk
= chunk
->next
) != NULL
);
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.
826 js_GetPropertyTreeChild(JSContext
*cx
, JSScopeProperty
*parent
,
827 const JSScopeProperty
&child
)
831 JSPropertyTreeEntry
*entry
;
832 JSScopeProperty
*sprop
;
833 PropTreeKidsChunk
*chunk
;
840 table
= &rt
->propertyTreeHash
;
841 entry
= (JSPropertyTreeEntry
*)
842 JS_DHashTableOperate(table
, &child
, JS_DHASH_ADD
);
846 sprop
= entry
->child
;
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.
866 sprop
= parent
->kids
;
868 if (KIDS_IS_CHUNKY(sprop
)) {
869 chunk
= KIDS_TO_CHUNK(sprop
);
871 table
= chunk
->table
;
874 entry
= (JSPropertyTreeEntry
*)
875 JS_DHashTableOperate(table
, &child
, JS_DHASH_LOOKUP
);
876 sprop
= entry
->child
;
879 goto locked_not_found
;
884 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
885 sprop
= chunk
->kids
[i
];
888 if (n
>= CHUNK_HASH_THRESHOLD
) {
889 chunk
= KIDS_TO_CHUNK(parent
->kids
);
891 table
= HashChunks(chunk
, n
);
896 JS_DHashTableDestroy(table
);
898 chunk
->table
= table
;
899 goto locked_not_found
;
905 if (sprop
->matches(&child
))
908 n
+= MAX_KIDS_PER_CHUNK
;
909 } while ((chunk
= chunk
->next
) != NULL
);
911 if (sprop
->matches(&child
))
921 sprop
= NewScopeProperty(rt
);
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);
936 entry
->child
= sprop
;
938 if (!InsertPropertyTreeChild(rt
, parent
, sprop
, NULL
))
948 JS_ReportOutOfMemory(cx
);
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.
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
;
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
)) {
987 if (inDictionaryMode()) {
988 JS_ASSERT(parent
== lastProp
);
989 if (newDictionaryProperty(cx
, child
, &lastProp
)) {
996 JSScopeProperty
*sprop
= js_GetPropertyTreeChild(cx
, parent
, child
);
998 JS_ASSERT(sprop
->parent
== parent
);
999 if (parent
== lastProp
) {
1002 JS_ASSERT(parent
== lastProp
->parent
);
1003 setLastProperty(sprop
);
1010 #ifdef DEBUG_notbrendan
1011 #define CHECK_ANCESTOR_LINE(scope, sparse) \
1013 if ((scope)->table) CheckAncestorLine(scope); \
1017 CheckAncestorLine(JSScope
*scope
)
1020 JSScopeProperty
**spp
, **start
, **end
, *ancestorLine
, *sprop
, *aprop
;
1021 uint32 entryCount
, ancestorCount
;
1023 ancestorLine
= scope
->lastProperty();
1025 JS_ASSERT(scope
->hasProperty(ancestorLine
));
1028 size
= SCOPE_CAPACITY(scope
);
1029 start
= scope
->table
;
1030 for (spp
= start
, end
= start
+ size
; spp
< end
; spp
++) {
1031 sprop
= SPROP_FETCH(spp
);
1034 for (aprop
= ancestorLine
; aprop
; aprop
= aprop
->parent
) {
1041 JS_ASSERT(entryCount
== scope
->entryCount
);
1044 for (sprop
= ancestorLine
; sprop
; sprop
= sprop
->parent
)
1046 JS_ASSERT(ancestorCount
== scope
->entryCount
);
1049 #define CHECK_ANCESTOR_LINE(scope, sparse) /* nothing */
1053 JSScope::reportReadOnlyScope(JSContext
*cx
)
1058 str
= js_ValueToString(cx
, OBJECT_TO_JSVAL(object
));
1061 bytes
= js_GetStringBytes(cx
, str
);
1064 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_READ_ONLY
, bytes
);
1068 JSScope::generateOwnShape(JSContext
*cx
)
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
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
);
1092 shape
= js_GenerateShape(cx
, false);
1097 JSScope::newDictionaryProperty(JSContext
*cx
, const JSScopeProperty
&child
,
1098 JSScopeProperty
**childp
)
1100 JSScopeProperty
*dprop
= NewScopeProperty(cx
->runtime
);
1102 JS_ReportOutOfMemory(cx
);
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
);
1122 JSScope::toDictionaryMode(JSContext
*cx
, JSScopeProperty
*&aprop
)
1124 JS_ASSERT(!inDictionaryMode());
1126 JSScopeProperty
**oldTable
= table
;
1127 uint32 saveRemovedCount
= removedCount
;
1129 int sizeLog2
= JS_DHASH_BITS
- hashShift
;
1130 JSScopeProperty
**newTable
= (JSScopeProperty
**)
1131 js_calloc(JS_BIT(sizeLog2
) * sizeof(JSScopeProperty
*));
1134 JS_ReportOutOfMemory(cx
);
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
;
1150 * Clear entryCount because JSScope::insertDictionaryProperty called from
1151 * JSScope::newDictionaryProperty bumps it.
1153 uint32 saveEntryCount
= entryCount
;
1156 for (JSScopeProperty
*sprop
= oldLastProp
, **childp
= &lastProp
; sprop
; sprop
= sprop
->parent
) {
1157 JSScopeProperty
*dprop
= newDictionaryProperty(cx
, *sprop
, childp
);
1159 entryCount
= saveEntryCount
;
1160 removedCount
= saveRemovedCount
;
1164 lastProp
= oldLastProp
;
1170 JSScopeProperty
**spp
= search(dprop
->id
, true);
1171 JS_ASSERT(!SPROP_FETCH(spp
));
1172 SPROP_STORE_PRESERVING_COLLISION(spp
, dprop
);
1177 childp
= &dprop
->parent
;
1182 setDictionaryMode();
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
;
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.
1218 reportReadOnlyScope(cx
);
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.
1233 NormalizeGetterAndSetter(JSContext
*cx
, JSScope
*scope
,
1234 jsid id
, uintN attrs
, uintN flags
,
1235 JSPropertyOp
&getter
,
1236 JSPropertyOp
&setter
)
1238 if (setter
== JS_PropertyStub
)
1240 if (flags
& JSScopeProperty::METHOD
) {
1241 /* Here, getter is the method, a function object reference. */
1243 JS_ASSERT(!setter
|| setter
== js_watch_set
);
1244 JS_ASSERT(!(attrs
& (JSPROP_GETTER
| JSPROP_SETTER
)));
1246 if (getter
== JS_PropertyStub
)
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
);
1259 METER(wrapWatchFails
);
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;
1283 if (!changeTable(cx
, change
) && entryCount
+ removedCount
== size
- 1)
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
;
1295 child
.getter
= getter
;
1296 child
.setter
= setter
;
1298 child
.attrs
= attrs
;
1299 child
.flags
= flags
;
1300 child
.shortid
= shortid
;
1301 sprop
= getChildProperty(cx
, lastProp
, child
);
1305 /* Store the tree node pointer in the table entry for id. */
1307 SPROP_STORE_PRESERVING_COLLISION(spp
, sprop
);
1308 CHECK_ANCESTOR_LINE(this, false);
1310 LIVE_SCOPE_METER(cx
, ++cx
->runtime
->liveScopeProps
);
1311 JS_RUNTIME_METER(cx
->runtime
, totalScopeProps
);
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);
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
));
1352 reportReadOnlyScope(cx
);
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
);
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)) {
1379 if (sprop
->matchesParamsAfterId(getter
, setter
, slot
, attrs
, flags
, shortid
)) {
1380 METER(redundantPuts
);
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();
1397 if (!inDictionaryMode()) {
1398 if (!toDictionaryMode(cx
, sprop
))
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.
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. */
1420 child
.getter
= getter
;
1421 child
.setter
= setter
;
1423 child
.attrs
= attrs
;
1424 child
.flags
= flags
;
1425 child
.shortid
= shortid
;
1426 sprop
= getChildProperty(cx
, lastProp
, child
);
1430 CHECK_ANCESTOR_LINE(this, false);
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);
1445 SPROP_STORE_PRESERVING_COLLISION(spp
, overwriting
);
1447 CHECK_ANCESTOR_LINE(this, true);
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
)
1476 if (setter
== JS_PropertyStub
)
1478 if (sprop
->attrs
== attrs
&&
1479 sprop
->getter
== getter
&&
1480 sprop
->setter
== setter
) {
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
);
1497 JSScopeProperty
**spp
= search(sprop
->id
, false);
1498 SPROP_STORE_PRESERVING_COLLISION(spp
, newsprop
);
1502 } else if (sprop
== lastProp
) {
1503 newsprop
= getChildProperty(cx
, sprop
->parent
, child
);
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);
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
);
1533 JSScope::removeProperty(JSContext
*cx
, jsid id
)
1535 JSScopeProperty
**spp
, *sprop
;
1538 JS_ASSERT(JS_IS_SCOPE_LOCKED(cx
, this));
1539 CHECK_ANCESTOR_LINE(this, true);
1541 reportReadOnlyScope(cx
);
1545 spp
= search(id
, false);
1546 sprop
= SPROP_CLEAR_COLLISION(*spp
);
1548 METER(uselessRemoves
);
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
))
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
)) {
1571 *spp
= SPROP_REMOVED
;
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
));
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
)
1599 removeDictionaryProperty(sprop
);
1601 JS_ASSERT(sprop
== lastProp
);
1602 removeLastProperty();
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) {
1611 (void) changeTable(cx
, -1);
1619 JSScope::clear(JSContext
*cx
)
1621 CHECK_ANCESTOR_LINE(this, true);
1622 LIVE_SCOPE_METER(cx
, cx
->runtime
->liveScopeProps
-= entryCount
);
1626 clearDictionaryMode();
1628 LeaveTraceIfGlobalObject(cx
, object
);
1630 JSClass
*clasp
= object
->getClass();
1631 JSObject
*proto
= object
->getProto();
1632 JSEmptyScope
*emptyScope
;
1635 OBJ_IS_NATIVE(proto
) &&
1636 (emptyScope
= OBJ_SCOPE(proto
)->emptyScope
) &&
1637 emptyScope
->clasp
== clasp
) {
1638 newShape
= emptyScope
->shape
;
1640 newShape
= js_GenerateShape(cx
, false);
1642 initMinimal(cx
, newShape
);
1644 JS_ATOMIC_INCREMENT(&cx
->runtime
->propertyRemovals
);
1648 JSScope::brandingShapeChange(JSContext
*cx
, uint32 slot
, jsval v
)
1650 generateOwnShape(cx
);
1654 JSScope::deletingShapeChange(JSContext
*cx
, JSScopeProperty
*sprop
)
1656 JS_ASSERT(!JSVAL_IS_NULL(sprop
->id
));
1657 generateOwnShape(cx
);
1661 JSScope::methodShapeChange(JSContext
*cx
, JSScopeProperty
*sprop
, jsval toval
)
1663 JS_ASSERT(!JSVAL_IS_NULL(sprop
->id
));
1664 if (sprop
->isMethod()) {
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
);
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
,
1681 sprop
->getFlags() & ~JSScopeProperty::METHOD
,
1687 generateOwnShape(cx
);
1692 JSScope::methodShapeChange(JSContext
*cx
, uint32 slot
, jsval toval
)
1694 if (!hasMethodBarrier()) {
1695 generateOwnShape(cx
);
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
);
1707 JSScope::protoShapeChange(JSContext
*cx
)
1709 generateOwnShape(cx
);
1713 JSScope::sealingShapeChange(JSContext
*cx
)
1715 generateOwnShape(cx
);
1719 JSScope::shadowingShapeChange(JSContext
*cx
, JSScopeProperty
*sprop
)
1721 JS_ASSERT(!JSVAL_IS_NULL(sprop
->id
));
1722 generateOwnShape(cx
);
1726 js_TraceId(JSTracer
*trc
, jsid id
)
1730 v
= ID_TO_VALUE(id
);
1731 JS_CALL_VALUE_TRACER(trc
, v
, "id");
1736 PrintPropertyGetterOrSetter(JSTracer
*trc
, char *buf
, size_t bufsize
)
1738 JSScopeProperty
*sprop
;
1743 JS_ASSERT(trc
->debugPrinter
== PrintPropertyGetterOrSetter
);
1744 sprop
= (JSScopeProperty
*)trc
->debugPrintArg
;
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
);
1757 JS_snprintf(buf
, bufsize
, "<object> %s", name
);
1762 PrintPropertyMethod(JSTracer
*trc
, char *buf
, size_t bufsize
)
1764 JSScopeProperty
*sprop
;
1768 JS_ASSERT(trc
->debugPrinter
== PrintPropertyMethod
);
1769 sprop
= (JSScopeProperty
*)trc
->debugPrintArg
;
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");
1781 JSScopeProperty::trace(JSTracer
*trc
)
1783 if (IS_GC_MARKING_TRACER(trc
))
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 */
1801 JS_SET_TRACING_DETAILS(trc
, PrintPropertyMethod
, this, 0);
1802 js_CallGCMarker(trc
, methodObject(), JSTRACE_OBJECT
);
1809 MeterKidCount(JSBasicStats
*bs
, uintN nkids
)
1811 JS_BASIC_STATS_ACCUM(bs
, nkids
);
1812 bs
->hist
[JS_MIN(nkids
, 10)]++;
1816 MeterPropertyTree(JSBasicStats
*bs
, JSScopeProperty
*node
)
1819 JSScopeProperty
*kids
, *kid
;
1820 PropTreeKidsChunk
*chunk
;
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
];
1831 MeterPropertyTree(bs
, kid
);
1836 MeterPropertyTree(bs
, kids
);
1841 MeterKidCount(bs
, nkids
);
1844 static JSDHashOperator
1845 js_MeterPropertyTree(JSDHashTable
*table
, JSDHashEntryHdr
*hdr
, uint32 number
,
1848 JSPropertyTreeEntry
*entry
= (JSPropertyTreeEntry
*)hdr
;
1849 JSBasicStats
*bs
= (JSBasicStats
*)arg
;
1851 MeterPropertyTree(bs
, entry
->child
);
1852 return JS_DHASH_NEXT
;
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
));
1865 if (JSVAL_IS_STRING(idval
)) {
1866 str
= JSVAL_TO_STRING(idval
);
1868 JS_ASSERT(JSVAL_IS_OBJECT(idval
));
1869 str
= js_ValueToString(cx
, idval
);
1870 fputs("object ", fp
);
1873 fputs("<error>", fp
);
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
),
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
);
1896 fprintf(fp
, "flags %x ", flags
);
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
);
1911 fprintf(fp
, "shortid %d\n", shortid
);
1915 JSScopeProperty::dumpSubtree(JSContext
*cx
, int level
, FILE *fp
)
1917 fprintf(fp
, "%*sid ", level
, "");
1922 if (KIDS_IS_CHUNKY(kids
)) {
1923 PropTreeKidsChunk
*chunk
= KIDS_TO_CHUNK(kids
);
1925 for (uintN i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
1926 JSScopeProperty
*kid
= chunk
->kids
[i
];
1929 JS_ASSERT(kid
->parent
== this);
1930 kid
->dumpSubtree(cx
, level
, fp
);
1932 } while ((chunk
= chunk
->next
) != NULL
);
1934 JSScopeProperty
*kid
= kids
;
1935 JS_ASSERT(kid
->parent
== this);
1936 kid
->dumpSubtree(cx
, level
, fp
);
1944 js_SweepScopeProperties(JSContext
*cx
)
1946 JSRuntime
*rt
= cx
->runtime
;
1948 JSScopeProperty
*limit
, *sprop
, *parent
, *kids
, *kid
;
1950 PropTreeKidsChunk
*chunk
, *nextChunk
, *freeChunk
;
1955 uint32 livePropCapacity
= 0, totalLiveCount
= 0;
1958 if (const char *filename
= getenv("JS_PROPTREE_STATFILE"))
1959 logfp
= fopen(filename
, "w");
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
);
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
);
1982 ap
= &rt
->propertyArenaPool
.first
.next
;
1983 while ((a
= *ap
) != NULL
) {
1984 limit
= (JSScopeProperty
*) a
->avail
;
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
)
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()) {
2001 if (rt
->gcRegenShapes
) {
2002 if (sprop
->hasRegenFlag())
2003 sprop
->clearRegenFlag();
2005 sprop
->shape
= js_RegenerateShapeForGC(cx
);
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
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
);
2053 nextChunk
= chunk
->next
;
2055 for (i
= 0; i
< MAX_KIDS_PER_CHUNK
; i
++) {
2056 kid
= chunk
->kids
[i
];
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.
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
);
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.
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
);
2112 livePropCapacity
+= limit
- (JSScopeProperty
*) a
->base
;
2113 totalLiveCount
+= liveCount
;
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
2130 #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
2133 "Scope search stats:\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"
2147 " redundantPuts: %6u\n"
2150 " changeFails: %6u\n"
2151 " compresses: %6u\n"
2154 " removeFrees: %6u\n"
2155 " uselessRemoves: %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
);
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");
2193 JSPropertyTreeEntry
*pte
, *end
;
2195 pte
= (JSPropertyTreeEntry
*) rt
->propertyTreeHash
.entryStore
;
2196 end
= pte
+ JS_DHASH_TABLE_SIZE(&rt
->propertyTreeHash
);
2199 pte
->child
->dumpSubtree(cx
, 0, dumpfp
);
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
;
2216 JS_InitArenaPool(&rt
->propertyArenaPool
, "properties",
2217 256 * sizeof(JSScopeProperty
), sizeof(void *), NULL
);
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
);