diff options
Diffstat (limited to 'js/src/gc/WeakMap-inl.h')
-rw-r--r-- | js/src/gc/WeakMap-inl.h | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/js/src/gc/WeakMap-inl.h b/js/src/gc/WeakMap-inl.h new file mode 100644 index 0000000000..3e3754f878 --- /dev/null +++ b/js/src/gc/WeakMap-inl.h @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +#ifndef gc_WeakMap_inl_h +#define gc_WeakMap_inl_h + +#include "gc/WeakMap.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/Unused.h" + +#include <algorithm> +#include <type_traits> + +#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) { + return v.get(); +} + +template <typename T> +static T* ExtractUnbarriered(T* v) { + return v; +} + +// 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; + } + const TenuredCell& t = cell->asTenured(); + if (rt != t.runtimeFromAnyThread()) { + return CellColor::Black; + } + if (!t.zoneFromAnyThread()->shouldMarkInZone()) { + return CellColor::Black; + } + return cell->color(); +} + +// 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; +} + +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 */ + +// Weakmap entry -> value edges are only visible if the map is traced, which +// only happens if the map zone is being collected. If the map and the value +// were in different zones, then we could have a case where the map zone is not +// collecting but the value zone is, and incorrectly free a value that is +// reachable solely through weakmaps. +template <class K, class V> +void WeakMap<K, V>::assertMapIsSameZoneWithValue(const V& v) { +#ifdef DEBUG + gc::Cell* cell = gc::ToMarkable(v); + if (cell) { + Zone* cellZone = cell->zoneFromAnyThread(); + MOZ_ASSERT(zone() == cellZone || cellZone->isAtomsZone()); + } +#endif +} + +template <class K, class V> +WeakMap<K, V>::WeakMap(JSContext* cx, JSObject* memOf) + : Base(cx->zone()), WeakMapBase(memOf, cx->zone()) { + using ElemType = typename K::ElementType; + using NonPtrType = std::remove_pointer_t<ElemType>; + + // The object's TraceKind needs to be added to CC graph if this object is + // used as a WeakMap key, otherwise the key is considered to be pointed from + // somewhere unknown, and results in leaking the subgraph which contains the + // key. See the comments in NoteWeakMapsTracer::trace for more details. + static_assert(JS::IsCCTraceKind(NonPtrType::TraceKind), + "Object's TraceKind should be added to CC graph."); + + zone()->gcWeakMapList().insertFront(this); + if (zone()->gcState() > Zone::Prepare) { + mapColor = CellColor::Black; + } +} + +// Trace a WeakMap entry based on 'markedCell' getting marked, where 'origKey' +// 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>::markKey(GCMarker* marker, gc::Cell* markedCell, + gc::Cell* origKey) { +#if DEBUG + if (!mapColor) { + fprintf(stderr, "markKey called on an unmarked map %p", this); + Zone* zone = markedCell->asTenured().zoneFromAnyThread(); + fprintf(stderr, " markedCell=%p from zone %p state %d mark %d\n", + markedCell, zone, zone->gcState(), + int(debug::GetMarkInfo(markedCell))); + zone = origKey->asTenured().zoneFromAnyThread(); + fprintf(stderr, " origKey=%p from zone %p state %d mark %d\n", origKey, + zone, zone->gcState(), int(debug::GetMarkInfo(markedCell))); + if (memberOf) { + zone = memberOf->asTenured().zoneFromAnyThread(); + fprintf(stderr, " memberOf=%p from zone %p state %d mark %d\n", + memberOf.get(), zone, zone->gcState(), + int(debug::GetMarkInfo(memberOf.get()))); + } + } +#endif + MOZ_ASSERT(mapColor); + + Ptr p = Base::lookup(static_cast<Lookup>(origKey)); + // We should only be processing <weakmap,key> pairs where the key exists in + // the weakmap. Such pairs are inserted when a weakmap is marked, and are + // removed by barriers if the key is removed from the weakmap. Failure here + // probably means gcWeakKeys is not being properly traced during a minor GC, + // or the weakmap keys are not being updated when tenured. + MOZ_ASSERT(p.found()); + + 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); + // The key needs to stay alive while both the delegate and map are live. + CellColor proxyPreserveColor = std::min(delegateColor, mapColor); + if (keyColor < proxyPreserveColor) { + gc::AutoSetMarkColor autoColor(*marker, proxyPreserveColor); + TraceWeakMapKeyEdge(marker, zone(), &key, + "proxy-preserved WeakMap entry key"); + MOZ_ASSERT(key->color() >= proxyPreserveColor); + marked = true; + keyColor = proxyPreserveColor; + } + } + + 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; + } + } + } + + return marked; +} + +template <class K, class V> +void WeakMap<K, V>::trace(JSTracer* trc) { + MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy(), isInList()); + + TraceNullableEdge(trc, &memberOf, "WeakMap owner"); + + if (trc->isMarkingTracer()) { + MOZ_ASSERT(trc->weakMapAction() == JS::WeakMapTraceAction::Expand); + auto marker = GCMarker::fromTracer(trc); + + // 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); + } + return; + } + + if (trc->weakMapAction() == JS::WeakMapTraceAction::Skip) { + return; + } + + // Trace keys only if weakMapAction() says to. + if (trc->weakMapAction() == JS::WeakMapTraceAction::TraceKeysAndValues) { + for (Enum e(*this); !e.empty(); e.popFront()) { + TraceWeakMapKeyEdge(trc, zone(), &e.front().mutableKey(), + "WeakMap entry key"); + } + } + + // Always trace all values (unless weakMapAction() is Skip). + for (Range r = Base::all(); !r.empty(); r.popFront()) { + TraceEdge(trc, &r.front().value(), "WeakMap entry value"); + } +} + +template <class K, class V> +/* static */ void WeakMap<K, V>::forgetKey(UnbarrieredKey key) { + // Remove the key or its delegate from weakKeys. + if (zone()->needsIncrementalBarrier()) { + JSRuntime* rt = zone()->runtimeFromMainThread(); + if (JSObject* delegate = js::gc::detail::GetDelegate(key)) { + js::gc::WeakKeyTable& weakKeys = delegate->zone()->gcWeakKeys(delegate); + rt->gc.marker.forgetWeakKey(weakKeys, this, delegate, key); + } else { + js::gc::WeakKeyTable& weakKeys = key->zone()->gcWeakKeys(key); + rt->gc.marker.forgetWeakKey(weakKeys, this, key, key); + } + } +} + +template <class K, class V> +/* static */ void WeakMap<K, V>::clear() { + Base::clear(); + JSRuntime* rt = zone()->runtimeFromMainThread(); + if (zone()->needsIncrementalBarrier()) { + rt->gc.marker.forgetWeakMap(this, zone()); + } +} + +/* static */ inline void WeakMapBase::addWeakEntry( + GCMarker* marker, gc::Cell* key, const gc::WeakMarkable& markable) { + auto& weakKeys = key->zone()->gcWeakKeys(key); + auto p = weakKeys.get(key); + if (p) { + gc::WeakEntryVector& weakEntries = p->value; + if (!weakEntries.append(markable)) { + marker->abortLinearWeakMarking(); + } + } else { + gc::WeakEntryVector weakEntries; + MOZ_ALWAYS_TRUE(weakEntries.append(markable)); + if (!weakKeys.put(key, std::move(weakEntries))) { + marker->abortLinearWeakMarking(); + } + } +} + +template <class K, class V> +bool WeakMap<K, V>::markEntries(GCMarker* marker) { + MOZ_ASSERT(mapColor); + bool markedAny = false; + + for (Enum e(*this); !e.empty(); e.popFront()) { + if (markEntry(marker, e.front().mutableKey(), e.front().value())) { + markedAny = true; + } + if (!marker->incrementalWeakMapMarkingEnabled && !marker->isWeakMarking()) { + // Populate weak keys table when we enter weak marking mode. + continue; + } + + 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() == JS::WeakMapTraceAction::Expand); + // 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); + if (JSObject* delegate = gc::detail::GetDelegate(e.front().key())) { + addWeakEntry(marker, delegate, markable); + } else { + addWeakEntry(marker, weakKey, markable); + } + } + } + + return markedAny; +} + +template <class K, class V> +void WeakMap<K, V>::postSeverDelegate(GCMarker* marker, JSObject* key) { + if (mapColor) { + // We only stored the delegate, not the key, and we're severing the + // delegate from the key. So store the key. + gc::WeakMarkable markable(this, key); + addWeakEntry(marker, key, markable); + } +} + +template <class K, class V> +void WeakMap<K, V>::postRestoreDelegate(GCMarker* marker, JSObject* key, + JSObject* delegate) { + if (mapColor) { + // We had the key stored, but are removing it. Store the delegate instead. + gc::WeakMarkable markable(this, key); + addWeakEntry(marker, delegate, markable); + } +} + +template <class K, class V> +void WeakMap<K, V>::sweep() { + /* Remove all entries whose keys remain unmarked. */ + for (Enum e(*this); !e.empty(); e.popFront()) { + if (gc::IsAboutToBeFinalized(&e.front().mutableKey())) { + e.removeFront(); + } + } + +#if DEBUG + // Once we've swept, all remaining edges should stay within the known-live + // part of the graph. + assertEntriesNotAboutToBeFinalized(); +#endif +} + +// memberOf can be nullptr, which means that the map is not part of a JSObject. +template <class K, class V> +void WeakMap<K, V>::traceMappings(WeakMapTracer* tracer) { + for (Range r = Base::all(); !r.empty(); r.popFront()) { + gc::Cell* key = gc::ToMarkable(r.front().key()); + gc::Cell* value = gc::ToMarkable(r.front().value()); + if (key && value) { + tracer->trace(memberOf, JS::GCCellPtr(r.front().key().get()), + JS::GCCellPtr(r.front().value().get())); + } + } +} + +template <class K, class V> +bool WeakMap<K, V>::findSweepGroupEdges() { + // For weakmap keys with delegates in a different zone, add a zone edge to + // ensure that the delegate zone finishes marking before the key zone. + JS::AutoSuppressGCAnalysis nogc; + for (Range r = all(); !r.empty(); r.popFront()) { + const K& key = r.front().key(); + + // If the key type doesn't have delegates, then this will always return + // nullptr and the optimizer can remove the entire body of this function. + JSObject* delegate = gc::detail::GetDelegate(key); + if (!delegate) { + continue; + } + + // Marking a WeakMap key's delegate will mark the key, so process the + // delegate zone no later than the key zone. + Zone* delegateZone = delegate->zone(); + Zone* keyZone = key->zone(); + if (delegateZone != keyZone && delegateZone->isGCMarking() && + keyZone->isGCMarking()) { + if (!delegateZone->addSweepGroupEdgeTo(keyZone)) { + return false; + } + } + } + return true; +} + +#if DEBUG +template <class K, class V> +void WeakMap<K, V>::assertEntriesNotAboutToBeFinalized() { + for (Range r = Base::all(); !r.empty(); r.popFront()) { + auto k = gc::detail::ExtractUnbarriered(r.front().key()); + MOZ_ASSERT(!gc::IsAboutToBeFinalizedUnbarriered(&k)); + JSObject* delegate = gc::detail::GetDelegate(k); + if (delegate) { + MOZ_ASSERT(!gc::IsAboutToBeFinalizedUnbarriered(&delegate), + "weakmap marking depends on a key tracing its delegate"); + } + MOZ_ASSERT(!gc::IsAboutToBeFinalized(&r.front().value())); + MOZ_ASSERT(k == r.front().key()); + } +} +#endif + +#ifdef JS_GC_ZEAL +template <class K, class V> +bool WeakMap<K, V>::checkMarking() const { + bool ok = true; + for (Range r = Base::all(); !r.empty(); r.popFront()) { + gc::Cell* key = gc::ToMarkable(r.front().key()); + gc::Cell* value = gc::ToMarkable(r.front().value()); + if (key && value) { + if (!gc::CheckWeakMapEntryMarking(this, key, value)) { + ok = false; + } + } + } + return ok; +} +#endif + +} /* namespace js */ + +#endif /* gc_WeakMap_inl_h */ |