/* -*- 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/. */ /* * API functions and methods used by the rest of SpiderMonkey and by embeddings. */ #include "mozilla/TimeStamp.h" #include "jsapi.h" #include "jsfriendapi.h" #include "gc/GC.h" #include "gc/PublicIterators.h" #include "jit/JitRealm.h" #include "js/HeapAPI.h" #include "js/Value.h" #include "util/DifferentialTesting.h" #include "vm/HelperThreads.h" #include "vm/Realm.h" #include "vm/Scope.h" #include "gc/Marking-inl.h" #include "gc/StableCellHasher-inl.h" #include "vm/GeckoProfiler-inl.h" #include "vm/JSContext-inl.h" using namespace js; using namespace js::gc; using mozilla::TimeStamp; extern JS_PUBLIC_API bool js::AddRawValueRoot(JSContext* cx, Value* vp, const char* name) { MOZ_ASSERT(vp); MOZ_ASSERT(name); bool ok = cx->runtime()->gc.addRoot(vp, name); if (!ok) { JS_ReportOutOfMemory(cx); } return ok; } extern JS_PUBLIC_API void js::RemoveRawValueRoot(JSContext* cx, Value* vp) { cx->runtime()->gc.removeRoot(vp); } JS_PUBLIC_API JS::HeapState JS::RuntimeHeapState() { return TlsContext.get()->runtime()->gc.heapState(); } JS::AutoDisableGenerationalGC::AutoDisableGenerationalGC(JSContext* cx) : cx(cx) { if (!cx->generationalDisabled) { cx->runtime()->gc.evictNursery(JS::GCReason::DISABLE_GENERATIONAL_GC); cx->nursery().disable(); } ++cx->generationalDisabled; MOZ_ASSERT(cx->nursery().isEmpty()); } JS::AutoDisableGenerationalGC::~AutoDisableGenerationalGC() { if (--cx->generationalDisabled == 0 && cx->runtime()->gc.tunables.gcMaxNurseryBytes() > 0) { cx->nursery().enable(); } } JS_PUBLIC_API bool JS::IsGenerationalGCEnabled(JSRuntime* rt) { return !rt->mainContextFromOwnThread()->generationalDisabled; } AutoDisableCompactingGC::AutoDisableCompactingGC(JSContext* cx) : cx(cx) { ++cx->compactingDisabledCount; if (cx->runtime()->gc.isIncrementalGCInProgress() && cx->runtime()->gc.isCompactingGc()) { FinishGC(cx); } } AutoDisableCompactingGC::~AutoDisableCompactingGC() { MOZ_ASSERT(cx->compactingDisabledCount > 0); --cx->compactingDisabledCount; } #ifdef DEBUG /* Should only be called manually under gdb */ void PreventGCDuringInteractiveDebug() { TlsContext.get()->suppressGC++; } #endif void js::ReleaseAllJITCode(JS::GCContext* gcx) { js::CancelOffThreadIonCompile(gcx->runtime()); for (ZonesIter zone(gcx->runtime(), SkipAtoms); !zone.done(); zone.next()) { zone->forceDiscardJitCode(gcx); } for (RealmsIter realm(gcx->runtime()); !realm.done(); realm.next()) { if (jit::JitRealm* jitRealm = realm->jitRealm()) { jitRealm->discardStubs(); } } } AutoSuppressGC::AutoSuppressGC(JSContext* cx) : suppressGC_(cx->suppressGC.ref()) { suppressGC_++; } #ifdef DEBUG AutoDisableProxyCheck::AutoDisableProxyCheck() { TlsContext.get()->disableStrictProxyChecking(); } AutoDisableProxyCheck::~AutoDisableProxyCheck() { TlsContext.get()->enableStrictProxyChecking(); } JS_PUBLIC_API void JS::AssertGCThingMustBeTenured(JSObject* obj) { MOZ_ASSERT(obj->isTenured()); MOZ_ASSERT(obj->getClass()->hasFinalize() && !(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE)); } JS_PUBLIC_API void JS::AssertGCThingIsNotNurseryAllocable(Cell* cell) { MOZ_ASSERT(cell); MOZ_ASSERT(!cell->is() && !cell->is() && !cell->is()); } JS_PUBLIC_API void js::gc::AssertGCThingHasType(js::gc::Cell* cell, JS::TraceKind kind) { if (!cell) { MOZ_ASSERT(kind == JS::TraceKind::Null); return; } MOZ_ASSERT(IsCellPointerValid(cell)); MOZ_ASSERT(cell->getTraceKind() == kind); } #endif #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED JS::AutoAssertNoGC::AutoAssertNoGC(JSContext* maybecx) { if (maybecx) { cx_ = maybecx; } else if (TlsContext.initialized()) { cx_ = TlsContext.get(); } else { cx_ = nullptr; } if (cx_) { cx_->inUnsafeRegion++; } } JS::AutoAssertNoGC::~AutoAssertNoGC() { if (cx_) { MOZ_ASSERT(cx_->inUnsafeRegion > 0); cx_->inUnsafeRegion--; } } #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED #ifdef DEBUG JS::AutoEnterCycleCollection::AutoEnterCycleCollection(JSRuntime* rt) : runtime_(rt) { MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); MOZ_ASSERT(!JS::RuntimeHeapIsBusy()); runtime_->gc.heapState_ = HeapState::CycleCollecting; } JS::AutoEnterCycleCollection::~AutoEnterCycleCollection() { MOZ_ASSERT(JS::RuntimeHeapIsCycleCollecting()); runtime_->gc.heapState_ = HeapState::Idle; } JS::AutoAssertGCCallback::AutoAssertGCCallback() : AutoSuppressGCAnalysis() { MOZ_ASSERT(JS::RuntimeHeapIsCollecting()); } #endif // DEBUG JS_PUBLIC_API const char* JS::GCTraceKindToAscii(JS::TraceKind kind) { switch (kind) { #define MAP_NAME(name, _0, _1, _2) \ case JS::TraceKind::name: \ return "JS " #name; JS_FOR_EACH_TRACEKIND(MAP_NAME); #undef MAP_NAME default: return "Invalid"; } } JS_PUBLIC_API size_t JS::GCTraceKindSize(JS::TraceKind kind) { switch (kind) { #define MAP_SIZE(name, type, _0, _1) \ case JS::TraceKind::name: \ return sizeof(type); JS_FOR_EACH_TRACEKIND(MAP_SIZE); #undef MAP_SIZE default: return 0; } } JS::GCCellPtr::GCCellPtr(const Value& v) : GCCellPtr(v.toGCThing(), v.traceKind()) {} JS::TraceKind JS::GCCellPtr::outOfLineKind() const { MOZ_ASSERT((ptr & OutOfLineTraceKindMask) == OutOfLineTraceKindMask); MOZ_ASSERT(asCell()->isTenured()); return MapAllocToTraceKind(asCell()->asTenured().getAllocKind()); } JS_PUBLIC_API void JS::PrepareZoneForGC(JSContext* cx, Zone* zone) { AssertHeapIsIdle(); CHECK_THREAD(cx); // If we got the zone from a shared atom, we may have the wrong atoms zone // here. if (zone->isAtomsZone()) { zone = cx->runtime()->atomsZone(); } MOZ_ASSERT(cx->runtime()->gc.hasZone(zone)); zone->scheduleGC(); } JS_PUBLIC_API void JS::PrepareForFullGC(JSContext* cx) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->runtime()->gc.fullGCRequested = true; for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) { zone->scheduleGC(); } } JS_PUBLIC_API void JS::PrepareForIncrementalGC(JSContext* cx) { AssertHeapIsIdle(); CHECK_THREAD(cx); if (!JS::IsIncrementalGCInProgress(cx)) { return; } for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) { if (zone->wasGCStarted()) { zone->scheduleGC(); } } } JS_PUBLIC_API bool JS::IsGCScheduled(JSContext* cx) { AssertHeapIsIdle(); CHECK_THREAD(cx); for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) { if (zone->isGCScheduled()) { return true; } } return false; } JS_PUBLIC_API void JS::SkipZoneForGC(JSContext* cx, Zone* zone) { AssertHeapIsIdle(); CHECK_THREAD(cx); MOZ_ASSERT(cx->runtime()->gc.hasZone(zone)); cx->runtime()->gc.fullGCRequested = false; zone->unscheduleGC(); } static inline void CheckGCOptions(JS::GCOptions options) { MOZ_ASSERT(options == JS::GCOptions::Normal || options == JS::GCOptions::Shrink || options == JS::GCOptions::Shutdown); } JS_PUBLIC_API void JS::NonIncrementalGC(JSContext* cx, JS::GCOptions options, GCReason reason) { AssertHeapIsIdle(); CHECK_THREAD(cx); CheckGCOptions(options); cx->runtime()->gc.gc(options, reason); MOZ_ASSERT(!IsIncrementalGCInProgress(cx)); } JS_PUBLIC_API void JS::StartIncrementalGC(JSContext* cx, JS::GCOptions options, GCReason reason, const js::SliceBudget& budget) { AssertHeapIsIdle(); CHECK_THREAD(cx); CheckGCOptions(options); cx->runtime()->gc.startGC(options, reason, budget); } JS_PUBLIC_API void JS::IncrementalGCSlice(JSContext* cx, GCReason reason, const js::SliceBudget& budget) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->runtime()->gc.gcSlice(reason, budget); } JS_PUBLIC_API bool JS::IncrementalGCHasForegroundWork(JSContext* cx) { AssertHeapIsIdle(); CHECK_THREAD(cx); return cx->runtime()->gc.hasForegroundWork(); } JS_PUBLIC_API void JS::FinishIncrementalGC(JSContext* cx, GCReason reason) { AssertHeapIsIdle(); CHECK_THREAD(cx); cx->runtime()->gc.finishGC(reason); } JS_PUBLIC_API void JS::AbortIncrementalGC(JSContext* cx) { AssertHeapIsIdle(); CHECK_THREAD(cx); if (IsIncrementalGCInProgress(cx)) { cx->runtime()->gc.abortGC(); } } char16_t* JS::GCDescription::formatSliceMessage(JSContext* cx) const { UniqueChars cstr = cx->runtime()->gc.stats().formatCompactSliceMessage(); size_t nchars = strlen(cstr.get()); UniqueTwoByteChars out(js_pod_malloc(nchars + 1)); if (!out) { return nullptr; } out.get()[nchars] = 0; CopyAndInflateChars(out.get(), cstr.get(), nchars); return out.release(); } char16_t* JS::GCDescription::formatSummaryMessage(JSContext* cx) const { UniqueChars cstr = cx->runtime()->gc.stats().formatCompactSummaryMessage(); size_t nchars = strlen(cstr.get()); UniqueTwoByteChars out(js_pod_malloc(nchars + 1)); if (!out) { return nullptr; } out.get()[nchars] = 0; CopyAndInflateChars(out.get(), cstr.get(), nchars); return out.release(); } JS::dbg::GarbageCollectionEvent::Ptr JS::GCDescription::toGCEvent( JSContext* cx) const { return JS::dbg::GarbageCollectionEvent::Create( cx->runtime(), cx->runtime()->gc.stats(), cx->runtime()->gc.majorGCCount()); } TimeStamp JS::GCDescription::startTime(JSContext* cx) const { return cx->runtime()->gc.stats().start(); } TimeStamp JS::GCDescription::endTime(JSContext* cx) const { return cx->runtime()->gc.stats().end(); } TimeStamp JS::GCDescription::lastSliceStart(JSContext* cx) const { return cx->runtime()->gc.stats().slices().back().start; } TimeStamp JS::GCDescription::lastSliceEnd(JSContext* cx) const { return cx->runtime()->gc.stats().slices().back().end; } JS::UniqueChars JS::GCDescription::sliceToJSONProfiler(JSContext* cx) const { size_t slices = cx->runtime()->gc.stats().slices().length(); MOZ_ASSERT(slices > 0); return cx->runtime()->gc.stats().renderJsonSlice(slices - 1); } JS::UniqueChars JS::GCDescription::formatJSONProfiler(JSContext* cx) const { return cx->runtime()->gc.stats().renderJsonMessage(); } JS_PUBLIC_API JS::UniqueChars JS::MinorGcToJSON(JSContext* cx) { JSRuntime* rt = cx->runtime(); return rt->gc.stats().renderNurseryJson(); } JS_PUBLIC_API JS::GCSliceCallback JS::SetGCSliceCallback( JSContext* cx, GCSliceCallback callback) { return cx->runtime()->gc.setSliceCallback(callback); } JS_PUBLIC_API JS::DoCycleCollectionCallback JS::SetDoCycleCollectionCallback( JSContext* cx, JS::DoCycleCollectionCallback callback) { return cx->runtime()->gc.setDoCycleCollectionCallback(callback); } JS_PUBLIC_API JS::GCNurseryCollectionCallback JS::SetGCNurseryCollectionCallback(JSContext* cx, GCNurseryCollectionCallback callback) { return cx->runtime()->gc.setNurseryCollectionCallback(callback); } JS_PUBLIC_API void JS::SetLowMemoryState(JSContext* cx, bool newState) { return cx->runtime()->gc.setLowMemoryState(newState); } JS_PUBLIC_API void JS::DisableIncrementalGC(JSContext* cx) { cx->runtime()->gc.disallowIncrementalGC(); } JS_PUBLIC_API bool JS::IsIncrementalGCEnabled(JSContext* cx) { GCRuntime& gc = cx->runtime()->gc; return gc.isIncrementalGCEnabled() && gc.isIncrementalGCAllowed(); } JS_PUBLIC_API bool JS::IsIncrementalGCInProgress(JSContext* cx) { return cx->runtime()->gc.isIncrementalGCInProgress(); } JS_PUBLIC_API bool JS::IsIncrementalGCInProgress(JSRuntime* rt) { return rt->gc.isIncrementalGCInProgress() && !rt->gc.isVerifyPreBarriersEnabled(); } JS_PUBLIC_API bool JS::IsIncrementalBarrierNeeded(JSContext* cx) { if (JS::RuntimeHeapIsBusy()) { return false; } auto state = cx->runtime()->gc.state(); return state != gc::State::NotActive && state <= gc::State::Sweep; } JS_PUBLIC_API void JS::IncrementalPreWriteBarrier(JSObject* obj) { if (!obj) { return; } AutoGeckoProfilerEntry profilingStackFrame( TlsContext.get(), "IncrementalPreWriteBarrier(JSObject*)", JS::ProfilingCategoryPair::GCCC_Barrier); PreWriteBarrier(obj); } JS_PUBLIC_API void JS::IncrementalPreWriteBarrier(GCCellPtr thing) { if (!thing) { return; } AutoGeckoProfilerEntry profilingStackFrame( TlsContext.get(), "IncrementalPreWriteBarrier(GCCellPtr)", JS::ProfilingCategoryPair::GCCC_Barrier); CellPtrPreWriteBarrier(thing); } JS_PUBLIC_API bool JS::WasIncrementalGC(JSRuntime* rt) { return rt->gc.isIncrementalGc(); } bool js::gc::CreateUniqueIdForNativeObject(NativeObject* nobj, uint64_t* uidp) { JSRuntime* runtime = nobj->runtimeFromMainThread(); *uidp = NextCellUniqueId(runtime); JSContext* cx = runtime->mainContextFromOwnThread(); return nobj->setUniqueId(cx, *uidp); } bool js::gc::CreateUniqueIdForNonNativeObject(Cell* cell, UniqueIdMap::AddPtr ptr, uint64_t* uidp) { // If the cell is in the nursery, hopefully unlikely, then we need to tell the // nursery about it so that it can sweep the uid if the thing does not get // tenured. JSRuntime* runtime = cell->runtimeFromMainThread(); if (IsInsideNursery(cell) && !runtime->gc.nursery().addedUniqueIdToCell(cell)) { return false; } // Set a new uid on the cell. *uidp = NextCellUniqueId(runtime); return cell->zone()->uniqueIds().add(ptr, cell, *uidp); } uint64_t js::gc::NextCellUniqueId(JSRuntime* rt) { return rt->gc.nextCellUniqueId(); } namespace js { static const struct GCParamInfo { const char* name; JSGCParamKey key; bool writable; } GCParameters[] = { #define DEFINE_PARAM_INFO(name, key, writable) {name, key, writable}, FOR_EACH_GC_PARAM(DEFINE_PARAM_INFO) #undef DEFINE_PARAM_INFO }; bool GetGCParameterInfo(const char* name, JSGCParamKey* keyOut, bool* writableOut) { MOZ_ASSERT(keyOut); MOZ_ASSERT(writableOut); for (const GCParamInfo& info : GCParameters) { if (strcmp(name, info.name) == 0) { *keyOut = info.key; *writableOut = info.writable; return true; } } return false; } namespace gc { namespace MemInfo { static bool GCBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.heapSize.bytes())); return true; } static bool MallocBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); double bytes = 0; for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) { bytes += zone->mallocHeapSize.bytes(); } args.rval().setNumber(bytes); return true; } static bool GCMaxBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.tunables.gcMaxBytes())); return true; } static bool GCHighFreqGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setBoolean( cx->runtime()->gc.schedulingState.inHighFrequencyGCMode()); return true; } static bool GCNumberGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.gcNumber())); return true; } static bool MajorGCCountGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.majorGCCount())); return true; } static bool MinorGCCountGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.minorGCCount())); return true; } static bool GCSliceCountGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.gcSliceCount())); return true; } static bool GCCompartmentCount(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); size_t count = 0; for (ZonesIter zone(cx->runtime(), WithAtoms); !zone.done(); zone.next()) { count += zone->compartments().length(); } args.rval().setNumber(double(count)); return true; } static bool GCLastStartReason(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); const char* reason = ExplainGCReason(cx->runtime()->gc.lastStartReason()); RootedString str(cx, JS_NewStringCopyZ(cx, reason)); if (!str) { return false; } args.rval().setString(str); return true; } static bool ZoneGCBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->zone()->gcHeapSize.bytes())); return true; } static bool ZoneGCTriggerBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->zone()->gcHeapThreshold.startBytes())); return true; } static bool ZoneGCAllocTriggerGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); bool highFrequency = cx->runtime()->gc.schedulingState.inHighFrequencyGCMode(); args.rval().setNumber( double(cx->zone()->gcHeapThreshold.eagerAllocTrigger(highFrequency))); return true; } static bool ZoneMallocBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->zone()->mallocHeapSize.bytes())); return true; } static bool ZoneMallocTriggerBytesGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->zone()->mallocHeapThreshold.startBytes())); return true; } static bool ZoneGCNumberGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setNumber(double(cx->runtime()->gc.gcNumber())); return true; } #ifdef DEBUG static bool DummyGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); args.rval().setUndefined(); return true; } #endif } /* namespace MemInfo */ JSObject* NewMemoryInfoObject(JSContext* cx) { RootedObject obj(cx, JS_NewObject(cx, nullptr)); if (!obj) { return nullptr; } using namespace MemInfo; struct NamedGetter { const char* name; JSNative getter; } getters[] = {{"gcBytes", GCBytesGetter}, {"gcMaxBytes", GCMaxBytesGetter}, {"mallocBytes", MallocBytesGetter}, {"gcIsHighFrequencyMode", GCHighFreqGetter}, {"gcNumber", GCNumberGetter}, {"majorGCCount", MajorGCCountGetter}, {"minorGCCount", MinorGCCountGetter}, {"sliceCount", GCSliceCountGetter}, {"compartmentCount", GCCompartmentCount}, {"lastStartReason", GCLastStartReason}}; for (auto pair : getters) { JSNative getter = pair.getter; #ifdef DEBUG if (js::SupportDifferentialTesting()) { getter = DummyGetter; } #endif if (!JS_DefineProperty(cx, obj, pair.name, getter, nullptr, JSPROP_ENUMERATE)) { return nullptr; } } RootedObject zoneObj(cx, JS_NewObject(cx, nullptr)); if (!zoneObj) { return nullptr; } if (!JS_DefineProperty(cx, obj, "zone", zoneObj, JSPROP_ENUMERATE)) { return nullptr; } struct NamedZoneGetter { const char* name; JSNative getter; } zoneGetters[] = {{"gcBytes", ZoneGCBytesGetter}, {"gcTriggerBytes", ZoneGCTriggerBytesGetter}, {"gcAllocTrigger", ZoneGCAllocTriggerGetter}, {"mallocBytes", ZoneMallocBytesGetter}, {"mallocTriggerBytes", ZoneMallocTriggerBytesGetter}, {"gcNumber", ZoneGCNumberGetter}}; for (auto pair : zoneGetters) { JSNative getter = pair.getter; #ifdef DEBUG if (js::SupportDifferentialTesting()) { getter = DummyGetter; } #endif if (!JS_DefineProperty(cx, zoneObj, pair.name, getter, nullptr, JSPROP_ENUMERATE)) { return nullptr; } } return obj; } const char* StateName(State state) { switch (state) { #define MAKE_CASE(name) \ case State::name: \ return #name; GCSTATES(MAKE_CASE) #undef MAKE_CASE } MOZ_CRASH("Invalid gc::State enum value"); } const char* StateName(JS::Zone::GCState state) { switch (state) { case JS::Zone::NoGC: return "NoGC"; case JS::Zone::Prepare: return "Prepare"; case JS::Zone::MarkBlackOnly: return "MarkBlackOnly"; case JS::Zone::MarkBlackAndGray: return "MarkBlackAndGray"; case JS::Zone::Sweep: return "Sweep"; case JS::Zone::Finished: return "Finished"; case JS::Zone::Compact: return "Compact"; case JS::Zone::VerifyPreBarriers: return "VerifyPreBarriers"; case JS::Zone::Limit: break; } MOZ_CRASH("Invalid Zone::GCState enum value"); } } /* namespace gc */ } /* namespace js */ JS_PUBLIC_API void js::gc::FinalizeDeadNurseryObject(JSContext* cx, JSObject* obj) { CHECK_THREAD(cx); MOZ_ASSERT(JS::RuntimeHeapIsMinorCollecting()); MOZ_ASSERT(obj); MOZ_ASSERT(IsInsideNursery(obj)); MOZ_ASSERT(!IsForwarded(obj)); const JSClass* jsClass = JS::GetClass(obj); jsClass->doFinalize(cx->gcContext(), obj); } JS_PUBLIC_API void js::gc::SetPerformanceHint(JSContext* cx, PerformanceHint hint) { CHECK_THREAD(cx); MOZ_ASSERT(!JS::RuntimeHeapIsCollecting()); cx->runtime()->gc.setPerformanceHint(hint); }