From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/gc/WeakMap-inl.h | 375 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 js/src/gc/WeakMap-inl.h (limited to 'js/src/gc/WeakMap-inl.h') diff --git a/js/src/gc/WeakMap-inl.h b/js/src/gc/WeakMap-inl.h new file mode 100644 index 0000000000..015e5071ed --- /dev/null +++ b/js/src/gc/WeakMap-inl.h @@ -0,0 +1,375 @@ +/* -*- 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 +#include + +#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::detail { + +// Return the effective cell color given the current marking state. +// This must be kept in sync with ShouldMark in Marking.cpp. +template +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; +} +static MOZ_MAYBE_UNUSED JSObject* GetDelegateInternal(const Value& key) { + if (key.isObject()) { + return GetDelegateInternal(&key.toObject()); + } + return nullptr; +} + +// Use a helper function to do overload resolution to handle cases like +// Heap: find everything that is convertible to JSObject* (and +// avoid calling barriers). +template +static inline JSObject* GetDelegate(const T& key) { + return GetDelegateInternal(key); +} + +template <> +inline JSObject* GetDelegate(gc::Cell* const&) = delete; + +} // namespace gc::detail + +// 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 +void WeakMap::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 +WeakMap::WeakMap(JSContext* cx, JSObject* memOf) + : WeakMap(cx->zone(), memOf) {} + +template +WeakMap::WeakMap(JS::Zone* zone, JSObject* memOf) + : Base(zone), WeakMapBase(memOf, zone) { + using ElemType = typename K::ElementType; + + // 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. + if constexpr (std::is_pointer_v) { + using NonPtrType = std::remove_pointer_t; + static_assert(JS::IsCCTraceKind(NonPtrType::TraceKind), + "Object's TraceKind should be added to CC graph."); + } + + zone->gcWeakMapList().insertFront(this); + if (zone->gcState() > Zone::Prepare) { + setMapColor(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 +bool WeakMap::markEntry(GCMarker* marker, gc::CellColor mapColor, K& key, + V& value, bool populateWeakKeysTable) { +#ifdef DEBUG + MOZ_ASSERT(IsMarked(mapColor)); + if (marker->isParallelMarking()) { + marker->runtime()->gc.assertCurrentThreadHasLockedGC(); + } +#endif + + bool marked = false; + CellColor markColor = AsCellColor(marker->markColor()); + CellColor keyColor = gc::detail::GetEffectiveColor(marker, key); + JSObject* delegate = gc::detail::GetDelegate(key); + JSTracer* trc = marker->tracer(); + + gc::Cell* keyCell = gc::ToMarkable(key); + MOZ_ASSERT(keyCell); + + 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(keyCell->color() >= proxyPreserveColor); + marked = true; + keyColor = proxyPreserveColor; + } + } + } + + gc::Cell* cellValue = gc::ToMarkable(value); + if (IsMarked(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(AsMarkColor(mapColor), keyCell, delegate, + tenuredValue)) { + marker->abortLinearWeakMarking(); + } + } + } + + return marked; +} + +template +void WeakMap::trace(JSTracer* trc) { + MOZ_ASSERT(isInList()); + + TraceNullableEdge(trc, &memberOf, "WeakMap owner"); + + if (trc->isMarkingTracer()) { + MOZ_ASSERT(trc->weakMapAction() == JS::WeakMapTraceAction::Expand); + GCMarker* marker = GCMarker::fromTracer(trc); + if (markMap(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"); + } +} + +template +bool WeakMap::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. + + // Lock during parallel marking to synchronize updates to the ephemeron edges + // table. + mozilla::Maybe lock; + if (marker->isParallelMarking()) { + lock.emplace(marker->runtime()); + } + + MOZ_ASSERT(IsMarked(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(); + + // Read the atomic color into a local variable so the compiler doesn't load it + // every time. + gc::CellColor mapColor = this->mapColor(); + + for (Enum e(*this); !e.empty(); e.popFront()) { + if (markEntry(marker, mapColor, e.front().mutableKey(), e.front().value(), + populateWeakKeysTable)) { + markedAny = true; + } + } + + return markedAny; +} + +template +void WeakMap::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 +void WeakMap::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 +bool WeakMap::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(); + gc::Cell* keyCell = gc::ToMarkable(key); + MOZ_ASSERT(keyCell); + Zone* keyZone = keyCell->zone(); + if (delegateZone != keyZone && delegateZone->isGCMarking() && + keyZone->isGCMarking()) { + if (!delegateZone->addSweepGroupEdgeTo(keyZone)) { + return false; + } + } + } + return true; +} + +template +size_t WeakMap::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return mallocSizeOf(this) + shallowSizeOfExcludingThis(mallocSizeOf); +} + +#if DEBUG +template +void WeakMap::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 +bool WeakMap::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 + +inline HashNumber GetHash(JS::Symbol* sym) { return sym->hash(); } + +inline bool HashMatch(JS::Symbol* key, JS::Symbol* lookup) { + return key->hash() == lookup->hash(); +} + +} // namespace js + +#endif /* gc_WeakMap_inl_h */ -- cgit v1.2.3