diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/gc/RootMarking.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/gc/RootMarking.cpp')
-rw-r--r-- | js/src/gc/RootMarking.cpp | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp new file mode 100644 index 0000000000..d1aa46d7ed --- /dev/null +++ b/js/src/gc/RootMarking.cpp @@ -0,0 +1,466 @@ +/* -*- 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/. */ + +#ifdef MOZ_VALGRIND +# include <valgrind/memcheck.h> +#endif + +#include "jstypes.h" + +#include "debugger/DebugAPI.h" +#include "gc/ClearEdgesTracer.h" +#include "gc/GCInternals.h" +#include "gc/PublicIterators.h" +#include "jit/JitFrames.h" +#include "jit/JitRuntime.h" +#include "js/ValueArray.h" +#include "vm/BigIntType.h" +#include "vm/Compartment.h" +#include "vm/HelperThreadState.h" +#include "vm/JSContext.h" + +using namespace js; +using namespace js::gc; + +using mozilla::LinkedList; + +using JS::AutoGCRooter; + +using RootRange = RootedValueMap::Range; +using RootEntry = RootedValueMap::Entry; +using RootEnum = RootedValueMap::Enum; + +template <typename Base, typename T> +inline void TypedRootedGCThingBase<Base, T>::trace(JSTracer* trc, + const char* name) { + auto* self = this->template derived<T>(); + TraceNullableRoot(trc, self->address(), name); +} + +template <typename T> +static inline void TraceExactStackRootList(JSTracer* trc, + StackRootedBase* listHead, + const char* name) { + // Check size of Rooted<T> does not increase. + static_assert(sizeof(Rooted<T>) == sizeof(T) + 2 * sizeof(uintptr_t)); + + for (StackRootedBase* root = listHead; root; root = root->previous()) { + static_cast<Rooted<T>*>(root)->trace(trc, name); + } +} + +static inline void TraceExactStackRootTraceableList(JSTracer* trc, + StackRootedBase* listHead, + const char* name) { + for (StackRootedBase* root = listHead; root; root = root->previous()) { + static_cast<StackRootedTraceableBase*>(root)->trace(trc, name); + } +} + +static inline void TraceStackRoots(JSTracer* trc, + JS::RootedListHeads& stackRoots) { +#define TRACE_ROOTS(name, type, _, _1) \ + TraceExactStackRootList<type*>(trc, stackRoots[JS::RootKind::name], \ + "exact-" #name); + JS_FOR_EACH_TRACEKIND(TRACE_ROOTS) +#undef TRACE_ROOTS + TraceExactStackRootList<jsid>(trc, stackRoots[JS::RootKind::Id], "exact-id"); + TraceExactStackRootList<Value>(trc, stackRoots[JS::RootKind::Value], + "exact-value"); + + // RootedTraceable uses virtual dispatch. + JS::AutoSuppressGCAnalysis nogc; + + TraceExactStackRootTraceableList(trc, stackRoots[JS::RootKind::Traceable], + "Traceable"); +} + +void JS::RootingContext::traceStackRoots(JSTracer* trc) { + TraceStackRoots(trc, stackRoots_); +} + +static void TraceExactStackRoots(JSContext* cx, JSTracer* trc) { + cx->traceStackRoots(trc); +} + +template <typename T> +static inline void TracePersistentRootedList( + JSTracer* trc, LinkedList<PersistentRootedBase>& list, const char* name) { + for (PersistentRootedBase* root : list) { + static_cast<PersistentRooted<T>*>(root)->trace(trc, name); + } +} + +static inline void TracePersistentRootedTraceableList( + JSTracer* trc, LinkedList<PersistentRootedBase>& list, const char* name) { + for (PersistentRootedBase* root : list) { + static_cast<PersistentRootedTraceableBase*>(root)->trace(trc, name); + } +} + +void JSRuntime::tracePersistentRoots(JSTracer* trc) { +#define TRACE_ROOTS(name, type, _, _1) \ + TracePersistentRootedList<type*>(trc, heapRoots.ref()[JS::RootKind::name], \ + "persistent-" #name); + JS_FOR_EACH_TRACEKIND(TRACE_ROOTS) +#undef TRACE_ROOTS + TracePersistentRootedList<jsid>(trc, heapRoots.ref()[JS::RootKind::Id], + "persistent-id"); + TracePersistentRootedList<Value>(trc, heapRoots.ref()[JS::RootKind::Value], + "persistent-value"); + + // RootedTraceable uses virtual dispatch. + JS::AutoSuppressGCAnalysis nogc; + + TracePersistentRootedTraceableList( + trc, heapRoots.ref()[JS::RootKind::Traceable], "persistent-traceable"); +} + +static void TracePersistentRooted(JSRuntime* rt, JSTracer* trc) { + rt->tracePersistentRoots(trc); +} + +template <typename T> +static void FinishPersistentRootedChain( + LinkedList<PersistentRootedBase>& list) { + while (!list.isEmpty()) { + static_cast<PersistentRooted<T>*>(list.getFirst())->reset(); + } +} + +void JSRuntime::finishPersistentRoots() { +#define FINISH_ROOT_LIST(name, type, _, _1) \ + FinishPersistentRootedChain<type*>(heapRoots.ref()[JS::RootKind::name]); + JS_FOR_EACH_TRACEKIND(FINISH_ROOT_LIST) +#undef FINISH_ROOT_LIST + FinishPersistentRootedChain<jsid>(heapRoots.ref()[JS::RootKind::Id]); + FinishPersistentRootedChain<Value>(heapRoots.ref()[JS::RootKind::Value]); + + // Note that we do not finalize the Traceable list as we do not know how to + // safely clear members. We instead assert that none escape the RootLists. + // See the comment on RootLists::~RootLists for details. +} + +JS_PUBLIC_API void js::TraceValueArray(JSTracer* trc, size_t length, + Value* elements) { + TraceRootRange(trc, length, elements, "JS::RootedValueArray"); +} + +void AutoGCRooter::trace(JSTracer* trc) { + switch (kind_) { + case Kind::Wrapper: + static_cast<AutoWrapperRooter*>(this)->trace(trc); + break; + + case Kind::WrapperVector: + static_cast<AutoWrapperVector*>(this)->trace(trc); + break; + + case Kind::Custom: + static_cast<JS::CustomAutoRooter*>(this)->trace(trc); + break; + + default: + MOZ_CRASH("Bad AutoGCRooter::Kind"); + break; + } +} + +void AutoWrapperRooter::trace(JSTracer* trc) { + /* + * We need to use TraceManuallyBarrieredEdge here because we trace wrapper + * roots in every slice. This is because of some rule-breaking in + * RemapAllWrappersForObject; see comment there. + */ + TraceManuallyBarrieredEdge(trc, &value.get(), "js::AutoWrapperRooter.value"); +} + +void AutoWrapperVector::trace(JSTracer* trc) { + /* + * We need to use TraceManuallyBarrieredEdge here because we trace wrapper + * roots in every slice. This is because of some rule-breaking in + * RemapAllWrappersForObject; see comment there. + */ + for (WrapperValue& value : *this) { + TraceManuallyBarrieredEdge(trc, &value.get(), + "js::AutoWrapperVector.vector"); + } +} + +void JS::RootingContext::traceAllGCRooters(JSTracer* trc) { + for (AutoGCRooter* list : autoGCRooters_) { + traceGCRooterList(trc, list); + } +} + +void JS::RootingContext::traceWrapperGCRooters(JSTracer* trc) { + traceGCRooterList(trc, autoGCRooters_[AutoGCRooter::Kind::Wrapper]); + traceGCRooterList(trc, autoGCRooters_[AutoGCRooter::Kind::WrapperVector]); +} + +/* static */ +inline void JS::RootingContext::traceGCRooterList(JSTracer* trc, + AutoGCRooter* head) { + for (AutoGCRooter* rooter = head; rooter; rooter = rooter->down) { + rooter->trace(trc); + } +} + +void PropertyDescriptor::trace(JSTracer* trc) { + TraceRoot(trc, &value_, "Descriptor::value"); + if (getter_) { + TraceRoot(trc, &getter_, "Descriptor::getter"); + } + if (setter_) { + TraceRoot(trc, &setter_, "Descriptor::setter"); + } +} + +void js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc, + AutoGCSession& session) { + MOZ_ASSERT(!TlsContext.get()->suppressGC); + + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS); + + // We only need to trace atoms when we're marking; atoms are never moved by + // compacting GC. + if (atomsZone()->isGCMarking()) { + traceRuntimeAtoms(trc); + } + + { + // Trace incoming cross compartment edges from uncollected compartments, + // skipping gray edges which are traced later. + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_CCWS); + Compartment::traceIncomingCrossCompartmentEdgesForZoneGC( + trc, Compartment::NonGrayEdges); + } + + traceRuntimeCommon(trc, MarkRuntime); +} + +void js::gc::GCRuntime::traceRuntimeForMinorGC(JSTracer* trc, + AutoGCSession& session) { + MOZ_ASSERT(!TlsContext.get()->suppressGC); + + // Note that we *must* trace the runtime during the SHUTDOWN_GC's minor GC + // despite having called FinishRoots already. This is because FinishRoots + // does not clear the crossCompartmentWrapper map. It cannot do this + // because Proxy's trace for CrossCompartmentWrappers asserts presence in + // the map. And we can reach its trace function despite having finished the + // roots via the edges stored by the pre-barrier verifier when we finish + // the verifier for the last time. + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS); + + traceRuntimeCommon(trc, TraceRuntime); +} + +void js::TraceRuntime(JSTracer* trc) { + MOZ_ASSERT(!trc->isMarkingTracer()); + + JSRuntime* rt = trc->runtime(); + AutoEmptyNurseryAndPrepareForTracing prep(rt->mainContextFromOwnThread()); + gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP); + rt->gc.traceRuntime(trc, prep); +} + +void js::TraceRuntimeWithoutEviction(JSTracer* trc) { + MOZ_ASSERT(!trc->isMarkingTracer()); + + JSRuntime* rt = trc->runtime(); + AutoTraceSession session(rt); + gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP); + rt->gc.traceRuntime(trc, session); +} + +void js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoTraceSession& session) { + MOZ_ASSERT(!rt->isBeingDestroyed()); + + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS); + + traceRuntimeAtoms(trc); + traceRuntimeCommon(trc, TraceRuntime); +} + +void js::gc::GCRuntime::traceRuntimeAtoms(JSTracer* trc) { + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_RUNTIME_DATA); + TraceAtoms(trc); + jit::JitRuntime::TraceAtomZoneRoots(trc); +} + +void js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, + TraceOrMarkRuntime traceOrMark) { + { + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_STACK); + + JSContext* cx = rt->mainContextFromOwnThread(); + + // Trace active interpreter and JIT stack roots. + TraceInterpreterActivations(cx, trc); + jit::TraceJitActivations(cx, trc); + + // Trace legacy C stack roots. + cx->traceAllGCRooters(trc); + + // Trace C stack roots. + TraceExactStackRoots(cx, trc); + + for (RootRange r = rootsHash.ref().all(); !r.empty(); r.popFront()) { + const RootEntry& entry = r.front(); + TraceRoot(trc, entry.key(), entry.value()); + } + } + + // Trace runtime global roots. + TracePersistentRooted(rt, trc); + +#ifdef JS_HAS_INTL_API + // Trace the shared Intl data. + rt->traceSharedIntlData(trc); +#endif + + // Trace the JSContext. + rt->mainContextFromOwnThread()->trace(trc); + + // Trace all realm roots, but not the realm itself; it is traced via the + // parent pointer if traceRoots actually traces anything. + for (RealmsIter r(rt); !r.done(); r.next()) { + r->traceRoots(trc, traceOrMark); + } + + if (!JS::RuntimeHeapIsMinorCollecting()) { + // Trace the self-hosting stencil. The contents of this are always tenured. + rt->traceSelfHostingStencil(trc); + + for (ZonesIter zone(this, ZoneSelector::SkipAtoms); !zone.done(); + zone.next()) { + zone->traceRootsInMajorGC(trc); + } + + // Trace interpreter entry code generated with --emit-interpreter-entry + if (rt->hasJitRuntime() && rt->jitRuntime()->hasInterpreterEntryMap()) { + rt->jitRuntime()->getInterpreterEntryMap()->traceTrampolineCode(trc); + } + } + + // Trace helper thread roots. + HelperThreadState().trace(trc); + + // Trace Debugger.Frames that have live hooks, since dropping them would be + // observable. In effect, they are rooted by the stack frames. + DebugAPI::traceFramesWithLiveHooks(trc); + + // Trace the embedding's black and gray roots. + if (!JS::RuntimeHeapIsMinorCollecting()) { + gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_EMBEDDING); + + /* + * The embedding can register additional roots here. + * + * We don't need to trace these in a minor GC because all pointers into + * the nursery should be in the store buffer, and we want to avoid the + * time taken to trace all these roots. + */ + traceEmbeddingBlackRoots(trc); + + /* During GC, we don't trace gray roots at this stage. */ + if (traceOrMark == TraceRuntime) { + traceEmbeddingGrayRoots(trc); + } + } + + traceKeptObjects(trc); +} + +void GCRuntime::traceEmbeddingBlackRoots(JSTracer* trc) { + // The analysis doesn't like the function pointer below. + JS::AutoSuppressGCAnalysis nogc; + + for (const auto& callback : blackRootTracers.ref()) { + (*callback.op)(trc, callback.data); + } +} + +void GCRuntime::traceEmbeddingGrayRoots(JSTracer* trc) { + SliceBudget budget = SliceBudget::unlimited(); + MOZ_ALWAYS_TRUE(traceEmbeddingGrayRoots(trc, budget) == Finished); +} + +IncrementalProgress GCRuntime::traceEmbeddingGrayRoots(JSTracer* trc, + SliceBudget& budget) { + // The analysis doesn't like the function pointer below. + JS::AutoSuppressGCAnalysis nogc; + + const auto& callback = grayRootTracer.ref(); + if (!callback.op) { + return Finished; + } + + return callback.op(trc, budget, callback.data) ? Finished : NotFinished; +} + +#ifdef DEBUG +class AssertNoRootsTracer final : public JS::CallbackTracer { + void onChild(JS::GCCellPtr thing, const char* name) override { + MOZ_CRASH("There should not be any roots during runtime shutdown"); + } + + public: + // This skips tracking WeakMap entries because they are not roots. + explicit AssertNoRootsTracer(JSRuntime* rt) + : JS::CallbackTracer(rt, JS::TracerKind::Callback, + JS::WeakMapTraceAction::Skip) {} +}; +#endif // DEBUG + +void js::gc::GCRuntime::finishRoots() { + AutoNoteSingleThreadedRegion anstr; + + rt->finishAtoms(); + restoreSharedAtomsZone(); + + rootsHash.ref().clear(); + + rt->finishPersistentRoots(); + + rt->finishSelfHosting(); + + for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) { + zone->finishRoots(); + } + +#ifdef JS_GC_ZEAL + clearSelectedForMarking(); +#endif + + // Clear out the interpreter entry map before the final gc. + ClearInterpreterEntryMap(rt); + + // Clear any remaining roots from the embedding (as otherwise they will be + // left dangling after we shut down) and remove the callbacks. + ClearEdgesTracer trc(rt); + traceEmbeddingBlackRoots(&trc); + traceEmbeddingGrayRoots(&trc); + clearBlackAndGrayRootTracers(); +} + +void js::gc::GCRuntime::checkNoRuntimeRoots(AutoGCSession& session) { +#ifdef DEBUG + AssertNoRootsTracer trc(rt); + traceRuntimeForMajorGC(&trc, session); +#endif // DEBUG +} + +JS_PUBLIC_API void JS::AddPersistentRoot(JS::RootingContext* cx, RootKind kind, + PersistentRootedBase* root) { + JSRuntime* rt = static_cast<JSContext*>(cx)->runtime(); + rt->heapRoots.ref()[kind].insertBack(root); +} + +JS_PUBLIC_API void JS::AddPersistentRoot(JSRuntime* rt, RootKind kind, + PersistentRootedBase* root) { + rt->heapRoots.ref()[kind].insertBack(root); +} |