diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index ce5e6b9c5ca..5ae31d591e5 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -87,6 +87,7 @@ enum AllocKind { FINALIZE_SCRIPT, FINALIZE_LAZY_SCRIPT, FINALIZE_SHAPE, + FINALIZE_ACCESSOR_SHAPE, FINALIZE_BASE_SHAPE, FINALIZE_TYPE_OBJECT, FINALIZE_FAT_INLINE_STRING, @@ -119,6 +120,7 @@ MapAllocToTraceKind(AllocKind kind) JSTRACE_SCRIPT, /* FINALIZE_SCRIPT */ JSTRACE_LAZY_SCRIPT,/* FINALIZE_LAZY_SCRIPT */ JSTRACE_SHAPE, /* FINALIZE_SHAPE */ + JSTRACE_SHAPE, /* FINALIZE_ACCESSOR_SHAPE */ JSTRACE_BASE_SHAPE, /* FINALIZE_BASE_SHAPE */ JSTRACE_TYPE_OBJECT,/* FINALIZE_TYPE_OBJECT */ JSTRACE_STRING, /* FINALIZE_FAT_INLINE_STRING */ diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 13a7c4b25e3..67ad20bb1a7 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -1214,6 +1214,12 @@ ScanShape(GCMarker *gcmarker, Shape *shape) else if (JSID_IS_SYMBOL(id)) PushMarkStack(gcmarker, JSID_TO_SYMBOL(id)); + if (shape->hasGetterObject()) + MaybePushMarkStackBetweenSlices(gcmarker, shape->getterObject()); + + if (shape->hasSetterObject()) + MaybePushMarkStackBetweenSlices(gcmarker, shape->setterObject()); + shape = shape->previous(); if (shape && shape->markIfUnmarked(gcmarker->getMarkColor())) goto restart; @@ -1226,12 +1232,6 @@ ScanBaseShape(GCMarker *gcmarker, BaseShape *base) base->compartment()->mark(); - if (base->hasGetterObject()) - MaybePushMarkStackBetweenSlices(gcmarker, base->getterObject()); - - if (base->hasSetterObject()) - MaybePushMarkStackBetweenSlices(gcmarker, base->setterObject()); - if (JSObject *parent = base->getObjectParent()) { MaybePushMarkStackBetweenSlices(gcmarker, parent); } else if (GlobalObject *global = base->compartment()->unsafeUnbarrieredMaybeGlobal()) { @@ -1427,9 +1427,8 @@ gc::MarkChildren(JSTracer *trc, BaseShape *base) * This function is used by the cycle collector to trace through the * children of a BaseShape (and its baseUnowned(), if any). The cycle * collector does not directly care about BaseShapes, so only the - * getter, setter, and parent are marked. Furthermore, the parent is - * marked only if it isn't the same as prevParent, which will be - * updated to the current shape's parent. + * parent is marked. Furthermore, the parent is marked only if it isn't the + * same as prevParent, which will be updated to the current shape's parent. */ static inline void MarkCycleCollectorChildren(JSTracer *trc, BaseShape *base, JSObject **prevParent) @@ -1438,23 +1437,10 @@ MarkCycleCollectorChildren(JSTracer *trc, BaseShape *base, JSObject **prevParent /* * The cycle collector does not need to trace unowned base shapes, - * as they have the same getter, setter and parent as the original - * base shape. + * as they have the same parent as the original base shape. */ base->assertConsistency(); - if (base->hasGetterObject()) { - JSObject *tmp = base->getterObject(); - MarkObjectUnbarriered(trc, &tmp, "getter"); - MOZ_ASSERT(tmp == base->getterObject()); - } - - if (base->hasSetterObject()) { - JSObject *tmp = base->setterObject(); - MarkObjectUnbarriered(trc, &tmp, "setter"); - MOZ_ASSERT(tmp == base->setterObject()); - } - JSObject *parent = base->getObjectParent(); if (parent && parent != *prevParent) { MarkObjectUnbarriered(trc, &parent, "parent"); @@ -1478,6 +1464,19 @@ gc::MarkCycleCollectorChildren(JSTracer *trc, Shape *shape) do { MarkCycleCollectorChildren(trc, shape->base(), &prevParent); MarkId(trc, &shape->propidRef(), "propid"); + + if (shape->hasGetterObject()) { + JSObject *tmp = shape->getterObject(); + MarkObjectUnbarriered(trc, &tmp, "getter"); + MOZ_ASSERT(tmp == shape->getterObject()); + } + + if (shape->hasSetterObject()) { + JSObject *tmp = shape->setterObject(); + MarkObjectUnbarriered(trc, &tmp, "setter"); + MOZ_ASSERT(tmp == shape->setterObject()); + } + shape = shape->previous(); } while (shape); } diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index 99208772dd7..f94be41396d 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -357,7 +357,14 @@ StackShape::trace(JSTracer *trc) { if (base) MarkBaseShapeRoot(trc, (BaseShape**) &base, "StackShape base"); + MarkIdRoot(trc, (jsid*) &propid, "StackShape id"); + + if ((attrs & JSPROP_GETTER) && rawGetter) + MarkObjectRoot(trc, (JSObject**)&rawGetter, "StackShape getter"); + + if ((attrs & JSPROP_SETTER) && rawSetter) + MarkObjectRoot(trc, (JSObject**)&rawSetter, "StackShape setter"); } void diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index ef0ddf6411c..5b833ab4f62 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -282,6 +282,7 @@ const uint32_t Arena::ThingSizes[] = CHECK_MIN_THING_SIZE( sizeof(JSScript), /* FINALIZE_SCRIPT */ sizeof(LazyScript), /* FINALIZE_LAZY_SCRIPT */ sizeof(Shape), /* FINALIZE_SHAPE */ + sizeof(AccessorShape), /* FINALIZE_ACCESSOR_SHAPE */ sizeof(BaseShape), /* FINALIZE_BASE_SHAPE */ sizeof(types::TypeObject), /* FINALIZE_TYPE_OBJECT */ sizeof(JSFatInlineString), /* FINALIZE_FAT_INLINE_STRING */ @@ -312,6 +313,7 @@ const uint32_t Arena::FirstThingOffsets[] = { OFFSET(JSScript), /* FINALIZE_SCRIPT */ OFFSET(LazyScript), /* FINALIZE_LAZY_SCRIPT */ OFFSET(Shape), /* FINALIZE_SHAPE */ + OFFSET(AccessorShape), /* FINALIZE_ACCESSOR_SHAPE */ OFFSET(BaseShape), /* FINALIZE_BASE_SHAPE */ OFFSET(types::TypeObject), /* FINALIZE_TYPE_OBJECT */ OFFSET(JSFatInlineString), /* FINALIZE_FAT_INLINE_STRING */ @@ -397,6 +399,7 @@ static const AllocKind BackgroundPhaseStringsAndSymbols[] = { static const AllocKind BackgroundPhaseShapes[] = { FINALIZE_SHAPE, + FINALIZE_ACCESSOR_SHAPE, FINALIZE_BASE_SHAPE, FINALIZE_TYPE_OBJECT }; @@ -623,6 +626,8 @@ FinalizeArenas(FreeOp *fop, return FinalizeTypedArenas(fop, src, dest, thingKind, budget); case FINALIZE_SHAPE: return FinalizeTypedArenas(fop, src, dest, thingKind, budget); + case FINALIZE_ACCESSOR_SHAPE: + return FinalizeTypedArenas(fop, src, dest, thingKind, budget); case FINALIZE_BASE_SHAPE: return FinalizeTypedArenas(fop, src, dest, thingKind, budget); case FINALIZE_TYPE_OBJECT: @@ -2684,6 +2689,7 @@ ArenaLists::queueShapesForSweep(FreeOp *fop) gcstats::AutoPhase ap(fop->runtime()->gc.stats, gcstats::PHASE_SWEEP_SHAPE); queueForBackgroundSweep(fop, FINALIZE_SHAPE); + queueForBackgroundSweep(fop, FINALIZE_ACCESSOR_SHAPE); queueForBackgroundSweep(fop, FINALIZE_BASE_SHAPE); queueForBackgroundSweep(fop, FINALIZE_TYPE_OBJECT); } @@ -4881,6 +4887,8 @@ GCRuntime::beginSweepingZoneGroup() zone->allocator.arenas.queueShapesForSweep(&fop); zone->allocator.arenas.gcShapeArenasToSweep = zone->allocator.arenas.arenaListsToSweep[FINALIZE_SHAPE]; + zone->allocator.arenas.gcAccessorShapeArenasToSweep = + zone->allocator.arenas.arenaListsToSweep[FINALIZE_ACCESSOR_SHAPE]; } finalizePhase = 0; @@ -4978,6 +4986,25 @@ GCRuntime::drainMarkStack(SliceBudget &sliceBudget, gcstats::Phase phase) return marker.drainMarkStack(sliceBudget); } +static bool +SweepShapes(ArenaHeader **arenasToSweep, size_t thingsPerArena, SliceBudget &sliceBudget) +{ + while (ArenaHeader *arena = *arenasToSweep) { + for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) { + Shape *shape = i.get(); + if (!shape->isMarked()) + shape->sweep(); + } + + *arenasToSweep = arena->next; + sliceBudget.step(thingsPerArena); + if (sliceBudget.isOverBudget()) + return false; /* Yield to the mutator. */ + } + + return true; +} + bool GCRuntime::sweepPhase(SliceBudget &sliceBudget) { @@ -5023,17 +5050,17 @@ GCRuntime::sweepPhase(SliceBudget &sliceBudget) for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) { Zone *zone = sweepZone; - while (ArenaHeader *arena = zone->allocator.arenas.gcShapeArenasToSweep) { - for (ArenaCellIterUnderGC i(arena); !i.done(); i.next()) { - Shape *shape = i.get(); - if (!shape->isMarked()) - shape->sweep(); - } - - zone->allocator.arenas.gcShapeArenasToSweep = arena->next; - sliceBudget.step(Arena::thingsPerArena(Arena::thingSize(FINALIZE_SHAPE))); - if (sliceBudget.isOverBudget()) - return false; /* Yield to the mutator. */ + if (!SweepShapes(&zone->allocator.arenas.gcShapeArenasToSweep, + Arena::thingsPerArena(Arena::thingSize(FINALIZE_SHAPE)), + sliceBudget)) + { + return false; /* Yield to the mutator. */ + } + if (!SweepShapes(&zone->allocator.arenas.gcAccessorShapeArenasToSweep, + Arena::thingsPerArena(Arena::thingSize(FINALIZE_ACCESSOR_SHAPE)), + sliceBudget)) + { + return false; /* Yield to the mutator. */ } } } diff --git a/js/src/jsgc.h b/js/src/jsgc.h index 226bbd784d2..a6e3292140a 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -62,6 +62,7 @@ template struct MapTypeToFinalizeKind {}; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_SCRIPT; }; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_LAZY_SCRIPT; }; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_SHAPE; }; +template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_ACCESSOR_SHAPE; }; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_BASE_SHAPE; }; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_TYPE_OBJECT; }; template <> struct MapTypeToFinalizeKind { static const AllocKind kind = FINALIZE_FAT_INLINE_STRING; }; @@ -91,6 +92,7 @@ IsNurseryAllocable(AllocKind kind) false, /* FINALIZE_SCRIPT */ false, /* FINALIZE_LAZY_SCRIPT */ false, /* FINALIZE_SHAPE */ + false, /* FINALIZE_ACCESSOR_SHAPE */ false, /* FINALIZE_BASE_SHAPE */ false, /* FINALIZE_TYPE_OBJECT */ false, /* FINALIZE_FAT_INLINE_STRING */ @@ -128,6 +130,7 @@ IsFJNurseryAllocable(AllocKind kind) false, /* FINALIZE_SCRIPT */ false, /* FINALIZE_LAZY_SCRIPT */ false, /* FINALIZE_SHAPE */ + false, /* FINALIZE_ACCESSOR_SHAPE */ false, /* FINALIZE_BASE_SHAPE */ false, /* FINALIZE_TYPE_OBJECT */ false, /* FINALIZE_FAT_INLINE_STRING */ @@ -161,6 +164,7 @@ IsBackgroundFinalized(AllocKind kind) false, /* FINALIZE_SCRIPT */ false, /* FINALIZE_LAZY_SCRIPT */ true, /* FINALIZE_SHAPE */ + true, /* FINALIZE_ACCESSOR_SHAPE */ true, /* FINALIZE_BASE_SHAPE */ true, /* FINALIZE_TYPE_OBJECT */ true, /* FINALIZE_FAT_INLINE_STRING */ @@ -615,6 +619,7 @@ class ArenaLists /* Shape arenas to be swept in the foreground. */ ArenaHeader *gcShapeArenasToSweep; + ArenaHeader *gcAccessorShapeArenasToSweep; public: ArenaLists() { @@ -626,6 +631,7 @@ class ArenaLists arenaListsToSweep[i] = nullptr; incrementalSweptArenaKind = FINALIZE_LIMIT; gcShapeArenasToSweep = nullptr; + gcAccessorShapeArenasToSweep = nullptr; } ~ArenaLists() { diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index b0733ee0327..c1bef06c4f1 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -738,6 +738,18 @@ NewGCExternalString(js::ThreadSafeContext *cx) return js::gc::AllocateNonObject(cx); } +inline Shape * +NewGCShape(ThreadSafeContext *cx) +{ + return gc::AllocateNonObject(cx); +} + +inline Shape * +NewGCAccessorShape(ThreadSafeContext *cx) +{ + return gc::AllocateNonObject(cx); +} + } /* namespace js */ inline JSScript * @@ -752,12 +764,6 @@ js_NewGCLazyScript(js::ThreadSafeContext *cx) return js::gc::AllocateNonObject(cx); } -inline js::Shape * -js_NewGCShape(js::ThreadSafeContext *cx) -{ - return js::gc::AllocateNonObject(cx); -} - template inline js::BaseShape * js_NewGCBaseShape(js::ThreadSafeContext *cx) diff --git a/js/src/jspropertytree.cpp b/js/src/jspropertytree.cpp index 57ea9112b0e..a4af3469d40 100644 --- a/js/src/jspropertytree.cpp +++ b/js/src/jspropertytree.cpp @@ -31,15 +31,6 @@ ShapeHasher::match(const Key k, const Lookup &l) return k->matches(l); } -Shape * -PropertyTree::newShape(ExclusiveContext *cx) -{ - Shape *shape = js_NewGCShape(cx); - if (!shape) - js_ReportOutOfMemory(cx); - return shape; -} - static KidsHash * HashChildren(Shape *kid1, Shape *kid2) { @@ -113,9 +104,15 @@ Shape::removeChild(Shape *child) KidsHash *hash = kidp->toHash(); MOZ_ASSERT(hash->count() >= 2); /* otherwise kidp->isShape() should be true */ +#ifdef DEBUG + size_t oldCount = hash->count(); +#endif + hash->remove(StackShape(child)); child->parent = nullptr; + MOZ_ASSERT(hash->count() == oldCount - 1); + if (hash->count() == 1) { /* Convert from HASH form back to SHAPE form. */ KidsHash::Range r = hash->all(); @@ -184,14 +181,10 @@ PropertyTree::getChild(ExclusiveContext *cx, Shape *parentArg, StackShape &unroo if (existingShape) return existingShape; - RootedGeneric child(cx, &unrootedChild); - - Shape *shape = newShape(cx); + Shape *shape = Shape::new_(cx, unrootedChild, parent->numFixedSlots()); if (!shape) return nullptr; - new (shape) Shape(*child, parent->numFixedSlots()); - if (!insertChild(cx, parent, shape)) return nullptr; @@ -287,8 +280,10 @@ Shape::fixupDictionaryShapeAfterMovingGC() MOZ_ASSERT(!IsInsideNursery(reinterpret_cast(listp))); AllocKind kind = TenuredCell::fromPointer(listp)->getAllocKind(); - MOZ_ASSERT(kind == FINALIZE_SHAPE || kind <= FINALIZE_OBJECT_LAST); - if (kind == FINALIZE_SHAPE) { + MOZ_ASSERT(kind == FINALIZE_SHAPE || + kind == FINALIZE_ACCESSOR_SHAPE || + kind <= FINALIZE_OBJECT_LAST); + if (kind == FINALIZE_SHAPE || kind == FINALIZE_ACCESSOR_SHAPE) { // listp points to the parent field of the next shape. Shape *next = reinterpret_cast(uintptr_t(listp) - offsetof(Shape, parent)); @@ -317,21 +312,30 @@ Shape::fixupShapeTreeAfterMovingGC() KidsHash *kh = kids.toHash(); for (KidsHash::Enum e(*kh); !e.empty(); e.popFront()) { Shape *key = e.front(); - if (!IsForwarded(key)) - continue; + if (IsForwarded(key)) + key = Forwarded(key); - key = Forwarded(key); BaseShape *base = key->base(); if (IsForwarded(base)) base = Forwarded(base); UnownedBaseShape *unowned = base->unowned(); if (IsForwarded(unowned)) unowned = Forwarded(unowned); + + PropertyOp getter = key->getter(); + if (key->hasGetterObject() && IsForwarded(key->getterObject())) + getter = PropertyOp(Forwarded(key->getterObject())); + + StrictPropertyOp setter = key->setter(); + if (key->hasSetterObject() && IsForwarded(key->setterObject())) + setter = StrictPropertyOp(Forwarded(key->setterObject())); + StackShape lookup(unowned, const_cast(key)->propidRef(), key->slotInfo & Shape::SLOT_MASK, key->attrs, key->flags); + lookup.updateGetterSetter(getter, setter); e.rekeyFront(lookup, key); } } @@ -347,6 +351,34 @@ Shape::fixupAfterMovingGC() #endif // JSGC_COMPACTING +#ifdef JSGC_GENERATIONAL +void +ShapeGetterSetterRef::mark(JSTracer *trc) +{ + // Update the current shape's entry in the parent KidsHash table if needed. + // This is necessary as the computed hash includes the getter/setter + // pointers. + + JSObject *obj = *objp; + JSObject *prior = obj; + trc->setTracingLocation(&*prior); + gc::Mark(trc, &obj, "AccessorShape getter or setter"); + if (obj == *objp) + return; + + Shape *parent = shape->parent; + if (shape->inDictionary() || !parent->kids.isHash()) { + *objp = obj; + return; + } + + KidsHash *kh = parent->kids.toHash(); + kh->remove(StackShape(shape)); + *objp = obj; + MOZ_ALWAYS_TRUE(kh->putNew(StackShape(shape), shape)); +} +#endif + #ifdef DEBUG void @@ -385,8 +417,8 @@ Shape::dump(JSContext *cx, FILE *fp) const } fprintf(fp, " g/s %p/%p slot %d attrs %x ", - JS_FUNC_TO_DATA_PTR(void *, base()->rawGetter), - JS_FUNC_TO_DATA_PTR(void *, base()->rawSetter), + JS_FUNC_TO_DATA_PTR(void *, getter()), + JS_FUNC_TO_DATA_PTR(void *, setter()), hasSlot() ? slot() : -1, attrs); if (attrs) { diff --git a/js/src/jspropertytree.h b/js/src/jspropertytree.h index 7d875de4e18..6cbc85b660f 100644 --- a/js/src/jspropertytree.h +++ b/js/src/jspropertytree.h @@ -96,7 +96,6 @@ class PropertyTree JSCompartment *compartment() { return compartment_; } - Shape *newShape(ExclusiveContext *cx); Shape *getChild(ExclusiveContext *cx, Shape *parent, StackShape &child); Shape *lookupChild(ThreadSafeContext *cx, Shape *parent, const StackShape &child); }; diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index d2393b9d4d2..ad431c3b346 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -407,7 +407,8 @@ class NativeObject : public JSObject Shape * replaceWithNewEquivalentShape(ThreadSafeContext *cx, - Shape *existingShape, Shape *newShape = nullptr); + Shape *existingShape, Shape *newShape = nullptr, + bool accessorShape = false); /* * Remove the last property of an object, provided that it is safe to do so diff --git a/js/src/vm/Shape-inl.h b/js/src/vm/Shape-inl.h index 3920a00f357..96053e2290a 100644 --- a/js/src/vm/Shape-inl.h +++ b/js/src/vm/Shape-inl.h @@ -30,8 +30,6 @@ StackBaseShape::StackBaseShape(ThreadSafeContext *cx, const Class *clasp, clasp(clasp), parent(parent), metadata(metadata), - rawGetter(nullptr), - rawSetter(nullptr), compartment(cx->compartment_) {} @@ -153,6 +151,24 @@ Shape::search(ExclusiveContext *cx, Shape *start, jsid id, Shape ***pspp, bool a return nullptr; } +inline Shape * +Shape::new_(ExclusiveContext *cx, StackShape &unrootedOther, uint32_t nfixed) +{ + RootedGeneric other(cx, &unrootedOther); + Shape *shape = other->isAccessorShape() ? NewGCAccessorShape(cx) : NewGCShape(cx); + if (!shape) { + js_ReportOutOfMemory(cx); + return nullptr; + } + + if (other->isAccessorShape()) + new (shape) AccessorShape(*other, nfixed); + else + new (shape) Shape(*other, nfixed); + + return shape; +} + template /* static */ inline bool EmptyShape::ensureInitialCustomShape(ExclusiveContext *cx, Handle obj) diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 6780d1971c4..a2ca9a922eb 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -385,7 +385,7 @@ NativeObject::getChildPropertyOnDictionary(ThreadSafeContext *cx, HandleNativeOb if (obj->inDictionaryMode()) { MOZ_ASSERT(parent == obj->lastProperty()); RootedGeneric childRoot(cx, &child); - shape = js_NewGCShape(cx); + shape = childRoot->isAccessorShape() ? NewGCAccessorShape(cx) : NewGCShape(cx); if (!shape) return nullptr; if (childRoot->hasSlot() && childRoot->slot() >= obj->lastProperty()->base()->slotSpan()) { @@ -467,7 +467,7 @@ js::NativeObject::toDictionaryMode(ThreadSafeContext *cx) while (shape) { MOZ_ASSERT(!shape->inDictionary()); - Shape *dprop = js_NewGCShape(cx); + Shape *dprop = shape->isAccessorShape() ? NewGCAccessorShape(cx) : NewGCShape(cx); if (!dprop) { js_ReportOutOfMemory(cx); return false; @@ -626,19 +626,18 @@ NativeObject::addPropertyInternal(typename ExecutionModeTraits::ExclusiveC bool indexed = js_IdIsIndex(id, &index); Rooted nbase(cx); - if (last->base()->matchesGetterSetter(getter, setter) && !indexed) { + if (!indexed) { nbase = last->base()->unowned(); } else { StackBaseShape base(last->base()); - base.updateGetterSetter(attrs, getter, setter); - if (indexed) - base.flags |= BaseShape::INDEXED; + base.flags |= BaseShape::INDEXED; nbase = GetOrLookupUnownedBaseShape(cx, base); if (!nbase) return nullptr; } StackShape child(nbase, id, slot, attrs, flags); + child.updateGetterSetter(getter, setter); shape = getOrLookupChildProperty(cx, obj, last, child); } @@ -845,7 +844,6 @@ NativeObject::putProperty(typename ExecutionModeTraits::ExclusiveContextTy uint32_t index; bool indexed = js_IdIsIndex(id, &index); StackBaseShape base(obj->lastProperty()->base()); - base.updateGetterSetter(attrs, getter, setter); if (indexed) base.flags |= BaseShape::INDEXED; nbase = GetOrLookupUnownedBaseShape(cx, base); @@ -857,7 +855,7 @@ NativeObject::putProperty(typename ExecutionModeTraits::ExclusiveContextTy * Now that we've possibly preserved slot, check whether all members match. * If so, this is a redundant "put" and we can return without more work. */ - if (shape->matchesParamsAfterId(nbase, slot, attrs, flags)) + if (shape->matchesParamsAfterId(nbase, slot, attrs, flags, getter, setter)) return shape; /* @@ -882,7 +880,8 @@ NativeObject::putProperty(typename ExecutionModeTraits::ExclusiveContextTy * is also the last property). */ bool updateLast = (shape == obj->lastProperty()); - shape = obj->replaceWithNewEquivalentShape(cx, shape); + bool accessorShape = getter || setter || (attrs & (JSPROP_GETTER | JSPROP_SETTER)); + shape = obj->replaceWithNewEquivalentShape(cx, shape, nullptr, accessorShape); if (!shape) return nullptr; if (!updateLast && !obj->generateOwnShape(cx)) @@ -903,14 +902,25 @@ NativeObject::putProperty(typename ExecutionModeTraits::ExclusiveContextTy shape->setSlot(slot); shape->attrs = uint8_t(attrs); - shape->flags = flags | Shape::IN_DICTIONARY; + shape->flags = flags | Shape::IN_DICTIONARY | (accessorShape ? Shape::ACCESSOR_SHAPE : 0); + if (shape->isAccessorShape()) { + AccessorShape &accShape = shape->asAccessorShape(); + accShape.rawGetter = getter; + if (accShape.hasGetterObject()) + GetterSetterWriteBarrierPost(&accShape, &accShape.getterObj); + accShape.rawSetter = setter; + if (accShape.hasSetterObject()) + GetterSetterWriteBarrierPost(&accShape, &accShape.setterObj); + } else { + MOZ_ASSERT(!getter); + MOZ_ASSERT(!setter); + } } else { /* * Updating the last property in a non-dictionary-mode object. Find an * alternate shared child of the last property's previous shape. */ StackBaseShape base(obj->lastProperty()->base()); - base.updateGetterSetter(attrs, getter, setter); UnownedBaseShape *nbase = GetOrLookupUnownedBaseShape(cx, base); if (!nbase) @@ -920,6 +930,7 @@ NativeObject::putProperty(typename ExecutionModeTraits::ExclusiveContextTy /* Find or create a property tree node labeled by our arguments. */ StackShape child(nbase, id, slot, attrs, flags); + child.updateGetterSetter(getter, setter); RootedShape parent(cx, shape->parent); Shape *newShape = getOrLookupChildProperty(cx, obj, parent, child); @@ -1053,7 +1064,8 @@ NativeObject::removeProperty(ExclusiveContext *cx, jsid id_) */ RootedShape spare(cx); if (self->inDictionaryMode()) { - spare = js_NewGCShape(cx); + /* For simplicity, always allocate an accessor shape for now. */ + spare = NewGCAccessorShape(cx); if (!spare) return false; new (spare) Shape(shape->base()->unowned(), 0); @@ -1066,7 +1078,6 @@ NativeObject::removeProperty(ExclusiveContext *cx, jsid id_) */ RootedShape previous(cx, self->lastProperty()->parent); StackBaseShape base(self->lastProperty()->base()); - base.updateGetterSetter(previous->attrs, previous->getter(), previous->setter()); BaseShape *nbase = BaseShape::getUnowned(cx, base); if (!nbase) return false; @@ -1188,7 +1199,8 @@ NativeObject::rollbackProperties(ExclusiveContext *cx, HandleNativeObject obj, u } Shape * -NativeObject::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape) +NativeObject::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldShape, Shape *newShape, + bool accessorShape) { MOZ_ASSERT(cx->isThreadLocal(this)); MOZ_ASSERT(cx->isThreadLocal(oldShape)); @@ -1214,7 +1226,9 @@ NativeObject::replaceWithNewEquivalentShape(ThreadSafeContext *cx, Shape *oldSha if (!newShape) { RootedNativeObject selfRoot(cx, self); RootedShape oldRoot(cx, oldShape); - newShape = js_NewGCShape(cx); + newShape = (oldShape->isAccessorShape() || accessorShape) + ? NewGCAccessorShape(cx) + : NewGCShape(cx); if (!newShape) return nullptr; new (newShape) Shape(oldRoot->base()->unowned(), 0); @@ -1430,8 +1444,6 @@ StackBaseShape::hash(const StackBaseShape *base) hash = RotateLeft(hash, 4) ^ (uintptr_t(base->clasp) >> 3); hash = RotateLeft(hash, 4) ^ (uintptr_t(base->parent) >> 3); hash = RotateLeft(hash, 4) ^ (uintptr_t(base->metadata) >> 3); - hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawGetter); - hash = RotateLeft(hash, 4) ^ uintptr_t(base->rawSetter); return hash; } @@ -1441,30 +1453,17 @@ StackBaseShape::match(UnownedBaseShape *key, const StackBaseShape *lookup) return key->flags == lookup->flags && key->clasp_ == lookup->clasp && key->parent == lookup->parent - && key->metadata == lookup->metadata - && key->rawGetter == lookup->rawGetter - && key->rawSetter == lookup->rawSetter; + && key->metadata == lookup->metadata; } void StackBaseShape::trace(JSTracer *trc) { - if (parent) { - gc::MarkObjectRoot(trc, (JSObject**)&parent, - "StackBaseShape parent"); - } - if (metadata) { - gc::MarkObjectRoot(trc, (JSObject**)&metadata, - "StackBaseShape metadata"); - } - if ((flags & BaseShape::HAS_GETTER_OBJECT) && rawGetter) { - gc::MarkObjectRoot(trc, (JSObject**)&rawGetter, - "StackBaseShape getter"); - } - if ((flags & BaseShape::HAS_SETTER_OBJECT) && rawSetter) { - gc::MarkObjectRoot(trc, (JSObject**)&rawSetter, - "StackBaseShape setter"); - } + if (parent) + gc::MarkObjectRoot(trc, (JSObject**)&parent, "StackBaseShape parent"); + + if (metadata) + gc::MarkObjectRoot(trc, (JSObject**)&metadata, "StackBaseShape metadata"); } /* static */ UnownedBaseShape* @@ -1513,10 +1512,6 @@ BaseShape::assertConsistency() #ifdef DEBUG if (isOwned()) { UnownedBaseShape *unowned = baseUnowned(); - MOZ_ASSERT(hasGetterObject() == unowned->hasGetterObject()); - MOZ_ASSERT(hasSetterObject() == unowned->hasSetterObject()); - MOZ_ASSERT_IF(hasGetterObject(), getterObject() == unowned->getterObject()); - MOZ_ASSERT_IF(hasSetterObject(), setterObject() == unowned->setterObject()); MOZ_ASSERT(getObjectParent() == unowned->getObjectParent()); MOZ_ASSERT(getObjectMetadata() == unowned->getObjectMetadata()); MOZ_ASSERT(getObjectFlags() == unowned->getObjectFlags()); @@ -1698,6 +1693,19 @@ JSCompartment::checkInitialShapesTableAfterMovingGC() #endif // JSGC_HASH_TABLE_CHECKS +Shape * +EmptyShape::new_(ExclusiveContext *cx, Handle base, uint32_t nfixed) +{ + Shape *shape = NewGCShape(cx); + if (!shape) { + js_ReportOutOfMemory(cx); + return nullptr; + } + + new (shape) EmptyShape(base, nfixed); + return shape; +} + /* static */ Shape * EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto, JSObject *parent, JSObject *metadata, @@ -1726,10 +1734,9 @@ EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProt if (!nbase) return nullptr; - Shape *shape = cx->compartment()->propertyTree.newShape(cx); + Shape *shape = EmptyShape::new_(cx, nbase, nfixed); if (!shape) return nullptr; - new (shape) EmptyShape(nbase, nfixed); Lookup lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags); if (!p.add(cx, table, lookup, InitialShapeEntry(ReadBarrieredShape(shape), protoRoot))) diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index 92e384e2034..20051adee94 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -98,6 +98,9 @@ * trees are more space-efficient than alternatives. This was removed in bug * 631138; see that bug for the full details. * + * For getters/setters, an AccessorShape is allocated. This is a slightly fatter + * type with extra fields for the getter/setter data. + * * Because many Shapes have similar data, there is actually a secondary type * called a BaseShape that holds some of a Shape's data. Many shapes can share * a single BaseShape. @@ -251,6 +254,7 @@ struct ShapeTable { * an earlier property, however. */ +class AccessorShape; class Shape; class UnownedBaseShape; struct StackBaseShape; @@ -259,16 +263,33 @@ namespace gc { void MergeCompartments(JSCompartment *source, JSCompartment *target); } +#ifdef JSGC_GENERATIONAL +// This class is used to add a post barrier on the AccessorShape's getter/setter +// objects. It updates the shape's entry in the parent's KidsHash table. +class ShapeGetterSetterRef : public gc::BufferableRef +{ + AccessorShape *shape; + JSObject **objp; + + public: + ShapeGetterSetterRef(AccessorShape *shape, JSObject **objp) + : shape(shape), objp(objp) + {} + + void mark(JSTracer *trc); +}; +#endif + static inline void -GetterSetterWriteBarrierPost(JSRuntime *rt, JSObject **objp) +GetterSetterWriteBarrierPost(AccessorShape *shape, JSObject **objp) { #ifdef JSGC_GENERATIONAL + MOZ_ASSERT(shape); MOZ_ASSERT(objp); MOZ_ASSERT(*objp); gc::Cell **cellp = reinterpret_cast(objp); - gc::StoreBuffer *storeBuffer = (*cellp)->storeBuffer(); - if (storeBuffer) - storeBuffer->putRelocatableCellFromAnyThread(cellp); + if (gc::StoreBuffer *sb = (*cellp)->storeBuffer()) + sb->putGeneric(ShapeGetterSetterRef(shape, objp)); #endif } @@ -293,9 +314,7 @@ class BaseShape : public gc::TenuredCell /* Owned by the referring shape. */ OWNED_SHAPE = 0x1, - /* getterObj/setterObj are active in unions below. */ - HAS_GETTER_OBJECT = 0x2, - HAS_SETTER_OBJECT = 0x4, + /* (0x2 and 0x4 are unused) */ /* * Flags set which describe the referring object. Once set these cannot @@ -341,18 +360,6 @@ class BaseShape : public gc::TenuredCell uint32_t slotSpan_; /* Object slot span for BaseShapes at * dictionary last properties. */ - union { - PropertyOp rawGetter; /* getter hook for shape */ - JSObject *getterObj; /* user-defined callable "get" object or - null if shape->hasGetterValue() */ - }; - - union { - StrictPropertyOp rawSetter; /* setter hook for shape */ - JSObject *setterObj; /* user-defined callable "set" object or - null if shape->hasSetterValue() */ - }; - /* For owned BaseShapes, the canonical unowned BaseShape. */ HeapPtrUnownedBaseShape unowned_; @@ -377,8 +384,7 @@ class BaseShape : public gc::TenuredCell } BaseShape(JSCompartment *comp, const Class *clasp, JSObject *parent, JSObject *metadata, - uint32_t objectFlags, uint8_t attrs, - PropertyOp rawGetter, StrictPropertyOp rawSetter) + uint32_t objectFlags, uint8_t attrs) { MOZ_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK)); mozilla::PodZero(this); @@ -386,16 +392,6 @@ class BaseShape : public gc::TenuredCell this->parent = parent; this->metadata = metadata; this->flags = objectFlags; - this->rawGetter = rawGetter; - this->rawSetter = rawSetter; - if ((attrs & JSPROP_GETTER) && rawGetter) { - this->flags |= HAS_GETTER_OBJECT; - GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->getterObj); - } - if ((attrs & JSPROP_SETTER) && rawSetter) { - this->flags |= HAS_SETTER_OBJECT; - GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->setterObj); - } this->compartment_ = comp; } @@ -410,22 +406,6 @@ class BaseShape : public gc::TenuredCell metadata = other.metadata; flags = other.flags; slotSpan_ = other.slotSpan_; - if (flags & HAS_GETTER_OBJECT) { - getterObj = other.getterObj; - GetterSetterWriteBarrierPost(runtimeFromMainThread(), &getterObj); - } else { - if (rawGetter) - GetterSetterWriteBarrierPostRemove(runtimeFromMainThread(), &getterObj); - rawGetter = other.rawGetter; - } - if (flags & HAS_SETTER_OBJECT) { - setterObj = other.setterObj; - GetterSetterWriteBarrierPost(runtimeFromMainThread(), &setterObj); - } else { - if (rawSetter) - GetterSetterWriteBarrierPostRemove(runtimeFromMainThread(), &setterObj); - rawSetter = other.rawSetter; - } compartment_ = other.compartment_; return *this; } @@ -434,10 +414,6 @@ class BaseShape : public gc::TenuredCell bool isOwned() const { return !!(flags & OWNED_SHAPE); } - bool matchesGetterSetter(PropertyOp rawGetter, StrictPropertyOp rawSetter) const { - return rawGetter == this->rawGetter && rawSetter == this->rawSetter; - } - inline void adoptUnowned(UnownedBaseShape *other); void setOwned(UnownedBaseShape *unowned) { @@ -449,12 +425,6 @@ class BaseShape : public gc::TenuredCell JSObject *getObjectMetadata() const { return metadata; } uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; } - bool hasGetterObject() const { return !!(flags & HAS_GETTER_OBJECT); } - JSObject *getterObject() const { MOZ_ASSERT(hasGetterObject()); return getterObj; } - - bool hasSetterObject() const { return !!(flags & HAS_SETTER_OBJECT); } - JSObject *setterObject() const { MOZ_ASSERT(hasSetterObject()); return setterObj; } - bool hasTable() const { MOZ_ASSERT_IF(table_, isOwned()); return table_ != nullptr; } ShapeTable &table() const { MOZ_ASSERT(table_ && isOwned()); return *table_; } void setTable(ShapeTable *table) { MOZ_ASSERT(isOwned()); table_ = table; } @@ -495,12 +465,6 @@ class BaseShape : public gc::TenuredCell static inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; } void markChildren(JSTracer *trc) { - if (hasGetterObject()) - gc::MarkObjectUnbarriered(trc, &getterObj, "getter"); - - if (hasSetterObject()) - gc::MarkObjectUnbarriered(trc, &setterObj, "setter"); - if (isOwned()) gc::MarkBaseShape(trc, &unowned_, "base"); @@ -568,8 +532,6 @@ struct StackBaseShape : public DefaultHasher const Class *clasp; JSObject *parent; JSObject *metadata; - PropertyOp rawGetter; - StrictPropertyOp rawSetter; JSCompartment *compartment; explicit StackBaseShape(BaseShape *base) @@ -577,8 +539,6 @@ struct StackBaseShape : public DefaultHasher clasp(base->clasp_), parent(base->parent), metadata(base->metadata), - rawGetter(nullptr), - rawSetter(nullptr), compartment(base->compartment()) {} @@ -586,21 +546,6 @@ struct StackBaseShape : public DefaultHasher JSObject *parent, JSObject *metadata, uint32_t objectFlags); explicit inline StackBaseShape(Shape *shape); - void updateGetterSetter(uint8_t attrs, PropertyOp rawGetter, StrictPropertyOp rawSetter) { - flags &= ~(BaseShape::HAS_GETTER_OBJECT | BaseShape::HAS_SETTER_OBJECT); - if ((attrs & JSPROP_GETTER) && rawGetter) { - MOZ_ASSERT(!IsPoisonedPtr(rawGetter)); - flags |= BaseShape::HAS_GETTER_OBJECT; - } - if ((attrs & JSPROP_SETTER) && rawSetter) { - MOZ_ASSERT(!IsPoisonedPtr(rawSetter)); - flags |= BaseShape::HAS_SETTER_OBJECT; - } - - this->rawGetter = rawGetter; - this->rawSetter = rawSetter; - } - static inline HashNumber hash(const StackBaseShape *lookup); static inline bool match(UnownedBaseShape *key, const StackBaseShape *lookup); @@ -616,12 +561,6 @@ BaseShape::BaseShape(const StackBaseShape &base) this->parent = base.parent; this->metadata = base.metadata; this->flags = base.flags; - this->rawGetter = base.rawGetter; - this->rawSetter = base.rawSetter; - if ((base.flags & HAS_GETTER_OBJECT) && base.rawGetter) - GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->getterObj); - if ((base.flags & HAS_SETTER_OBJECT) && base.rawSetter) - GetterSetterWriteBarrierPost(runtimeFromMainThread(), &this->setterObj); this->compartment_ = base.compartment; } @@ -640,6 +579,7 @@ class Shape : public gc::TenuredCell friend class js::NativeObject; friend class js::PropertyTree; friend class js::StaticBlockObject; + friend class js::ShapeGetterSetterRef; friend struct js::StackShape; friend struct js::StackBaseShape; @@ -698,14 +638,7 @@ class Shape : public gc::TenuredCell void removeFromDictionary(NativeObject *obj); void insertIntoDictionary(HeapPtrShape *dictp); - void initDictionaryShape(const StackShape &child, uint32_t nfixed, HeapPtrShape *dictp) { - new (this) Shape(child, nfixed); - this->flags |= IN_DICTIONARY; - - this->listp = nullptr; - if (dictp) - insertIntoDictionary(dictp); - } + inline void initDictionaryShape(const StackShape &child, uint32_t nfixed, HeapPtrShape *dictp); /* Replace the base shape of the last shape in a non-dictionary lineage with base. */ static Shape *replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base, @@ -758,6 +691,15 @@ class Shape : public gc::TenuredCell return !(flags & NON_NATIVE); } + bool isAccessorShape() const { + MOZ_ASSERT_IF(flags & ACCESSOR_SHAPE, getAllocKind() == gc::FINALIZE_ACCESSOR_SHAPE); + return flags & ACCESSOR_SHAPE; + } + AccessorShape &asAccessorShape() const { + MOZ_ASSERT(isAccessorShape()); + return *(AccessorShape *)this; + } + const HeapPtrShape &previous() const { return parent; } JSCompartment *compartment() const { return base()->compartment(); } @@ -830,7 +772,13 @@ class Shape : public gc::TenuredCell */ OVERWRITTEN = 0x04, - UNUSED_BITS = 0x38 + /* + * This shape is an AccessorShape, a fat Shape that can store + * getter/setter information. + */ + ACCESSOR_SHAPE = 0x08, + + UNUSED_BITS = 0x3C }; /* Get a shape identical to this one, without parent/kids information. */ @@ -842,6 +790,9 @@ class Shape : public gc::TenuredCell /* Copy constructor disabled, to avoid misuse of the above form. */ Shape(const Shape &other) MOZ_DELETE; + /* Allocate a new shape based on the given StackShape. */ + static inline Shape *new_(ExclusiveContext *cx, StackShape &unrootedOther, uint32_t nfixed); + /* * Whether this shape has a valid slot value. This may be true even if * !hasSlot() (see SlotInfo comment above), and may be false even if @@ -855,38 +806,40 @@ class Shape : public gc::TenuredCell return (flags & IN_DICTIONARY) != 0; } - PropertyOp getter() const { return base()->rawGetter; } - bool hasDefaultGetter() const {return !base()->rawGetter; } - PropertyOp getterOp() const { MOZ_ASSERT(!hasGetterValue()); return base()->rawGetter; } - JSObject *getterObject() const { MOZ_ASSERT(hasGetterValue()); return base()->getterObj; } + inline PropertyOp getter() const; + bool hasDefaultGetter() const { return !getter(); } + PropertyOp getterOp() const { MOZ_ASSERT(!hasGetterValue()); return getter(); } + inline JSObject *getterObject() const; + bool hasGetterObject() const { return hasGetterValue() && getterObject(); } // Per ES5, decode null getterObj as the undefined value, which encodes as null. Value getterValue() const { MOZ_ASSERT(hasGetterValue()); - return base()->getterObj ? ObjectValue(*base()->getterObj) : UndefinedValue(); + if (JSObject *getterObj = getterObject()) + return ObjectValue(*getterObj); + return UndefinedValue(); } Value getterOrUndefined() const { - return (hasGetterValue() && base()->getterObj) - ? ObjectValue(*base()->getterObj) - : UndefinedValue(); + return hasGetterValue() ? getterValue() : UndefinedValue(); } - StrictPropertyOp setter() const { return base()->rawSetter; } - bool hasDefaultSetter() const { return !base()->rawSetter; } - StrictPropertyOp setterOp() const { MOZ_ASSERT(!hasSetterValue()); return base()->rawSetter; } - JSObject *setterObject() const { MOZ_ASSERT(hasSetterValue()); return base()->setterObj; } + inline StrictPropertyOp setter() const; + bool hasDefaultSetter() const { return !setter(); } + StrictPropertyOp setterOp() const { MOZ_ASSERT(!hasSetterValue()); return setter(); } + inline JSObject *setterObject() const; + bool hasSetterObject() const { return hasSetterValue() && setterObject(); } // Per ES5, decode null setterObj as the undefined value, which encodes as null. Value setterValue() const { MOZ_ASSERT(hasSetterValue()); - return base()->setterObj ? ObjectValue(*base()->setterObj) : UndefinedValue(); + if (JSObject *setterObj = setterObject()) + return ObjectValue(*setterObj); + return UndefinedValue(); } Value setterOrUndefined() const { - return (hasSetterValue() && base()->setterObj) - ? ObjectValue(*base()->setterObj) - : UndefinedValue(); + return hasSetterValue() ? setterValue() : UndefinedValue(); } void setOverwritten() { @@ -900,16 +853,20 @@ class Shape : public gc::TenuredCell bool matches(const Shape *other) const { return propid_.get() == other->propid_.get() && - matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags); + matchesParamsAfterId(other->base(), other->maybeSlot(), other->attrs, other->flags, + other->getter(), other->setter()); } inline bool matches(const StackShape &other) const; - bool matchesParamsAfterId(BaseShape *base, uint32_t aslot, unsigned aattrs, unsigned aflags) const + bool matchesParamsAfterId(BaseShape *base, uint32_t aslot, unsigned aattrs, unsigned aflags, + PropertyOp rawGetter, StrictPropertyOp rawSetter) const { return base->unowned() == this->base()->unowned() && maybeSlot() == aslot && - attrs == aattrs; + attrs == aattrs && + getter() == rawGetter && + setter() == rawSetter; } bool get(JSContext* cx, HandleObject receiver, JSObject *obj, JSObject *pobj, MutableHandleValue vp); @@ -1047,12 +1004,7 @@ class Shape : public gc::TenuredCell static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; } - void markChildren(JSTracer *trc) { - MarkBaseShape(trc, &base_, "base"); - gc::MarkId(trc, &propidRef(), "propid"); - if (parent) - MarkShape(trc, &parent, "parent"); - } + inline void markChildren(JSTracer *trc); inline Shape *search(ExclusiveContext *cx, jsid id); inline Shape *searchLinear(jsid id); @@ -1079,6 +1031,29 @@ class Shape : public gc::TenuredCell } }; +/* Fat Shape used for accessor properties. */ +class AccessorShape : public Shape +{ + friend class Shape; + friend class ShapeGetterSetterRef; + friend class NativeObject; + + union { + PropertyOp rawGetter; /* getter hook for shape */ + JSObject *getterObj; /* user-defined callable "get" object or + null if shape->hasGetterValue() */ + }; + union { + StrictPropertyOp rawSetter; /* setter hook for shape */ + JSObject *setterObj; /* user-defined callable "set" object or + null if shape->hasSetterValue() */ + }; + + public: + /* Get a shape identical to this one, without parent/kids information. */ + inline AccessorShape(const StackShape &other, uint32_t nfixed); +}; + inline StackBaseShape::StackBaseShape(Shape *shape) : flags(shape->getObjectFlags()), @@ -1086,9 +1061,7 @@ StackBaseShape::StackBaseShape(Shape *shape) parent(shape->getObjectParent()), metadata(shape->getObjectMetadata()), compartment(shape->compartment()) -{ - updateGetterSetter(shape->attrs, shape->getter(), shape->setter()); -} +{} class AutoRooterGetterSetter { @@ -1126,6 +1099,8 @@ struct EmptyShape : public js::Shape flags |= NON_NATIVE; } + static Shape *new_(ExclusiveContext *cx, Handle base, uint32_t nfixed); + /* * Lookup an initial shape matching the given parameters, creating an empty * shape if none was found. @@ -1233,6 +1208,8 @@ struct StackShape /* For performance, StackShape only roots when absolutely necessary. */ UnownedBaseShape *base; jsid propid; + PropertyOp rawGetter; + StrictPropertyOp rawSetter; uint32_t slot_; uint8_t attrs; uint8_t flags; @@ -1241,6 +1218,8 @@ struct StackShape unsigned attrs, unsigned flags) : base(base), propid(propid), + rawGetter(nullptr), + rawSetter(nullptr), slot_(slot), attrs(uint8_t(attrs)), flags(uint8_t(flags)) @@ -1254,11 +1233,26 @@ struct StackShape explicit StackShape(Shape *shape) : base(shape->base()->unowned()), propid(shape->propidRef()), + rawGetter(shape->getter()), + rawSetter(shape->setter()), slot_(shape->maybeSlot()), attrs(shape->attrs), flags(shape->flags) {} + void updateGetterSetter(PropertyOp rawGetter, StrictPropertyOp rawSetter) { + MOZ_ASSERT_IF((attrs & JSPROP_GETTER) && rawGetter, !IsPoisonedPtr(rawGetter)); + MOZ_ASSERT_IF((attrs & JSPROP_SETTER) && rawSetter, !IsPoisonedPtr(rawSetter)); + + if (rawGetter || rawSetter || (attrs & (JSPROP_GETTER|JSPROP_SETTER))) + flags |= Shape::ACCESSOR_SHAPE; + else + flags &= ~Shape::ACCESSOR_SHAPE; + + this->rawGetter = rawGetter; + this->rawSetter = rawSetter; + } + bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; } bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; } @@ -1275,6 +1269,10 @@ struct StackShape slot_ = slot; } + bool isAccessorShape() const { + return flags & Shape::ACCESSOR_SHAPE; + } + HashNumber hash() const { HashNumber hash = uintptr_t(base); @@ -1282,6 +1280,8 @@ struct StackShape hash = mozilla::RotateLeft(hash, 4) ^ attrs; hash = mozilla::RotateLeft(hash, 4) ^ slot_; hash = mozilla::RotateLeft(hash, 4) ^ JSID_BITS(propid); + hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawGetter); + hash = mozilla::RotateLeft(hash, 4) ^ uintptr_t(rawSetter); return hash; } @@ -1357,10 +1357,30 @@ Shape::Shape(const StackShape &other, uint32_t nfixed) flags(other.flags), parent(nullptr) { +#ifdef DEBUG + gc::AllocKind allocKind = getAllocKind(); + MOZ_ASSERT_IF(other.isAccessorShape(), allocKind == gc::FINALIZE_ACCESSOR_SHAPE); + MOZ_ASSERT_IF(allocKind == gc::FINALIZE_SHAPE, !other.isAccessorShape()); +#endif + MOZ_ASSERT_IF(attrs & (JSPROP_GETTER | JSPROP_SETTER), attrs & JSPROP_SHARED); kids.setNull(); } +inline +AccessorShape::AccessorShape(const StackShape &other, uint32_t nfixed) + : Shape(other, nfixed), + rawGetter(other.rawGetter), + rawSetter(other.rawSetter) +{ + MOZ_ASSERT(getAllocKind() == gc::FINALIZE_ACCESSOR_SHAPE); + + if ((attrs & JSPROP_GETTER) && rawGetter) + GetterSetterWriteBarrierPost(this, &this->getterObj); + if ((attrs & JSPROP_SETTER) && rawSetter) + GetterSetterWriteBarrierPost(this, &this->setterObj); +} + inline Shape::Shape(UnownedBaseShape *base, uint32_t nfixed) : base_(base), @@ -1374,6 +1394,46 @@ Shape::Shape(UnownedBaseShape *base, uint32_t nfixed) kids.setNull(); } +inline PropertyOp +Shape::getter() const +{ + return isAccessorShape() ? asAccessorShape().rawGetter : nullptr; +} + +inline StrictPropertyOp +Shape::setter() const +{ + return isAccessorShape() ? asAccessorShape().rawSetter : nullptr; +} + +inline JSObject * +Shape::getterObject() const +{ + MOZ_ASSERT(hasGetterValue()); + return asAccessorShape().getterObj; +} + +inline JSObject * +Shape::setterObject() const +{ + MOZ_ASSERT(hasSetterValue()); + return asAccessorShape().setterObj; +} + +inline void +Shape::initDictionaryShape(const StackShape &child, uint32_t nfixed, HeapPtrShape *dictp) +{ + if (child.isAccessorShape()) + new (this) AccessorShape(child, nfixed); + else + new (this) Shape(child, nfixed); + this->flags |= IN_DICTIONARY; + + this->listp = nullptr; + if (dictp) + insertIntoDictionary(dictp); +} + inline Shape * Shape::searchLinear(jsid id) { @@ -1394,6 +1454,21 @@ Shape::searchLinear(jsid id) return nullptr; } +inline void +Shape::markChildren(JSTracer *trc) +{ + MarkBaseShape(trc, &base_, "base"); + gc::MarkId(trc, &propidRef(), "propid"); + if (parent) + MarkShape(trc, &parent, "parent"); + + if (hasGetterObject()) + gc::MarkObjectUnbarriered(trc, &asAccessorShape().getterObj, "getter"); + + if (hasSetterObject()) + gc::MarkObjectUnbarriered(trc, &asAccessorShape().setterObj, "setter"); +} + /* * Keep this function in sync with search. It neither hashifies the start * shape nor increments linear search count. @@ -1417,7 +1492,8 @@ inline bool Shape::matches(const StackShape &other) const { return propid_.get() == other.propid && - matchesParamsAfterId(other.base, other.slot_, other.attrs, other.flags); + matchesParamsAfterId(other.base, other.slot_, other.attrs, other.flags, + other.rawGetter, other.rawSetter); } template<> struct RootKind : SpecificRootKind {};