Source code

Revision control

Other Tools

/* -*- 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()->wasGCStarted()) {
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() == ExpandWeakMaps);
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() == DoNotTraceWeakMaps) {
return;
}
// Trace keys only if weakMapAction() says to.
if (trc->weakMapAction() == TraceWeakMapKeysValues) {
for (Enum e(*this); !e.empty(); e.popFront()) {
TraceWeakMapKeyEdge(trc, zone(), &e.front().mutableKey(),
"WeakMap entry key");
}
}
// Always trace all values (unless weakMapAction() is
// DoNotTraceWeakMaps).
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() == 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);
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 */