1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "jspropertytree.h"
9 #include "mozilla/DebugOnly.h"
17 #include "jsgcinlines.h"
19 #include "vm/Shape-inl.h"
22 using namespace js::gc
;
24 using mozilla::DebugOnly
;
27 ShapeHasher::hash(const Lookup
& l
)
33 ShapeHasher::match(const Key k
, const Lookup
& l
)
39 HashChildren(Shape
* kid1
, Shape
* kid2
)
41 KidsHash
* hash
= js_new
<KidsHash
>();
42 if (!hash
|| !hash
->init(2)) {
47 JS_ALWAYS_TRUE(hash
->putNew(StackShape(kid1
), kid1
));
48 JS_ALWAYS_TRUE(hash
->putNew(StackShape(kid2
), kid2
));
53 PropertyTree::insertChild(ExclusiveContext
* cx
, Shape
* parent
, Shape
* child
)
55 MOZ_ASSERT(!parent
->inDictionary());
56 MOZ_ASSERT(!child
->parent
);
57 MOZ_ASSERT(!child
->inDictionary());
58 MOZ_ASSERT(child
->compartment() == parent
->compartment());
59 MOZ_ASSERT(cx
->isInsideCurrentCompartment(this));
61 KidsPointer
* kidp
= &parent
->kids
;
64 child
->setParent(parent
);
65 kidp
->setShape(child
);
69 if (kidp
->isShape()) {
70 Shape
* shape
= kidp
->toShape();
71 MOZ_ASSERT(shape
!= child
);
72 MOZ_ASSERT(!shape
->matches(child
));
74 KidsHash
* hash
= HashChildren(shape
, child
);
76 js_ReportOutOfMemory(cx
);
80 child
->setParent(parent
);
84 if (!kidp
->toHash()->putNew(StackShape(child
), child
)) {
85 js_ReportOutOfMemory(cx
);
89 child
->setParent(parent
);
94 Shape::removeChild(Shape
* child
)
96 MOZ_ASSERT(!child
->inDictionary());
97 MOZ_ASSERT(child
->parent
== this);
99 KidsPointer
* kidp
= &kids
;
101 if (kidp
->isShape()) {
102 MOZ_ASSERT(kidp
->toShape() == child
);
104 child
->parent
= nullptr;
108 KidsHash
* hash
= kidp
->toHash();
109 MOZ_ASSERT(hash
->count() >= 2); /* otherwise kidp->isShape() should be true */
112 size_t oldCount
= hash
->count();
115 hash
->remove(StackShape(child
));
116 child
->parent
= nullptr;
118 MOZ_ASSERT(hash
->count() == oldCount
- 1);
120 if (hash
->count() == 1) {
121 /* Convert from HASH form back to SHAPE form. */
122 KidsHash::Range r
= hash
->all();
123 Shape
* otherChild
= r
.front();
124 MOZ_ASSERT((r
.popFront(), r
.empty())); /* No more elements! */
125 kidp
->setShape(otherChild
);
131 PropertyTree::getChild(ExclusiveContext
* cx
, Shape
* parentArg
, StackShape
& unrootedChild
)
133 RootedShape
parent(cx
, parentArg
);
136 Shape
* existingShape
= nullptr;
139 * The property tree has extremely low fan-out below its root in
140 * popular embeddings with real-world workloads. Patterns such as
141 * defining closures that capture a constructor's environment as
142 * getters or setters on the new object that is passed in as
143 * |this| can significantly increase fan-out below the property
144 * tree root -- see bug 335700 for details.
146 KidsPointer
* kidp
= &parent
->kids
;
147 if (kidp
->isShape()) {
148 Shape
* kid
= kidp
->toShape();
149 if (kid
->matches(unrootedChild
))
151 } else if (kidp
->isHash()) {
152 if (KidsHash::Ptr p
= kidp
->toHash()->lookup(unrootedChild
))
155 /* If kidp->isNull(), we always insert. */
159 JS::Zone
* zone
= existingShape
->zone();
160 if (zone
->needsIncrementalBarrier()) {
162 * We need a read barrier for the shape tree, since these are weak
165 Shape
* tmp
= existingShape
;
166 MarkShapeUnbarriered(zone
->barrierTracer(), &tmp
, "read barrier");
167 MOZ_ASSERT(tmp
== existingShape
);
168 } else if (zone
->isGCSweeping() && !existingShape
->isMarked() &&
169 !existingShape
->arenaHeader()->allocatedDuringIncremental
)
172 * The shape we've found is unreachable and due to be finalized, so
173 * remove our weak reference to it and don't use it.
175 MOZ_ASSERT(parent
->isMarked());
176 parent
->removeChild(existingShape
);
177 existingShape
= nullptr;
178 } else if (existingShape
->isMarked(gc::GRAY
)) {
179 UnmarkGrayShapeRecursively(existingShape
);
184 return existingShape
;
186 Shape
* shape
= Shape::new_(cx
, unrootedChild
, parent
->numFixedSlots());
190 if (!insertChild(cx
, parent
, shape
))
203 * We detach the child from the parent if the parent is reachable.
205 * Note that due to incremental sweeping, the parent pointer may point
206 * to the original reachable parent, or it may point to a new live
207 * object allocated in the same cell that used to hold the parent.
209 * There are three cases:
211 * Case 1: parent is not marked - parent is unreachable, may have been
212 * finalized, and the cell may subsequently have been
213 * reallocated to a compartment that is not being marked (cells
214 * are marked when allocated in a compartment that is currenly
215 * being marked by the collector).
217 * Case 2: parent is marked and is in a different compartment - parent
218 * has been freed and reallocated to compartment that was being
221 * Case 3: parent is marked and is in the same compartment - parent is
222 * stil reachable and we need to detach from it.
224 if (parent
&& parent
->isMarked() && parent
->compartment() == compartment())
225 parent
->removeChild(this);
229 Shape::finalize(FreeOp
* fop
)
231 if (!inDictionary() && kids
.isHash())
232 fop
->delete_(kids
.toHash());
235 #ifdef JSGC_COMPACTING
238 Shape::fixupDictionaryShapeAfterMovingGC()
243 // It's possible that this shape is unreachable and that listp points to the
244 // location of a dead object in the nursery, in which case we should never
246 if (IsInsideNursery(reinterpret_cast<Cell
*>(listp
))) {
251 MOZ_ASSERT(!IsInsideNursery(reinterpret_cast<Cell
*>(listp
)));
252 AllocKind kind
= TenuredCell::fromPointer(listp
)->getAllocKind();
253 MOZ_ASSERT(kind
== FINALIZE_SHAPE
||
254 kind
== FINALIZE_ACCESSOR_SHAPE
||
255 kind
<= FINALIZE_OBJECT_LAST
);
256 if (kind
== FINALIZE_SHAPE
|| kind
== FINALIZE_ACCESSOR_SHAPE
) {
257 // listp points to the parent field of the next shape.
258 Shape
* next
= reinterpret_cast<Shape
*>(uintptr_t(listp
) -
259 offsetof(Shape
, parent
));
260 listp
= &gc::MaybeForwarded(next
)->parent
;
262 // listp points to the shape_ field of an object.
263 JSObject
* last
= reinterpret_cast<JSObject
*>(uintptr_t(listp
) -
264 offsetof(JSObject
, shape_
));
265 listp
= &gc::MaybeForwarded(last
)->shape_
;
270 Shape::fixupShapeTreeAfterMovingGC()
275 if (kids
.isShape()) {
276 if (gc::IsForwarded(kids
.toShape()))
277 kids
.setShape(gc::Forwarded(kids
.toShape()));
281 MOZ_ASSERT(kids
.isHash());
282 KidsHash
* kh
= kids
.toHash();
283 for (KidsHash::Enum
e(*kh
); !e
.empty(); e
.popFront()) {
284 Shape
* key
= e
.front();
285 if (IsForwarded(key
))
286 key
= Forwarded(key
);
288 BaseShape
* base
= key
->base();
289 if (IsForwarded(base
))
290 base
= Forwarded(base
);
291 UnownedBaseShape
* unowned
= base
->unowned();
292 if (IsForwarded(unowned
))
293 unowned
= Forwarded(unowned
);
295 PropertyOp getter
= key
->getter();
296 if (key
->hasGetterObject())
297 getter
= PropertyOp(MaybeForwarded(key
->getterObject()));
299 StrictPropertyOp setter
= key
->setter();
300 if (key
->hasSetterObject())
301 setter
= StrictPropertyOp(MaybeForwarded(key
->setterObject()));
303 StackShape
lookup(unowned
,
304 const_cast<Shape
*>(key
)->propidRef(),
305 key
->slotInfo
& Shape::SLOT_MASK
,
308 lookup
.updateGetterSetter(getter
, setter
);
309 e
.rekeyFront(lookup
, key
);
314 Shape::fixupAfterMovingGC()
317 fixupDictionaryShapeAfterMovingGC();
319 fixupShapeTreeAfterMovingGC();
322 #endif // JSGC_COMPACTING
325 ShapeGetterSetterRef::mark(JSTracer
* trc
)
327 // Update the current shape's entry in the parent KidsHash table if needed.
328 // This is necessary as the computed hash includes the getter/setter
331 JSObject
* obj
= *objp
;
332 JSObject
* prior
= obj
;
336 trc
->setTracingLocation(&*prior
);
337 gc::Mark(trc
, &obj
, "AccessorShape getter or setter");
341 Shape
* parent
= shape
->parent
;
342 if (shape
->inDictionary() || !parent
->kids
.isHash()) {
347 KidsHash
* kh
= parent
->kids
.toHash();
348 kh
->remove(StackShape(shape
));
350 MOZ_ALWAYS_TRUE(kh
->putNew(StackShape(shape
), shape
));
356 KidsPointer::checkConsistency(Shape
* aKid
) const
359 MOZ_ASSERT(toShape() == aKid
);
361 MOZ_ASSERT(isHash());
362 KidsHash
* hash
= toHash();
363 KidsHash::Ptr ptr
= hash
->lookup(StackShape(aKid
));
364 MOZ_ASSERT(*ptr
== aKid
);
369 Shape::dump(JSContext
* cx
, FILE* fp
) const
371 /* This is only used from gdb, so allowing GC here would just be confusing. */
372 gc::AutoSuppressGC
suppress(cx
);
374 jsid propid
= this->propid();
376 MOZ_ASSERT(!JSID_IS_VOID(propid
));
378 if (JSID_IS_INT(propid
)) {
379 fprintf(fp
, "[%ld]", (long) JSID_TO_INT(propid
));
380 } else if (JSID_IS_ATOM(propid
)) {
381 if (JSLinearString
* str
= JSID_TO_ATOM(propid
))
382 FileEscapedString(fp
, str
, '"');
384 fputs("<error>", fp
);
386 MOZ_ASSERT(JSID_IS_SYMBOL(propid
));
387 JSID_TO_SYMBOL(propid
)->dump(fp
);
390 fprintf(fp
, " g/s %p/%p slot %d attrs %x ",
391 JS_FUNC_TO_DATA_PTR(void*, getter()),
392 JS_FUNC_TO_DATA_PTR(void*, setter()),
393 hasSlot() ? slot() : -1, attrs
);
398 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(&(" " #display)[first], fp), first = 0
399 DUMP_ATTR(ENUMERATE
, enumerate
);
400 DUMP_ATTR(READONLY
, readonly
);
401 DUMP_ATTR(PERMANENT
, permanent
);
402 DUMP_ATTR(GETTER
, getter
);
403 DUMP_ATTR(SETTER
, setter
);
404 DUMP_ATTR(SHARED
, shared
);
409 fprintf(fp
, "flags %x ", flags
);
413 #define DUMP_FLAG(name, display) if (flags & name) fputs(&(" " #display)[first], fp), first = 0
414 DUMP_FLAG(IN_DICTIONARY
, in_dictionary
);
421 Shape::dumpSubtree(JSContext
* cx
, int level
, FILE* fp
) const
424 MOZ_ASSERT(level
== 0);
425 MOZ_ASSERT(JSID_IS_EMPTY(propid_
));
426 fprintf(fp
, "class %s emptyShape\n", getObjectClass()->name
);
428 fprintf(fp
, "%*sid ", level
, "");
432 if (!kids
.isNull()) {
434 if (kids
.isShape()) {
435 Shape
* kid
= kids
.toShape();
436 MOZ_ASSERT(kid
->parent
== this);
437 kid
->dumpSubtree(cx
, level
, fp
);
439 const KidsHash
& hash
= *kids
.toHash();
440 for (KidsHash::Range range
= hash
.all(); !range
.empty(); range
.popFront()) {
441 Shape
* kid
= range
.front();
443 MOZ_ASSERT(kid
->parent
== this);
444 kid
->dumpSubtree(cx
, level
, fp
);