/* -*- 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/. */ /* * Trace methods for all GC things, defined in a separate header to allow * inlining. * * This also includes eager inline marking versions. Both paths must end up * traversing equivalent subgraphs. */ #ifndef gc_TraceMethods_inl_h #define gc_TraceMethods_inl_h #include "mozilla/Likely.h" #include "gc/GCMarker.h" #include "gc/Tracer.h" #include "jit/JitCode.h" #include "vm/BigIntType.h" #include "vm/GetterSetter.h" #include "vm/GlobalObject.h" #include "vm/JSScript.h" #include "vm/PropMap.h" #include "vm/Realm.h" #include "vm/Scope.h" #include "vm/Shape.h" #include "vm/StringType.h" #include "vm/SymbolType.h" #include "wasm/WasmJS.h" #include "gc/BufferAllocator-inl.h" #include "gc/Marking-inl.h" #include "vm/StringType-inl.h" inline void js::BaseScript::traceChildren(JSTracer* trc) { TraceNullableEdge(trc, &function_, "function"); TraceEdge(trc, &sourceObject_, "sourceObject"); if (data_) { TraceBufferEdge(trc, this, &data_, "PrivateScriptData"); data_->trace(trc); } warmUpData_.trace(trc); } inline void js::Shape::traceChildren(JSTracer* trc) { TraceCellHeaderEdge(trc, this, "base"); if (isNative()) { asNative().traceChildren(trc); } } inline void js::NativeShape::traceChildren(JSTracer* trc) { TraceNullableEdge(trc, &propMap_, "propertymap"); } template void js::GCMarker::eagerlyMarkChildren(Shape* shape) { MOZ_ASSERT(shape->isMarked(markColor())); BaseShape* base = shape->base(); markAndTraverseEdge(shape, base); if (shape->isNative()) { if (PropMap* map = shape->asNative().propMap()) { markAndTraverseEdge(shape, map); } } } inline void js::BaseShape::traceChildren(JSTracer* trc) { // Note: the realm's global can be nullptr if we GC while creating the global. JSObject* global = realm()->unsafeUnbarrieredMaybeGlobal(); if (MOZ_LIKELY(global)) { TraceManuallyBarrieredEdge(trc, &global, "baseshape_global"); } if (proto_.isObject()) { TraceEdge(trc, &proto_, "baseshape_proto"); } } template void js::GCMarker::eagerlyMarkChildren(BaseShape* base) { JSObject* global = base->realm()->unsafeUnbarrieredMaybeGlobal(); if (MOZ_LIKELY(global)) { markAndTraverseEdge(base, global); } TaggedProto proto = base->proto(); if (proto.isObject()) { markAndTraverseEdge(base, proto.toObject()); } } inline void JSString::traceChildren(JSTracer* trc) { // Concurrent marking uses the other path. MOZ_ASSERT(!js::IsConcurrentMarkingTracer(trc)); if (hasBase()) { traceBase(trc); } else if (isRope()) { asRope().traceChildren(trc); } } template void js::GCMarker::eagerlyMarkChildren(JSString* str) { uint32_t flags = str->flags(); if (flags & js::StringFlags::LINEAR_BIT) { eagerlyMarkChildren(static_cast(str)); } else { eagerlyMarkChildren(static_cast(str)); } } inline void JSString::traceBase(JSTracer* trc) { MOZ_ASSERT(hasBase()); js::TraceManuallyBarrieredEdge(trc, &d.s.u3.base, "base"); } template void js::GCMarker::eagerlyMarkChildren(JSLinearString* linearStr) { gc::AssertShouldMarkInZone(this, linearStr); MOZ_ASSERT(linearStr->isMarkedAny()); // Use iterative marking to avoid blowing out the stack. while (linearStr->hasBase()) { linearStr = linearStr->base(); // It's possible to observe a rope as the base of a linear string if we // process barriers during rope flattening. See the assignment of base in // JSRope::flattenInternal's finish_node section. if (static_cast(linearStr)->isRope()) { MOZ_ASSERT(!JS::RuntimeHeapIsMajorCollecting()); break; } MOZ_ASSERT(linearStr->JSString::isLinear()); gc::AssertShouldMarkInZone(this, linearStr); if (!mark(static_cast(linearStr))) { break; } } } inline void JSRope::traceChildren(JSTracer* trc) { // Concurrent marking uses the other path. MOZ_ASSERT(!js::IsConcurrentMarkingTracer(trc)); js::TraceManuallyBarrieredEdge(trc, &d.s.u2.left, "left child"); js::TraceManuallyBarrieredEdge(trc, &d.s.u3.right, "right child"); } template void js::GCMarker::eagerlyMarkChildren(JSRope* rope) { // This function tries to scan the whole rope tree using the marking stack as // temporary storage. If that becomes full, the unscanned ropes are added to // the delayed marking list. When the function returns, the marking stack is // at the same depth as it was on entry and ropes never leak to other users of // the stack. This also assumes that a rope can only point to other ropes or // linear strings, it cannot refer to GC things of other types. size_t savedPos = stack.position(); MOZ_DIAGNOSTIC_ASSERT(rope->getTraceKind() == JS::TraceKind::String); while (true) { MOZ_DIAGNOSTIC_ASSERT(rope->getTraceKind() == JS::TraceKind::String); gc::AssertShouldMarkInZone(this, rope); MOZ_ASSERT(rope->isMarkedAny()); JSRope* next = nullptr; JSString* left = rope->leftChild(); JSString* right = rope->rightChild(); #ifdef JS_GC_CONCURRENT_MARKING // Check for a change of type performed by the main thread. This uses // fence/fence synchronisation with the atomic operation being the update to // the string flags. // // If we observe a change we skip marking the string here. This string will // be marked by main thread. // // We care about the following transitions here: // - rope => dependent string (rope flattening) // - rope => extensible string (rope flattening) // - rope => atom ref // // The order of operations is important here: we read the child fields, // perform the memory fence and then check the flags. When changing the type // on the main thread these happen in the reverse order. This ensures that // we can't observe updated children with the old type. Observing the new // type with old children is possible but benign. if constexpr (bool(opts & gc::MarkingOptions::ConcurrentMarking)) { gc::MemoryAcquireFence(rope->runtimeFromAnyThread()); if (!rope->isRopeAtomic()) { continue; } } #else MOZ_DIAGNOSTIC_ASSERT(rope->JSString::isRope()); #endif if (mark(right)) { MOZ_ASSERT(!right->isPermanentAtom()); if (right->isLinear()) { eagerlyMarkChildren(static_cast(right)); } else { next = static_cast(right); } } if (mark(left)) { MOZ_ASSERT(!left->isPermanentAtom()); if (left->isLinear()) { eagerlyMarkChildren(static_cast(left)); } else { // When both children are ropes, set aside the right one to // scan it later. if (next && !stack.pushTempRope(next)) { delayMarkingChildrenOnOOM(next); } next = static_cast(left); } } if (next) { rope = next; } else if (savedPos != stack.position()) { MOZ_ASSERT(savedPos < stack.position()); rope = stack.popPtr().asTempRope(); } else { break; } } MOZ_ASSERT(savedPos == stack.position()); } inline void JS::Symbol::traceChildren(JSTracer* trc) { js::TraceNullableCellHeaderEdge(trc, this, "symbol description"); } template void js::RuntimeScopeData::trace(JSTracer* trc) { TraceBindingNames(trc, GetScopeDataTrailingNamesPointer(this), length); } inline void js::FunctionScope::RuntimeData::trace(JSTracer* trc) { TraceNullableEdge(trc, &canonicalFunction, "scope canonical function"); TraceNullableBindingNames(trc, GetScopeDataTrailingNamesPointer(this), length); } inline void js::ModuleScope::RuntimeData::trace(JSTracer* trc) { TraceNullableEdge(trc, &module, "scope module"); TraceBindingNames(trc, GetScopeDataTrailingNamesPointer(this), length); } inline void js::WasmInstanceScope::RuntimeData::trace(JSTracer* trc) { TraceNullableEdge(trc, &instance, "wasm instance"); TraceBindingNames(trc, GetScopeDataTrailingNamesPointer(this), length); } inline void js::Scope::traceChildren(JSTracer* trc) { TraceNullableEdge(trc, &environmentShape_, "scope env shape"); TraceNullableEdge(trc, &enclosingScope_, "scope enclosing"); BaseScopeData* data = rawData(); if (data) { TraceBufferEdge(trc, this, &data, "Scope data"); if (data != rawData()) { setHeaderPtr(data); } applyScopeDataTyped([trc](auto data) { data->trace(trc); }); } } template void js::GCMarker::eagerlyMarkChildren(Scope* scope) { do { if (Shape* shape = scope->environmentShape()) { markAndTraverseEdge(scope, shape); } if (BaseScopeData* data = scope->rawData()) { MarkTenuredBuffer(scope->zone(), data); } mozilla::Span> names; switch (scope->kind()) { case ScopeKind::Function: { FunctionScope::RuntimeData& data = scope->as().data(); if (data.canonicalFunction) { markAndTraverseObjectEdge(scope, data.canonicalFunction); } names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::FunctionBodyVar: { VarScope::RuntimeData& data = scope->as().data(); names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: case ScopeKind::FunctionLexical: { LexicalScope::RuntimeData& data = scope->as().data(); names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::ClassBody: { ClassBodyScope::RuntimeData& data = scope->as().data(); names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::Global: case ScopeKind::NonSyntactic: { GlobalScope::RuntimeData& data = scope->as().data(); names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::Eval: case ScopeKind::StrictEval: { EvalScope::RuntimeData& data = scope->as().data(); names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::Module: { ModuleScope::RuntimeData& data = scope->as().data(); if (data.module) { markAndTraverseObjectEdge(scope, data.module); } names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::With: break; case ScopeKind::WasmInstance: { WasmInstanceScope::RuntimeData& data = scope->as().data(); markAndTraverseObjectEdge(scope, data.instance); names = GetScopeDataTrailingNames(&data); break; } case ScopeKind::WasmFunction: { WasmFunctionScope::RuntimeData& data = scope->as().data(); names = GetScopeDataTrailingNames(&data); break; } } if (scope->kind_ == ScopeKind::Function) { for (auto& binding : names) { if (JSAtom* name = binding.name()) { markAndTraverseStringEdge(scope, name); } } } else { for (auto& binding : names) { markAndTraverseStringEdge(scope, binding.name()); } } scope = scope->enclosing(); } while (scope && mark(scope)); } inline void js::GetterSetter::traceChildren(JSTracer* trc) { if (getter()) { TraceCellHeaderEdge(trc, this, "gettersetter_getter"); } if (setter()) { TraceEdge(trc, &setter_, "gettersetter_setter"); } } inline void js::PropMap::traceChildren(JSTracer* trc) { if (hasPrevious()) { TraceEdge(trc, &asLinked()->data_.previous, "propmap_previous"); } if (isShared()) { SharedPropMap::TreeData& treeData = asShared()->treeDataRef(); if (SharedPropMap* parent = treeData.parent.maybeMap()) { TraceManuallyBarrieredEdge(trc, &parent, "propmap_parent"); if (parent != treeData.parent.map()) { treeData.setParent(parent, treeData.parent.index()); } } } for (uint32_t i = 0; i < PropMap::Capacity; i++) { if (hasKey(i)) { TraceEdge(trc, &keys_[i], "propmap_key"); } } if (canHaveTable() && asLinked()->hasTable()) { asLinked()->data_.table->trace(trc); } } template void js::GCMarker::eagerlyMarkChildren(PropMap* map) { MOZ_ASSERT(map->isMarkedAny()); do { for (uint32_t i = 0; i < PropMap::Capacity; i++) { if (map->hasKey(i)) { markAndTraverseEdge(map, map->getKey(i)); } } if (map->canHaveTable()) { // Special case: if a map has a table then all its pointers must point to // this map or an ancestor. Since these pointers will be traced by this // loop they do not need to be traced here as well. MOZ_ASSERT_IF(state != MarkingState::ConcurrentMarking, map->asLinked()->canSkipMarkingTable()); } if (map->isDictionary()) { map = map->asDictionary()->previous(); } else { // For shared maps follow the |parent| link and not the |previous| link. // They're different when a map had a branch that wasn't at the end of the // map, but in this case they must have the same |previous| map. This is // asserted in SharedPropMap::addChild. In other words, marking all // |parent| maps will also mark all |previous| maps. map = map->asShared()->treeDataRef().parent.maybeMap(); } } while (map && mark(map)); } inline void JS::BigInt::traceChildren(JSTracer* trc) { if (!hasInlineDigits()) { js::TraceBufferEdge(trc, this, &heapDigits_, "BigInt::heapDigits_"); } } // JitCode::traceChildren is not defined inline due to its dependence on // MacroAssembler. #endif // gc_TraceMethods_inl_h