diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index b2fffbd75ecc23e5a5ea563cdccdaccf92fe9b9c..4459b91d7c53b2df747f9cd3058a3a3cca4b8ddc 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -379,8 +379,6 @@ class JS_FRIEND_API GCCellPtr { return uintptr_t(p) | (uintptr_t(traceKind) & OutOfLineTraceKindMask); } - bool mayBeOwnedByOtherRuntimeSlow() const; - JS::TraceKind outOfLineKind() const; uintptr_t ptr; diff --git a/js/src/gc/Cell.h b/js/src/gc/Cell.h index f1a50e25fda39fe20999540419b2cb26fd29ff14..34a333d0540809189dfb0864317a68b72103c1f8 100644 --- a/js/src/gc/Cell.h +++ b/js/src/gc/Cell.h @@ -52,6 +52,54 @@ class TenuredCell; extern bool CurrentThreadIsGCMarking(); #endif +// Like gc::MarkColor but allows the possibility of the cell being unmarked. +// +// This class mimics an enum class, but supports operator overloading. +class CellColor { + public: + enum Color { White = 0, Gray = 1, Black = 2 }; + + CellColor() : color(White) {} + + MOZ_IMPLICIT CellColor(MarkColor markColor) + : color(markColor == MarkColor::Black ? Black : Gray) {} + + MOZ_IMPLICIT constexpr CellColor(Color c) : color(c) {} + + MarkColor asMarkColor() const { + MOZ_ASSERT(color != White); + return color == Black ? MarkColor::Black : MarkColor::Gray; + } + + // Implement a total ordering for CellColor, with white being 'least marked' + // and black being 'most marked'. + bool operator<(const CellColor other) const { return color < other.color; } + bool operator>(const CellColor other) const { return color > other.color; } + bool operator<=(const CellColor other) const { return color <= other.color; } + bool operator>=(const CellColor other) const { return color >= other.color; } + bool operator!=(const CellColor other) const { return color != other.color; } + bool operator==(const CellColor other) const { return color == other.color; } + explicit operator bool() const { return color != White; } + +#if defined(JS_GC_ZEAL) || defined(DEBUG) + const char* name() const { + switch (color) { + case CellColor::White: + return "white"; + case CellColor::Black: + return "black"; + case CellColor::Gray: + return "gray"; + default: + MOZ_CRASH("Unexpected cell color"); + } + } +#endif + + private: + Color color; +}; + // [SMDOC] GC Cell // // A GC cell is the base class for all GC things. All types allocated on the GC @@ -90,6 +138,12 @@ struct alignas(gc::CellAlignBytes) Cell { MOZ_ALWAYS_INLINE bool isMarked(gc::MarkColor color) const; MOZ_ALWAYS_INLINE bool isMarkedAtLeast(gc::MarkColor color) const; + MOZ_ALWAYS_INLINE CellColor color() const { + return isMarkedBlack() + ? CellColor::Black + : isMarkedGray() ? CellColor::Gray : CellColor::White; + } + inline JSRuntime* runtimeFromMainThread() const; // Note: Unrestricted access to the runtime of a GC thing from an arbitrary @@ -169,6 +223,13 @@ class TenuredCell : public Cell { MOZ_ALWAYS_INLINE bool isMarkedBlack() const; MOZ_ALWAYS_INLINE bool isMarkedGray() const; + // Same as Cell::color, but skips nursery checks. + MOZ_ALWAYS_INLINE CellColor color() const { + return isMarkedBlack() + ? CellColor::Black + : isMarkedGray() ? CellColor::Gray : CellColor::White; + } + // The return value indicates if the cell went from unmarked to marked. MOZ_ALWAYS_INLINE bool markIfUnmarked( MarkColor color = MarkColor::Black) const; diff --git a/js/src/gc/GC.cpp b/js/src/gc/GC.cpp index cfcf076f400635a8a075cde8a0ba2d725a2117a9..f2dd2fc68842fef82c45fb269dae7c8c00cc0c68 100644 --- a/js/src/gc/GC.cpp +++ b/js/src/gc/GC.cpp @@ -4300,7 +4300,7 @@ void js::gc::MarkingValidator::nonIncrementalMark(AutoGCSession& session) { * collecting. */ - WeakMapSet markedWeakMaps; + WeakMapColors markedWeakMaps; /* * For saving, smush all of the keys into one big table and split them back diff --git a/js/src/gc/GCMarker.h b/js/src/gc/GCMarker.h index af59b1253486c3a78e5428cce9e33adb86e962c8..5993e44348291f11ab147fc15331f005e4086498 100644 --- a/js/src/gc/GCMarker.h +++ b/js/src/gc/GCMarker.h @@ -284,21 +284,6 @@ class GCMarker : public JSTracer { // must be empty when this is called. void setMainStackColor(gc::MarkColor newColor); - // Return whether a cell is marked relative to the current marking color. If - // the cell is black then this returns true, but if it's gray it will return - // false if the mark color is black. - template <typename T> - bool isMarked(T* thingp) { - return color == gc::MarkColor::Black ? gc::IsMarkedBlack(runtime(), thingp) - : gc::IsMarked(runtime(), thingp); - } - template <typename T> - bool isMarkedUnbarriered(T* thingp) { - return color == gc::MarkColor::Black - ? gc::IsMarkedBlackUnbarriered(runtime(), thingp) - : gc::IsMarkedUnbarriered(runtime(), thingp); - } - void enterWeakMarkingMode(); void leaveWeakMarkingMode(); void abortLinearWeakMarking() { @@ -513,6 +498,9 @@ class MOZ_RAII AutoSetMarkColor { marker_.setMarkColor(newColor); } + AutoSetMarkColor(GCMarker& marker, CellColor newColor) + : AutoSetMarkColor(marker, newColor.asMarkColor()) {} + ~AutoSetMarkColor() { marker_.setMarkColor(initialColor_); } }; diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index dcaa6e8ae09161717e4c82ab7358a527ddc53cc1..370051f0ddf29bf9cc2c45a2c011340b7faa4ce7 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -12,6 +12,7 @@ #include "mozilla/ReentrancyGuard.h" #include "mozilla/ScopeExit.h" #include "mozilla/TypeTraits.h" +#include "mozilla/Unused.h" #include <algorithm> @@ -41,6 +42,7 @@ #include "gc/GC-inl.h" #include "gc/Nursery-inl.h" #include "gc/PrivateIterators-inl.h" +#include "gc/WeakMap-inl.h" #include "gc/Zone-inl.h" #include "vm/GeckoProfiler-inl.h" #include "vm/NativeObject-inl.h" @@ -654,12 +656,7 @@ void GCMarker::markEphemeronValues(gc::Cell* markedCell, DebugOnly<size_t> initialLen = values.length(); for (const auto& markable : values) { - if (color == gc::MarkColor::Black && - markable.weakmap->markColor == gc::MarkColor::Gray) { - continue; - } - - markable.weakmap->markEntry(this, markedCell, markable.key); + markable.weakmap->markKey(this, markedCell, markable.key); } // The vector should not be appended to during iteration because the key is @@ -2607,8 +2604,8 @@ void GCMarker::enterWeakMarkingMode() { for (SweepGroupZonesIter zone(runtime()); !zone.done(); zone.next()) { for (WeakMapBase* m : zone->gcWeakMapList()) { - if (m->marked) { - (void)m->markEntries(this); + if (m->mapColor) { + mozilla::Unused << m->markEntries(this); } } } diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index 7a18709c0d2eefa6418fc84f8088f2af6f0f4bc1..07cf27f47f03ad2362cad0964b7dc1243da62ff5 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -10,6 +10,8 @@ #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Sprintf.h" +#include <algorithm> + #ifdef MOZ_VALGRIND # include <valgrind/memcheck.h> #endif @@ -453,23 +455,6 @@ void js::gc::GCRuntime::finishVerifier() { #if defined(JS_GC_ZEAL) || defined(DEBUG) -static const char* CellColorName(CellColor color) { - switch (color) { - case CellColor::White: - return "white"; - case CellColor::Black: - return "black"; - case CellColor::Gray: - return "gray"; - default: - MOZ_CRASH("Unexpected cell color"); - } -} - -static const char* GetCellColorName(Cell* cell) { - return CellColorName(GetCellColor(cell)); -} - class HeapCheckTracerBase : public JS::CallbackTracer { public: explicit HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind); @@ -587,7 +572,7 @@ void HeapCheckTracerBase::dumpCellInfo(Cell* cell) { JSObject* obj = kind == JS::TraceKind::Object ? static_cast<JSObject*>(cell) : nullptr; - fprintf(stderr, "%s %s", GetCellColorName(cell), GCTraceKindToAscii(kind)); + fprintf(stderr, "%s %s", cell->color().name(), GCTraceKindToAscii(kind)); if (obj) { fprintf(stderr, " %s", obj->getClass()->name); } @@ -769,31 +754,26 @@ bool js::gc::CheckWeakMapEntryMarking(const WeakMapBase* map, Cell* key, Zone* valueZone = GetCellZoneFromAnyThread(value); MOZ_ASSERT(valueZone == zone || valueZone->isAtomsZone()); - CellColor mapColor = - map->markColor == MarkColor::Black ? CellColor::Black : CellColor::Gray; - if (object && GetCellColor(object) != mapColor) { + if (object && object->color() != map->mapColor) { fprintf(stderr, "WeakMap object is marked differently to the map\n"); fprintf(stderr, "(map %p is %s, object %p is %s)\n", map, - CellColorName(mapColor), object, - CellColorName(GetCellColor(object))); + map->mapColor.name(), object, object->color().name()); ok = false; } - CellColor keyColor = GetCellColor(key); - // Values belonging to other runtimes or in uncollected zones are treated as // black. CellColor valueColor = CellColor::Black; if (value->runtimeFromAnyThread() == zone->runtimeFromAnyThread() && valueZone->isGCMarking()) { - valueColor = GetCellColor(value); + valueColor = value->color(); } - if (valueColor < ExpectedWeakMapValueColor(mapColor, keyColor)) { + if (valueColor < std::min(map->mapColor, key->color())) { fprintf(stderr, "WeakMap value is less marked than map and key\n"); fprintf(stderr, "(map %p is %s, key %p is %s, value %p is %s)\n", map, - CellColorName(mapColor), key, CellColorName(keyColor), value, - CellColorName(valueColor)); + map->mapColor.name(), key, key->color().name(), value, + valueColor.name()); ok = false; } @@ -812,17 +792,17 @@ bool js::gc::CheckWeakMapEntryMarking(const WeakMapBase* map, Cell* key, CellColor delegateColor; if (delegate->zone()->isGCMarking() || delegate->zone()->isGCSweeping()) { - delegateColor = GetCellColor(delegate); + delegateColor = delegate->color(); } else { // IsMarked() assumes cells in uncollected zones are marked. delegateColor = CellColor::Black; } - if (keyColor < ExpectedWeakMapKeyColor(mapColor, delegateColor)) { - fprintf(stderr, "WeakMap key is less marked than map and delegate\n"); + if (key->color() < std::min(map->mapColor, delegateColor)) { + fprintf(stderr, "WeakMap key is less marked than map or delegate\n"); fprintf(stderr, "(map %p is %s, delegate %p is %s, key %p is %s)\n", map, - CellColorName(mapColor), delegate, CellColorName(delegateColor), - key, CellColorName(keyColor)); + map->mapColor.name(), delegate, delegateColor.name(), key, + key->color().name()); ok = false; } diff --git a/js/src/gc/Verifier.h b/js/src/gc/Verifier.h index 537244f06eb6eb8f444fc89d08e51494f73fde34..0e7df3dc15b86c7a4c43345f8caf44338b9bfb2b 100644 --- a/js/src/gc/Verifier.h +++ b/js/src/gc/Verifier.h @@ -18,47 +18,12 @@ namespace js { namespace gc { -// Like gc::MarkColor but allows the possibility of the cell being -// unmarked. -enum class CellColor : uint8_t { - White = 0, - Gray = uint8_t(MarkColor::Gray), - Black = uint8_t(MarkColor::Black) -}; - static constexpr CellColor AllCellColors[] = {CellColor::White, CellColor::Gray, CellColor::Black}; static constexpr CellColor MarkedCellColors[] = {CellColor::Gray, CellColor::Black}; -inline CellColor GetCellColor(Cell* cell) { - if (cell->isMarkedBlack()) { - return CellColor::Black; - } - - if (cell->isMarkedGray()) { - return CellColor::Gray; - } - - return CellColor::White; -} - -inline CellColor ExpectedWeakMapValueColor(CellColor keyColor, - CellColor mapColor) { - return std::min(keyColor, mapColor); -} - -inline CellColor ExpectedWeakMapKeyColor(CellColor mapColor, - CellColor delegateColor) { - return std::min(mapColor, delegateColor); -} - -inline CellColor ExpectedKeyAndDelegateColor(CellColor keyColor, - CellColor delegateColor) { - return std::max(keyColor, delegateColor); -} - } // namespace gc } // namespace js diff --git a/js/src/gc/WeakMap-inl.h b/js/src/gc/WeakMap-inl.h index e4396600abbdce628cb0086743554348dfa3bc21..15d2208f510531e9946ed30e0dd20d7a3e52dfd8 100644 --- a/js/src/gc/WeakMap-inl.h +++ b/js/src/gc/WeakMap-inl.h @@ -9,38 +9,79 @@ #include "gc/WeakMap.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Unused.h" + +#include <algorithm> + #include "gc/Zone.h" #include "js/TraceKind.h" #include "vm/JSContext.h" namespace js { +namespace gc { + +// Specializations for barriered types. +template <typename T> +inline Cell* ToMarkable(WriteBarriered<T>* thingp) { + return ToMarkable(thingp->get()); +} + +namespace detail { template <typename T> -static T extractUnbarriered(const WriteBarriered<T>& v) { +static T ExtractUnbarriered(const WriteBarriered<T>& v) { return v.get(); } template <typename T> -static T* extractUnbarriered(T* v) { +static T* ExtractUnbarriered(T* v) { return v; } -inline /* static */ JSObject* WeakMapBase::getDelegate(JSObject* key) { - if (!IsWrapper(key)) { - return nullptr; +// Return the effective cell color given the current marking state. +// This must be kept in sync with ShouldMark in Marking.cpp. +template <typename T> +static CellColor GetEffectiveColor(JSRuntime* rt, const T& item) { + Cell* cell = ToMarkable(item); + if (!cell->isTenured()) { + return CellColor::Black; } - - return UncheckedUnwrapWithoutExpose(key); + const TenuredCell& t = cell->asTenured(); + if (rt != t.runtimeFromAnyThread()) { + return CellColor::Black; + } + if (!t.zoneFromAnyThread()->shouldMarkInZone()) { + return CellColor::Black; + } + return cell->color(); } -inline /* static */ JSObject* WeakMapBase::getDelegate(JSScript* script) { +// Only objects have delegates, so default to returning nullptr. Note that some +// compilation units will only ever use the object version. +static MOZ_MAYBE_UNUSED JSObject* GetDelegateInternal(gc::Cell* key) { return nullptr; } -inline /* static */ JSObject* WeakMapBase::getDelegate(LazyScript* script) { - return nullptr; +static JSObject* GetDelegateInternal(JSObject* key) { + JSObject* delegate = UncheckedUnwrapWithoutExpose(key); + return (key == delegate) ? nullptr : delegate; } +// Use a helper function to do overload resolution to handle cases like +// Heap<ObjectSubclass*>: find everything that is convertible to JSObject* (and +// avoid calling barriers). +template <typename T> +static inline JSObject* GetDelegate(const T& key) { + return GetDelegateInternal(ExtractUnbarriered(key)); +} + +template <> +inline JSObject* GetDelegate(gc::Cell* const&) = delete; + +} /* namespace detail */ +} /* namespace gc */ + template <class K, class V> WeakMap<K, V>::WeakMap(JSContext* cx, JSObject* memOf) : Base(cx->zone()), WeakMapBase(memOf, cx->zone()) { @@ -56,37 +97,64 @@ WeakMap<K, V>::WeakMap(JSContext* cx, JSObject* memOf) zone()->gcWeakMapList().insertFront(this); if (zone()->wasGCStarted()) { - marked = true; - markColor = gc::MarkColor::Black; + mapColor = CellColor::Black; } } // Trace a WeakMap entry based on 'markedCell' getting marked, where 'origKey' -// is the key in the weakmap. These will probably be the same, but can be -// different eg when markedCell is a delegate for origKey. -// -// This implementation does not use 'markedCell'; it looks up origKey and checks -// the mark bits on everything it cares about, one of which will be -// markedCell. But a subclass might use it to optimize the liveness check. +// is the key in the weakmap. In the absence of delegates, these will be the +// same, but when a delegate is marked then origKey will be its wrapper. +// `markedCell` is only used for an assertion. template <class K, class V> -void WeakMap<K, V>::markEntry(GCMarker* marker, gc::Cell* markedCell, - gc::Cell* origKey) { - MOZ_ASSERT(marked); +void WeakMap<K, V>::markKey(GCMarker* marker, gc::Cell* markedCell, + gc::Cell* origKey) { + MOZ_ASSERT(mapColor); Ptr p = Base::lookup(static_cast<Lookup>(origKey)); MOZ_ASSERT(p.found()); - K key(p->key()); - MOZ_ASSERT((markedCell == extractUnbarriered(key)) || - (markedCell == getDelegate(key))); - if (marker->isMarked(&key)) { - TraceEdge(marker, &p->value(), "ephemeron value"); - } else if (keyNeedsMark(marker, key)) { - TraceEdge(marker, &p->value(), "WeakMap ephemeron value"); - TraceEdge(marker, &key, "proxy-preserved WeakMap ephemeron key"); - MOZ_ASSERT(key == p->key()); // No moving + mozilla::DebugOnly<gc::Cell*> oldKey = gc::ToMarkable(p->key()); + MOZ_ASSERT((markedCell == oldKey) || + (markedCell == gc::detail::GetDelegate(p->key()))); + + markEntry(marker, p->mutableKey(), p->value()); + MOZ_ASSERT(oldKey == gc::ToMarkable(p->key()), "no moving GC"); +} + +// If the entry is live, ensure its key and value are marked. Also make sure +// the key is at least as marked as the delegate, so it cannot get discarded +// and then recreated by rewrapping the delegate. +template <class K, class V> +bool WeakMap<K, V>::markEntry(GCMarker* marker, K& key, V& value) { + bool marked = false; + JSRuntime* rt = zone()->runtimeFromAnyThread(); + CellColor keyColor = gc::detail::GetEffectiveColor(rt, key); + JSObject* delegate = gc::detail::GetDelegate(key); + if (delegate) { + CellColor delegateColor = gc::detail::GetEffectiveColor(rt, delegate); + if (keyColor < delegateColor) { + gc::AutoSetMarkColor autoColor(*marker, delegateColor); + TraceEdge(marker, &key, "proxy-preserved WeakMap entry key"); + MOZ_ASSERT(key->color() >= delegateColor); + marked = true; + keyColor = delegateColor; + } + } + + if (keyColor) { + gc::Cell* cellValue = gc::ToMarkable(&value); + if (cellValue) { + gc::AutoSetMarkColor autoColor(*marker, std::min(mapColor, keyColor)); + CellColor valueColor = gc::detail::GetEffectiveColor(rt, cellValue); + if (valueColor < marker->markColor()) { + TraceEdge(marker, &value, "WeakMap entry value"); + MOZ_ASSERT(cellValue->color() >= std::min(mapColor, keyColor)); + marked = true; + } + } } - key.unsafeSet(nullptr); // Prevent destructor from running barriers. + + return marked; } template <class K, class V> @@ -99,17 +167,13 @@ void WeakMap<K, V>::trace(JSTracer* trc) { MOZ_ASSERT(trc->weakMapAction() == ExpandWeakMaps); auto marker = GCMarker::fromTracer(trc); - // Don't change the map color from black to gray. This can happen when a - // barrier pushes the map object onto the black mark stack when it's already - // present on the gray mark stack, which is marked later. - if (marked && markColor == gc::MarkColor::Black && - marker->markColor() == gc::MarkColor::Gray) { - return; + // Don't downgrade the map color from black to gray. This can happen when a + // barrier pushes the map object onto the black mark stack when it's + // already present on the gray mark stack, which is marked later. + if (mapColor < marker->markColor()) { + mapColor = marker->markColor(); + mozilla::Unused << markEntries(marker); } - - marked = true; - markColor = marker->markColor(); - (void)markEntries(marker); return; } @@ -154,38 +218,32 @@ template <class K, class V> template <class K, class V> bool WeakMap<K, V>::markEntries(GCMarker* marker) { - MOZ_ASSERT(marked); - if (marker->markColor() == gc::MarkColor::Black && - markColor == gc::MarkColor::Gray) { - return false; - } - + MOZ_ASSERT(mapColor); bool markedAny = false; for (Enum e(*this); !e.empty(); e.popFront()) { - // If the entry is live, ensure its key and value are marked. - bool keyIsMarked = marker->isMarked(&e.front().mutableKey()); - if (!keyIsMarked && keyNeedsMark(marker, e.front().key())) { - TraceEdge(marker, &e.front().mutableKey(), - "proxy-preserved WeakMap entry key"); - keyIsMarked = true; + if (markEntry(marker, e.front().mutableKey(), e.front().value())) { markedAny = true; } - if (keyIsMarked) { - if (!marker->isMarked(&e.front().value())) { - TraceEdge(marker, &e.front().value(), "WeakMap entry value"); - markedAny = true; - } - } else if (marker->isWeakMarkingTracer()) { - // Entry is not yet known to be live. Record this weakmap and - // the lookup key in the list of weak keys. Also record the - // delegate, if any, because marking the delegate also marks - // the entry. - gc::Cell* weakKey = extractUnbarriered(e.front().key()); + JSRuntime* rt = zone()->runtimeFromAnyThread(); + CellColor keyColor = + gc::detail::GetEffectiveColor(rt, e.front().key().get()); + + // Changes in the map's mark color will be handled in this code, but + // changes in the key's mark color are handled through the weak keys table. + // So we only need to populate the table if the key is less marked than the + // map, to catch later updates in the key's mark color. + if (keyColor < mapColor) { + MOZ_ASSERT(marker->weakMapAction() == ExpandWeakMaps); + // The final color of the key is not yet known. Record this weakmap and + // the lookup key in the list of weak keys. If the key has a delegate, + // then the lookup key is the delegate (because marking the key will end + // up marking the delegate and thereby mark the entry.) + gc::Cell* weakKey = gc::detail::ExtractUnbarriered(e.front().key()); gc::WeakMarkable markable(this, weakKey); addWeakEntry(marker, weakKey, markable); - if (JSObject* delegate = getDelegate(e.front().key())) { + if (JSObject* delegate = gc::detail::GetDelegate(e.front().key())) { addWeakEntry(marker, delegate, markable); } } @@ -194,28 +252,6 @@ bool WeakMap<K, V>::markEntries(GCMarker* marker) { return markedAny; } -template <class K, class V> -inline bool WeakMap<K, V>::keyNeedsMark(GCMarker* marker, JSObject* key) const { - JSObject* delegate = getDelegate(key); - /* - * Check if the delegate is marked with any color to properly handle - * gray marking when the key's delegate is black and the map is gray. - */ - return delegate && marker->isMarkedUnbarriered(&delegate); -} - -template <class K, class V> -inline bool WeakMap<K, V>::keyNeedsMark(GCMarker* marker, - JSScript* script) const { - return false; -} - -template <class K, class V> -inline bool WeakMap<K, V>::keyNeedsMark(GCMarker* marker, - LazyScript* script) const { - return false; -} - template <class K, class V> void WeakMap<K, V>::sweep() { /* Remove all entries whose keys remain unmarked. */ @@ -251,7 +287,7 @@ void WeakMap<K, V>::assertEntriesNotAboutToBeFinalized() { for (Range r = Base::all(); !r.empty(); r.popFront()) { K k(r.front().key()); MOZ_ASSERT(!gc::IsAboutToBeFinalized(&k)); - JSObject* delegate = getDelegate(k); + JSObject* delegate = gc::detail::GetDelegate(k); if (delegate) { MOZ_ASSERT(!gc::IsAboutToBeFinalizedUnbarriered(&delegate), "weakmap marking depends on a key tracing its delegate"); diff --git a/js/src/gc/WeakMap.cpp b/js/src/gc/WeakMap.cpp index 4659aa68363e1957251fbc14f28ceb242a3ef612..c9637ae9e56aff5e06c67a45052948a7b573ab24 100644 --- a/js/src/gc/WeakMap.cpp +++ b/js/src/gc/WeakMap.cpp @@ -23,7 +23,7 @@ using namespace js; using namespace js::gc; WeakMapBase::WeakMapBase(JSObject* memOf, Zone* zone) - : memberOf(memOf), zone_(zone), marked(false), markColor(MarkColor::Black) { + : memberOf(memOf), zone_(zone), mapColor(CellColor::White) { MOZ_ASSERT_IF(memberOf, memberOf->compartment()->zone() == zone); } @@ -33,7 +33,7 @@ WeakMapBase::~WeakMapBase() { void WeakMapBase::unmarkZone(JS::Zone* zone) { for (WeakMapBase* m : zone->gcWeakMapList()) { - m->marked = false; + m->mapColor = CellColor::White; } } @@ -52,7 +52,7 @@ bool WeakMapBase::checkMarkingForZone(JS::Zone* zone) { bool ok = true; for (WeakMapBase* m : zone->gcWeakMapList()) { - if (m->marked && !m->checkMarking()) { + if (m->mapColor && !m->checkMarking()) { ok = false; } } @@ -64,7 +64,7 @@ bool WeakMapBase::checkMarkingForZone(JS::Zone* zone) { bool WeakMapBase::markZoneIteratively(JS::Zone* zone, GCMarker* marker) { bool markedAny = false; for (WeakMapBase* m : zone->gcWeakMapList()) { - if (m->marked && m->markEntries(marker)) { + if (m->mapColor && m->markEntries(marker)) { markedAny = true; } } @@ -83,7 +83,7 @@ bool WeakMapBase::findSweepGroupEdgesForZone(JS::Zone* zone) { void WeakMapBase::sweepZone(JS::Zone* zone) { for (WeakMapBase* m = zone->gcWeakMapList().getFirst(); m;) { WeakMapBase* next = m->getNext(); - if (m->marked) { + if (m->mapColor) { m->sweep(); } else { m->clearAndCompact(); @@ -94,7 +94,7 @@ void WeakMapBase::sweepZone(JS::Zone* zone) { #ifdef DEBUG for (WeakMapBase* m : zone->gcWeakMapList()) { - MOZ_ASSERT(m->isInList() && m->marked); + MOZ_ASSERT(m->isInList() && m->mapColor); } #endif } @@ -111,21 +111,22 @@ void WeakMapBase::traceAllMappings(WeakMapTracer* tracer) { } bool WeakMapBase::saveZoneMarkedWeakMaps(JS::Zone* zone, - WeakMapSet& markedWeakMaps) { + WeakMapColors& markedWeakMaps) { for (WeakMapBase* m : zone->gcWeakMapList()) { - if (m->marked && !markedWeakMaps.put(m)) { + if (m->mapColor && !markedWeakMaps.put(m, m->mapColor)) { return false; } } return true; } -void WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps) { - for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) { - WeakMapBase* map = r.front(); +void WeakMapBase::restoreMarkedWeakMaps(WeakMapColors& markedWeakMaps) { + for (WeakMapColors::Range r = markedWeakMaps.all(); !r.empty(); + r.popFront()) { + WeakMapBase* map = r.front().key(); MOZ_ASSERT(map->zone()->isGCMarking()); - MOZ_ASSERT(!map->marked); - map->marked = true; + MOZ_ASSERT(map->mapColor == CellColor::White); + map->mapColor = r.front().value(); } } @@ -146,7 +147,7 @@ bool ObjectValueWeakMap::findSweepGroupEdges() { if (key->asTenured().isMarkedBlack()) { continue; } - JSObject* delegate = getDelegate(key); + JSObject* delegate = gc::detail::GetDelegate(key); if (!delegate) { continue; } diff --git a/js/src/gc/WeakMap.h b/js/src/gc/WeakMap.h index 3e4b911d09abbb1ab923349c2ddca55d006ae131..02517e0d00ab5ae6c53b423d3f77b7405fdf654a 100644 --- a/js/src/gc/WeakMap.h +++ b/js/src/gc/WeakMap.h @@ -47,8 +47,8 @@ bool CheckWeakMapEntryMarking(const WeakMapBase* map, Cell* key, Cell* value); // the implicit edges stored in the map) and of removing (sweeping) table // entries when collection is complete. -typedef HashSet<WeakMapBase*, DefaultHasher<WeakMapBase*>, SystemAllocPolicy> - WeakMapSet; +using WeakMapColors = HashMap<WeakMapBase*, js::gc::CellColor, + DefaultHasher<WeakMapBase*>, SystemAllocPolicy>; // Common base class for all WeakMap specializations, used for calling // subclasses' GC-related methods. @@ -56,6 +56,8 @@ class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase> { friend class js::GCMarker; public: + using CellColor = js::gc::CellColor; + WeakMapBase(JSObject* memOf, JS::Zone* zone); virtual ~WeakMapBase(); @@ -88,19 +90,15 @@ class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase> { // Save information about which weak maps are marked for a zone. static bool saveZoneMarkedWeakMaps(JS::Zone* zone, - WeakMapSet& markedWeakMaps); + WeakMapColors& markedWeakMaps); // Restore information about which weak maps are marked for many zones. - static void restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps); + static void restoreMarkedWeakMaps(WeakMapColors& markedWeakMaps); #if defined(JS_GC_ZEAL) || defined(DEBUG) static bool checkMarkingForZone(JS::Zone* zone); #endif - static JSObject* getDelegate(JSObject* key); - static JSObject* getDelegate(JSScript* script); - static JSObject* getDelegate(LazyScript* script); - protected: // Instance member functions called by the above. Instantiations of WeakMap // override these with definitions appropriate for their Key and Value types. @@ -112,8 +110,7 @@ class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase> { // Any weakmap key types that want to participate in the non-iterative // ephemeron marking must override this method. - virtual void markEntry(GCMarker* marker, gc::Cell* markedCell, - gc::Cell* l) = 0; + virtual void markKey(GCMarker* marker, gc::Cell* markedCell, gc::Cell* l) = 0; virtual bool markEntries(GCMarker* marker) = 0; @@ -132,10 +129,21 @@ class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase> { // Whether this object has been marked during garbage collection and which // color it was marked. - bool marked; - gc::MarkColor markColor; + gc::CellColor mapColor; +}; + +namespace detail { + +template <typename T> +struct RemoveBarrier {}; + +template <typename T> +struct RemoveBarrier<js::HeapPtr<T>> { + using Type = T; }; +} // namespace detail + template <class Key, class Value> class WeakMap : private HashMap<Key, Value, MovableCellHasher<Key>, ZoneAllocPolicy>, @@ -161,6 +169,8 @@ class WeakMap // Resolve ambiguity with LinkedListElement<>::remove. using Base::remove; + using UnbarrieredKey = typename detail::RemoveBarrier<Key>::Type; + explicit WeakMap(JSContext* cx, JSObject* memOf = nullptr); // Add a read barrier to prevent an incorrectly gray value from escaping the @@ -211,8 +221,10 @@ class WeakMap } #endif - void markEntry(GCMarker* marker, gc::Cell* markedCell, - gc::Cell* origKey) override; + void markKey(GCMarker* marker, gc::Cell* markedCell, + gc::Cell* origKey) override; + + bool markEntry(GCMarker* marker, Key& key, Value& value); void trace(JSTracer* trc) override; @@ -242,10 +254,6 @@ class WeakMap JS::ExposeObjectToActiveJS(obj); } - bool keyNeedsMark(GCMarker* marker, JSObject* key) const; - bool keyNeedsMark(GCMarker* marker, JSScript* script) const; - bool keyNeedsMark(GCMarker* marker, LazyScript* script) const; - bool findSweepGroupEdges() override { // This is overridden by ObjectValueWeakMap and DebuggerWeakMap. return true; diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index d40ef25df1c248073a0cc3774d4c783b4c241ec4..7fc299680e101aa822319bf01724b858de72b3f3 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -228,7 +228,7 @@ void Zone::sweepWeakKeysAfterMinorGC() { // If the key has a delegate, then it will map to a WeakKeyEntryVector // containing the key that needs to be updated. - JSObject* delegate = WeakMapBase::getDelegate(key->as<JSObject>()); + JSObject* delegate = gc::detail::GetDelegate(key->as<JSObject>()); if (!delegate) { continue; } diff --git a/js/src/jit-test/tests/gc/weak-marking-03.js b/js/src/jit-test/tests/gc/weak-marking-03.js index 7ae58181fe843cfb13b090042a6f8ebb973ed8f4..3ef6b32a6739a3b476980d324a65e672751f01cc 100644 --- a/js/src/jit-test/tests/gc/weak-marking-03.js +++ b/js/src/jit-test/tests/gc/weak-marking-03.js @@ -412,6 +412,7 @@ function grayMapKey() { "key is now marked black"); finishgc(); // Finish the GC + assertEq(getMarks().join("/"), "gray/black/gray", "at end: black/gray => gray"); @@ -483,3 +484,83 @@ function grayKeyMap() { if (this.enqueueMark) runtest(grayKeyMap); + +// Cause a key to be marked black *during gray marking*, by first marking a +// delegate black, then marking the map and key gray. When the key is scanned, +// it should be seen to be a CCW of a black delegate and so should itself be +// marked black. +// +// Note that this is currently buggy -- the key will be marked because the +// delegate is marked, but the color won't be taken into account. So the key +// will be marked gray (or rather, it will see that it's already gray.) The bad +// behavior this would cause is if: +// +// 1. You wrap an object in a CCW and use it as a weakmap key to some +// information. +// 2. You keep a strong reference to the object (in its compartment). +// 3. The only references to the CCW are gray, and are in fact part of a cycle. +// 4. The CC runs and discards the CCW. +// 5. You look up the object in the weakmap again. This creates a new wrapper +// to use as a key. It is not in the weakmap, so the information you stored +// before is not found. (It may have even been collected, if you had no +// other references to it.) +// +function blackDuringGray() { + const g = newGlobal({newCompartment: true}); + const vals = {}; + vals.map = new WeakMap(); + vals.key = g.eval("Object.create(null)"); + vals.val = Object.create(null); + vals.map.set(vals.key, vals.val); + + g.delegate = vals.key; + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key]); + addMarkObservers([vals.val]); + // Mark observers: map, key, delegate, value + + gc(); + + g.enqueueMark(vals.key); // Mark the delegate black + enqueueMark("yield"); // checkpoint 1 + + // Mark the map gray. This will scan through all entries, find our key, and + // mark it black because its delegate is black. + enqueueMark("set-color-gray"); + enqueueMark(vals.map); // Mark the map gray + + vals.map = null; + vals.val = null; + vals.key = null; + g.delegate = null; + + const showmarks = () => { + print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); + }; + + print("Starting incremental GC"); + startgc(100000); + // Checkpoint 1, after marking delegate black + showmarks(); + var marks = getMarks(); + assertEq(marks[0], "unmarked", "map is not marked yet"); + assertEq(marks[1], "unmarked", "key is not marked yet"); + assertEq(marks[2], "black", "delegate is black"); + assertEq(marks[3], "unmarked", "values is not marked yet"); + + finishgc(); + showmarks(); + marks = getMarks(); + assertEq(marks[0], "gray", "map is gray"); + assertEq(marks[1], "black", "key inherits black even though in gray marking"); + assertEq(marks[2], "black", "delegate is still black"); + assertEq(marks[3], "gray", "gray map + black key => gray value"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(blackDuringGray); diff --git a/js/src/jsapi-tests/testGCGrayMarking.cpp b/js/src/jsapi-tests/testGCGrayMarking.cpp index 59c768c5a3d597a7936fa173f9f5e02bb387a9ba..090d151754a9633b2c96e3b48147d3b7b23e85e1 100644 --- a/js/src/jsapi-tests/testGCGrayMarking.cpp +++ b/js/src/jsapi-tests/testGCGrayMarking.cpp @@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <algorithm> + #include "gc/Heap.h" #include "gc/Verifier.h" #include "gc/WeakMap.h" @@ -155,7 +157,7 @@ bool TestMarking() { return true; } -static const CellColor DontMark = CellColor::White; +static constexpr CellColor DontMark = CellColor::White; enum MarkKeyOrDelegate : bool { MarkKey = true, MarkDelegate = false }; @@ -163,8 +165,7 @@ bool TestJSWeakMaps() { for (auto keyOrDelegateColor : MarkedCellColors) { for (auto mapColor : MarkedCellColors) { for (auto markKeyOrDelegate : {MarkKey, MarkDelegate}) { - CellColor expected = - ExpectedWeakMapValueColor(keyOrDelegateColor, mapColor); + CellColor expected = std::min(keyOrDelegateColor, mapColor); CHECK(TestJSWeakMap(markKeyOrDelegate, keyOrDelegateColor, mapColor, expected)); #ifdef JS_GC_ZEAL @@ -186,10 +187,10 @@ bool TestInternalWeakMaps() { continue; } - CellColor keyOrDelegateColor = - ExpectedKeyAndDelegateColor(keyMarkColor, delegateMarkColor); - CellColor expected = - ExpectedWeakMapValueColor(keyOrDelegateColor, CellColor::Black); + // The map is black. The delegate marks its key via wrapper preservation. + // The key maps its delegate and the value. Thus, all three end up the + // maximum of the key and delegate colors. + CellColor expected = std::max(keyMarkColor, delegateMarkColor); CHECK(TestInternalWeakMap(keyMarkColor, delegateMarkColor, expected)); #ifdef JS_GC_ZEAL @@ -240,9 +241,9 @@ bool TestJSWeakMap(MarkKeyOrDelegate markKey, CellColor weakMapMarkColor, ClearGrayRoots(); - CHECK(GetCellColor(weakMap) == weakMapMarkColor); - CHECK(GetCellColor(keyOrDelegate) == keyOrDelegateMarkColor); - CHECK(GetCellColor(value) == expectedValueColor); + CHECK(weakMap->color() == weakMapMarkColor); + CHECK(keyOrDelegate->color() == keyOrDelegateMarkColor); + CHECK(value->color() == expectedValueColor); } return true; @@ -296,9 +297,9 @@ bool TestJSWeakMapWithGrayUnmarking(MarkKeyOrDelegate markKey, ClearGrayRoots(); - CHECK(GetCellColor(weakMap) == weakMapMarkColor); - CHECK(GetCellColor(keyOrDelegate) == keyOrDelegateMarkColor); - CHECK(GetCellColor(value) == expectedValueColor); + CHECK(weakMap->color() == weakMapMarkColor); + CHECK(keyOrDelegate->color() == keyOrDelegateMarkColor); + CHECK(value->color() == expectedValueColor); } JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking)); @@ -369,9 +370,9 @@ bool TestInternalWeakMap(CellColor keyMarkColor, CellColor delegateMarkColor, ClearGrayRoots(); - CHECK(GetCellColor(key) == expectedColor); - CHECK(GetCellColor(delegate) == expectedColor); - CHECK(GetCellColor(value) == expectedColor); + CHECK(key->color() == expectedColor); + CHECK(delegate->color() == expectedColor); + CHECK(value->color() == expectedColor); } return true; @@ -421,9 +422,9 @@ bool TestInternalWeakMapWithGrayUnmarking(CellColor keyMarkColor, ClearGrayRoots(); - CHECK(GetCellColor(key) == expectedColor); - CHECK(GetCellColor(delegate) == expectedColor); - CHECK(GetCellColor(value) == expectedColor); + CHECK(key->color() == expectedColor); + CHECK(delegate->color() == expectedColor); + CHECK(value->color() == expectedColor); } JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));