diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/gc/WeakMap-inl.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/gc/WeakMap-inl.h')
-rw-r--r-- | js/src/gc/WeakMap-inl.h | 413 |
1 files changed, 413 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..1b5626211f --- /dev/null +++ b/js/src/gc/WeakMap-inl.h @@ -0,0 +1,413 @@ +/* -*- 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/Maybe.h" + +#include <algorithm> +#include <type_traits> + +#include "gc/GCLock.h" +#include "gc/Marking.h" +#include "gc/Zone.h" +#include "js/TraceKind.h" +#include "vm/JSContext.h" + +#include "gc/StableCellHasher-inl.h" + +namespace js { +namespace gc { + +namespace detail { + +// 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(GCMarker* marker, const T& item) { + Cell* cell = ToMarkable(item); + if (!cell->isTenured()) { + return CellColor::Black; + } + const TenuredCell& t = cell->asTenured(); + if (!t.zoneFromAnyThread()->shouldMarkInZone(marker->markColor())) { + return CellColor::Black; + } + MOZ_ASSERT(t.runtimeFromAnyThread() == marker->runtime()); + return t.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 MOZ_MAYBE_UNUSED 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(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) + : WeakMap(cx->zone(), memOf) {} + +template <class K, class V> +WeakMap<K, V>::WeakMap(JS::Zone* zone, JSObject* memOf) + : Base(zone), WeakMapBase(memOf, 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; + } +} + +// If the entry is live, ensure its key and value are marked. Also make sure the +// key is at least as marked as min(map, delegate), so it cannot get discarded +// and then recreated by rewrapping the delegate. +// +// Optionally adds edges to the ephemeron edges table for any keys (or +// delegates) where future changes to their mark color would require marking the +// value (or the key). +template <class K, class V> +bool WeakMap<K, V>::markEntry(GCMarker* marker, K& key, V& value, + bool populateWeakKeysTable) { +#ifdef DEBUG + MOZ_ASSERT(mapColor); + if (marker->isParallelMarking()) { + marker->runtime()->gc.assertCurrentThreadHasLockedGC(); + } +#endif + + bool marked = false; + CellColor markColor = marker->markColor(); + CellColor keyColor = gc::detail::GetEffectiveColor(marker, key); + JSObject* delegate = gc::detail::GetDelegate(key); + JSTracer* trc = marker->tracer(); + + if (delegate) { + CellColor delegateColor = gc::detail::GetEffectiveColor(marker, delegate); + // The key needs to stay alive while both the delegate and map are live. + CellColor proxyPreserveColor = std::min(delegateColor, mapColor); + if (keyColor < proxyPreserveColor) { + MOZ_ASSERT(markColor >= proxyPreserveColor); + if (markColor == proxyPreserveColor) { + TraceWeakMapKeyEdge(trc, zone(), &key, + "proxy-preserved WeakMap entry key"); + MOZ_ASSERT(key->color() >= proxyPreserveColor); + marked = true; + keyColor = proxyPreserveColor; + } + } + } + + gc::Cell* cellValue = gc::ToMarkable(value); + if (keyColor) { + if (cellValue) { + CellColor targetColor = std::min(mapColor, keyColor); + CellColor valueColor = gc::detail::GetEffectiveColor(marker, cellValue); + if (valueColor < targetColor) { + MOZ_ASSERT(markColor >= targetColor); + if (markColor == targetColor) { + TraceEdge(trc, &value, "WeakMap entry value"); + MOZ_ASSERT(cellValue->color() >= targetColor); + marked = true; + } + } + } + } + + if (populateWeakKeysTable) { + // Note that delegateColor >= keyColor because marking a key marks its + // delegate, so we only need to check whether keyColor < mapColor to tell + // this. + + if (keyColor < mapColor) { + MOZ_ASSERT(trc->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::TenuredCell* tenuredValue = nullptr; + if (cellValue && cellValue->isTenured()) { + tenuredValue = &cellValue->asTenured(); + } + + if (!this->addImplicitEdges(key, delegate, tenuredValue)) { + marker->abortLinearWeakMarking(); + } + } + } + + return marked; +} + +template <class K, class V> +void WeakMap<K, V>::trace(JSTracer* trc) { + MOZ_ASSERT(isInList()); + + TraceNullableEdge(trc, &memberOf, "WeakMap owner"); + + if (trc->isMarkingTracer()) { + MOZ_ASSERT(trc->weakMapAction() == JS::WeakMapTraceAction::Expand); + auto marker = GCMarker::fromTracer(trc); + + // Lock if we are marking in parallel to synchronize updates to: + // - the weak map's color + // - the ephemeron edges table + mozilla::Maybe<AutoLockGC> lock; + if (marker->isParallelMarking()) { + lock.emplace(marker->runtime()); + } + + // 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(); + (void)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"); + } +} + +bool WeakMapBase::addImplicitEdges(gc::Cell* key, gc::Cell* delegate, + gc::TenuredCell* value) { + if (delegate) { + auto& edgeTable = delegate->zone()->gcEphemeronEdges(delegate); + auto* p = edgeTable.get(delegate); + + gc::EphemeronEdgeVector newVector; + gc::EphemeronEdgeVector& edges = p ? p->value : newVector; + + // Add a <weakmap, delegate> -> key edge: the key must be preserved for + // future lookups until either the weakmap or the delegate dies. + gc::EphemeronEdge keyEdge{mapColor, key}; + if (!edges.append(keyEdge)) { + return false; + } + + if (value) { + gc::EphemeronEdge valueEdge{mapColor, value}; + if (!edges.append(valueEdge)) { + return false; + } + } + + if (!p) { + return edgeTable.put(delegate, std::move(newVector)); + } + + return true; + } + + // No delegate. Insert just the key -> value edge. + + if (!value) { + return true; + } + + auto& edgeTable = key->zone()->gcEphemeronEdges(key); + auto* p = edgeTable.get(key); + gc::EphemeronEdge valueEdge{mapColor, value}; + if (p) { + return p->value.append(valueEdge); + } else { + gc::EphemeronEdgeVector edges; + MOZ_ALWAYS_TRUE(edges.append(valueEdge)); + return edgeTable.put(key, std::move(edges)); + } +} + +template <class K, class V> +bool WeakMap<K, V>::markEntries(GCMarker* marker) { + // This method is called whenever the map's mark color changes. Mark values + // (and keys with delegates) as required for the new color and populate the + // ephemeron edges if we're in incremental marking mode. + +#ifdef DEBUG + if (marker->isParallelMarking()) { + marker->runtime()->gc.assertCurrentThreadHasLockedGC(); + } +#endif + + MOZ_ASSERT(mapColor); + bool markedAny = false; + + // If we don't populate the weak keys table now then we do it when we enter + // weak marking mode. + bool populateWeakKeysTable = + marker->incrementalWeakMapMarkingEnabled || marker->isWeakMarking(); + + for (Enum e(*this); !e.empty(); e.popFront()) { + if (markEntry(marker, e.front().mutableKey(), e.front().value(), + populateWeakKeysTable)) { + markedAny = true; + } + } + + return markedAny; +} + +template <class K, class V> +void WeakMap<K, V>::traceWeakEdges(JSTracer* trc) { + // Remove all entries whose keys remain unmarked. + for (Enum e(*this); !e.empty(); e.popFront()) { + if (!TraceWeakEdge(trc, &e.front().mutableKey(), "WeakMap key")) { + 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; +} + +template <class K, class V> +size_t WeakMap<K, V>::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return mallocSizeOf(this) + shallowSizeOfExcludingThis(mallocSizeOf); +} + +#if DEBUG +template <class K, class V> +void WeakMap<K, V>::assertEntriesNotAboutToBeFinalized() { + for (Range r = Base::all(); !r.empty(); r.popFront()) { + UnbarrieredKey k = 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())); + } +} +#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 */ |