/* -*- 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/. */ #include "gc/Barrier.h" #include "gc/Policy.h" #include "jit/Ion.h" #include "js/HashTable.h" #include "js/shadow/Zone.h" // JS::shadow::Zone #include "js/Value.h" #include "vm/BigIntType.h" // JS::BigInt #include "vm/EnvironmentObject.h" #include "vm/GeneratorObject.h" #include "vm/JSObject.h" #include "vm/Realm.h" #include "vm/SharedArrayObject.h" #include "vm/SymbolType.h" #include "wasm/WasmJS.h" #include "gc/Zone-inl.h" namespace js { bool RuntimeFromMainThreadIsHeapMajorCollecting(JS::shadow::Zone* shadowZone) { MOZ_ASSERT( CurrentThreadCanAccessRuntime(shadowZone->runtimeFromMainThread())); return JS::RuntimeHeapIsMajorCollecting(); } #ifdef DEBUG bool IsMarkedBlack(JSObject* obj) { return obj->isMarkedBlack(); } bool HeapSlot::preconditionForSet(NativeObject* owner, Kind kind, uint32_t slot) const { if (kind == Slot) { return &owner->getSlotRef(slot) == this; } uint32_t numShifted = owner->getElementsHeader()->numShiftedElements(); MOZ_ASSERT(slot >= numShifted); return &owner->getDenseElement(slot - numShifted) == (const Value*)this; } void HeapSlot::assertPreconditionForPostWriteBarrier( NativeObject* obj, Kind kind, uint32_t slot, const Value& target) const { if (kind == Slot) { MOZ_ASSERT(obj->getSlotAddressUnchecked(slot)->get() == target); } else { uint32_t numShifted = obj->getElementsHeader()->numShiftedElements(); MOZ_ASSERT(slot >= numShifted); MOZ_ASSERT( static_cast(obj->getDenseElements() + (slot - numShifted)) ->get() == target); } AssertTargetIsNotGray(obj); } bool CurrentThreadIsIonCompiling() { jit::JitContext* jcx = jit::MaybeGetJitContext(); return jcx && jcx->inIonBackend(); } bool CurrentThreadIsGCMarking() { JSContext* cx = MaybeGetJSContext(); return cx && cx->gcUse == JSContext::GCUse::Marking; } bool CurrentThreadIsGCSweeping() { JSContext* cx = MaybeGetJSContext(); return cx && cx->gcUse == JSContext::GCUse::Sweeping; } bool CurrentThreadIsGCFinalizing() { JSContext* cx = MaybeGetJSContext(); return cx && cx->gcUse == JSContext::GCUse::Finalizing; } bool CurrentThreadIsTouchingGrayThings() { JSContext* cx = MaybeGetJSContext(); return cx && cx->isTouchingGrayThings; } AutoTouchingGrayThings::AutoTouchingGrayThings() { TlsContext.get()->isTouchingGrayThings++; } AutoTouchingGrayThings::~AutoTouchingGrayThings() { JSContext* cx = TlsContext.get(); MOZ_ASSERT(cx->isTouchingGrayThings); cx->isTouchingGrayThings--; } #endif // DEBUG // Tagged pointer barriers // // It's tempting to use ApplyGCThingTyped to dispatch to the typed barrier // functions (e.g. gc::ReadBarrier(JSObject*)) but this does not compile well // (clang generates 1580 bytes on x64 versus 296 bytes for this implementation // of ValueReadBarrier). // // Instead, check known special cases and call the generic barrier functions. static MOZ_ALWAYS_INLINE bool ValueIsPermanent(const Value& value) { gc::Cell* cell = value.toGCThing(); if (value.isString()) { return cell->as()->isPermanentAndMayBeShared(); } if (value.isSymbol()) { return cell->as()->isPermanentAndMayBeShared(); } #ifdef DEBUG // Using mozilla::DebugOnly here still generated code in opt builds. bool isPermanent = MapGCThingTyped(value, [](auto t) { return t->isPermanentAndMayBeShared(); }).value(); MOZ_ASSERT(!isPermanent); #endif return false; } void gc::ValueReadBarrier(const Value& v) { MOZ_ASSERT(v.isGCThing()); if (!ValueIsPermanent(v)) { ReadBarrierImpl(v.toGCThing()); } } void gc::ValuePreWriteBarrier(const Value& v) { MOZ_ASSERT(v.isGCThing()); if (!ValueIsPermanent(v)) { PreWriteBarrierImpl(v.toGCThing()); } } static MOZ_ALWAYS_INLINE bool IdIsPermanent(jsid id) { gc::Cell* cell = id.toGCThing(); if (id.isString()) { return cell->as()->isPermanentAndMayBeShared(); } if (id.isSymbol()) { return cell->as()->isPermanentAndMayBeShared(); } #ifdef DEBUG bool isPermanent = MapGCThingTyped(id, [](auto t) { return t->isPermanentAndMayBeShared(); }).value(); MOZ_ASSERT(!isPermanent); #endif return false; } void gc::IdPreWriteBarrier(jsid id) { MOZ_ASSERT(id.isGCThing()); if (!IdIsPermanent(id)) { PreWriteBarrierImpl(&id.toGCThing()->asTenured()); } } static MOZ_ALWAYS_INLINE bool CellPtrIsPermanent(JS::GCCellPtr thing) { if (thing.mayBeOwnedByOtherRuntime()) { return true; } #ifdef DEBUG bool isPermanent = MapGCThingTyped( thing, [](auto t) { return t->isPermanentAndMayBeShared(); }); MOZ_ASSERT(!isPermanent); #endif return false; } void gc::CellPtrPreWriteBarrier(JS::GCCellPtr thing) { MOZ_ASSERT(thing); if (!CellPtrIsPermanent(thing)) { PreWriteBarrierImpl(thing.asCell()); } } template /* static */ bool MovableCellHasher::hasHash(const Lookup& l) { if (!l) { return true; } return l->zoneFromAnyThread()->hasUniqueId(l); } template /* static */ bool MovableCellHasher::ensureHash(const Lookup& l) { if (!l) { return true; } uint64_t unusedId; return l->zoneFromAnyThread()->getOrCreateUniqueId(l, &unusedId); } template /* static */ HashNumber MovableCellHasher::hash(const Lookup& l) { if (!l) { return 0; } // We have to access the zone from-any-thread here: a worker thread may be // cloning a self-hosted object from the main runtime's self- hosting zone // into another runtime. The zone's uid lock will protect against multiple // workers doing this simultaneously. MOZ_ASSERT(CurrentThreadCanAccessZone(l->zoneFromAnyThread()) || l->zoneFromAnyThread()->isSelfHostingZone() || CurrentThreadIsPerformingGC()); return l->zoneFromAnyThread()->getHashCodeInfallible(l); } template /* static */ bool MovableCellHasher::match(const Key& k, const Lookup& l) { // Return true if both are null or false if only one is null. if (!k) { return !l; } if (!l) { return false; } MOZ_ASSERT(k); MOZ_ASSERT(l); MOZ_ASSERT(CurrentThreadCanAccessZone(l->zoneFromAnyThread()) || l->zoneFromAnyThread()->isSelfHostingZone()); Zone* zone = k->zoneFromAnyThread(); if (zone != l->zoneFromAnyThread()) { return false; } #ifdef DEBUG // Incremental table sweeping means that existing table entries may no // longer have unique IDs. We fail the match in that case and the entry is // removed from the table later on. if (!zone->hasUniqueId(k)) { Key key = k; MOZ_ASSERT(IsAboutToBeFinalizedUnbarriered(&key)); } MOZ_ASSERT(zone->hasUniqueId(l)); #endif uint64_t keyId; if (!zone->maybeGetUniqueId(k, &keyId)) { // Key is dead and cannot match lookup which must be live. return false; } return keyId == zone->getUniqueIdInfallible(l); } #if !MOZ_IS_GCC template struct JS_PUBLIC_API MovableCellHasher; #endif template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; template struct JS_PUBLIC_API MovableCellHasher; } // namespace js // Post-write barrier, used by the C++ Heap implementation. JS_PUBLIC_API void JS::HeapObjectPostWriteBarrier(JSObject** objp, JSObject* prev, JSObject* next) { MOZ_ASSERT(objp); js::InternalBarrierMethods::postBarrier(objp, prev, next); } JS_PUBLIC_API void JS::HeapStringPostWriteBarrier(JSString** strp, JSString* prev, JSString* next) { MOZ_ASSERT(strp); js::InternalBarrierMethods::postBarrier(strp, prev, next); } JS_PUBLIC_API void JS::HeapBigIntPostWriteBarrier(JS::BigInt** bip, JS::BigInt* prev, JS::BigInt* next) { MOZ_ASSERT(bip); js::InternalBarrierMethods::postBarrier(bip, prev, next); } JS_PUBLIC_API void JS::HeapValuePostWriteBarrier(JS::Value* valuep, const Value& prev, const Value& next) { MOZ_ASSERT(valuep); js::InternalBarrierMethods::postBarrier(valuep, prev, next); } // Combined pre- and post-write barriers, used by the rust Heap // implementation. JS_PUBLIC_API void JS::HeapObjectWriteBarriers(JSObject** objp, JSObject* prev, JSObject* next) { MOZ_ASSERT(objp); js::InternalBarrierMethods::preBarrier(prev); js::InternalBarrierMethods::postBarrier(objp, prev, next); } JS_PUBLIC_API void JS::HeapStringWriteBarriers(JSString** strp, JSString* prev, JSString* next) { MOZ_ASSERT(strp); js::InternalBarrierMethods::preBarrier(prev); js::InternalBarrierMethods::postBarrier(strp, prev, next); } JS_PUBLIC_API void JS::HeapBigIntWriteBarriers(JS::BigInt** bip, JS::BigInt* prev, JS::BigInt* next) { MOZ_ASSERT(bip); js::InternalBarrierMethods::preBarrier(prev); js::InternalBarrierMethods::postBarrier(bip, prev, next); } JS_PUBLIC_API void JS::HeapScriptWriteBarriers(JSScript** scriptp, JSScript* prev, JSScript* next) { MOZ_ASSERT(scriptp); js::InternalBarrierMethods::preBarrier(prev); js::InternalBarrierMethods::postBarrier(scriptp, prev, next); } JS_PUBLIC_API void JS::HeapValueWriteBarriers(JS::Value* valuep, const Value& prev, const Value& next) { MOZ_ASSERT(valuep); js::InternalBarrierMethods::preBarrier(prev); js::InternalBarrierMethods::postBarrier(valuep, prev, next); }