Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
3 : * This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "gc/Zone.h"
8 :
9 : #include "gc/FreeOp.h"
10 : #include "gc/Policy.h"
11 : #include "gc/PublicIterators.h"
12 : #include "jit/BaselineJIT.h"
13 : #include "jit/Ion.h"
14 : #include "jit/JitRealm.h"
15 : #include "vm/Debugger.h"
16 : #include "vm/Runtime.h"
17 :
18 : #include "gc/GC-inl.h"
19 : #include "gc/Marking-inl.h"
20 : #include "vm/Realm-inl.h"
21 :
22 : using namespace js;
23 : using namespace js::gc;
24 :
25 : Zone * const Zone::NotOnList = reinterpret_cast<Zone*>(1);
26 :
27 0 : JS::Zone::Zone(JSRuntime* rt)
28 : : JS::shadow::Zone(rt, &rt->gc.marker),
29 : // Note: don't use |this| before initializing helperThreadUse_!
30 : // ProtectedData checks in CheckZone::check may read this field.
31 : helperThreadUse_(HelperThreadUse::None),
32 : helperThreadOwnerContext_(nullptr),
33 : debuggers(this, nullptr),
34 : uniqueIds_(this),
35 : suppressAllocationMetadataBuilder(this, false),
36 : arenas(rt, this),
37 : types(this),
38 : gcWeakMapList_(this),
39 : compartments_(),
40 : gcGrayRoots_(this),
41 : gcWeakRefs_(this),
42 : weakCaches_(this),
43 0 : gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
44 : typeDescrObjects_(this, this),
45 : regExps(this),
46 : markedAtoms_(this),
47 : atomCache_(this),
48 : externalStringCache_(this),
49 : functionToStringCache_(this),
50 : keepAtomsCount(this, 0),
51 : purgeAtomsDeferred(this, 0),
52 : usage(&rt->gc.usage),
53 : threshold(),
54 : gcDelayBytes(0),
55 : tenuredStrings(this, 0),
56 : allocNurseryStrings(this, true),
57 : propertyTree_(this, this),
58 : baseShapes_(this, this),
59 : initialShapes_(this, this),
60 : nurseryShapes_(this),
61 : data(this, nullptr),
62 : isSystem(this, false),
63 : #ifdef DEBUG
64 : gcLastSweepGroupIndex(this, 0),
65 : #endif
66 : jitZone_(this, nullptr),
67 : gcScheduled_(false),
68 : gcScheduledSaved_(false),
69 : gcPreserveCode_(this, false),
70 : keepShapeTables_(this, false),
71 882 : listNext_(NotOnList)
72 : {
73 : /* Ensure that there are no vtables to mess us up here. */
74 : MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone*>(this) ==
75 : static_cast<JS::shadow::Zone*>(this));
76 :
77 0 : AutoLockGC lock(rt);
78 0 : threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables, rt->gc.schedulingState, lock);
79 0 : setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
80 21 : jitCodeCounter.setMax(jit::MaxCodeBytesPerProcess * 0.8, lock);
81 0 : }
82 :
83 0 : Zone::~Zone()
84 : {
85 0 : MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::None);
86 :
87 0 : JSRuntime* rt = runtimeFromAnyThread();
88 10 : if (this == rt->gc.systemZone)
89 0 : rt->gc.systemZone = nullptr;
90 :
91 10 : js_delete(debuggers.ref());
92 10 : js_delete(jitZone_.ref());
93 :
94 : #ifdef DEBUG
95 : // Avoid assertions failures warning that not everything has been destroyed
96 : // if the embedding leaked GC things.
97 0 : if (!rt->gc.shutdownCollectedEverything()) {
98 0 : gcWeakMapList().clear();
99 0 : regExps.clear();
100 : }
101 : #endif
102 5 : }
103 :
104 : bool
105 0 : Zone::init(bool isSystemArg)
106 : {
107 0 : isSystem = isSystemArg;
108 0 : return uniqueIds().init() &&
109 0 : gcSweepGroupEdges().init() &&
110 0 : gcWeakKeys().init() &&
111 0 : typeDescrObjects().init() &&
112 0 : markedAtoms().init() &&
113 84 : atomCache().init() &&
114 42 : regExps.init();
115 : }
116 :
117 : void
118 0 : Zone::setNeedsIncrementalBarrier(bool needs)
119 : {
120 0 : MOZ_ASSERT_IF(needs, canCollect());
121 0 : needsIncrementalBarrier_ = needs;
122 0 : }
123 :
124 : void
125 0 : Zone::beginSweepTypes(bool releaseTypes)
126 : {
127 0 : types.beginSweep(releaseTypes);
128 0 : }
129 :
130 : Zone::DebuggerVector*
131 0 : Zone::getOrCreateDebuggers(JSContext* cx)
132 : {
133 0 : if (debuggers)
134 0 : return debuggers;
135 :
136 0 : debuggers = js_new<DebuggerVector>();
137 0 : if (!debuggers)
138 0 : ReportOutOfMemory(cx);
139 0 : return debuggers;
140 : }
141 :
142 : void
143 0 : Zone::sweepBreakpoints(FreeOp* fop)
144 : {
145 0 : if (fop->runtime()->debuggerList().isEmpty())
146 : return;
147 :
148 : /*
149 : * Sweep all compartments in a zone at the same time, since there is no way
150 : * to iterate over the scripts belonging to a single compartment in a zone.
151 : */
152 :
153 0 : MOZ_ASSERT(isGCSweepingOrCompacting());
154 0 : for (auto iter = cellIter<JSScript>(); !iter.done(); iter.next()) {
155 0 : JSScript* script = iter;
156 0 : if (!script->hasAnyBreakpointsOrStepMode())
157 0 : continue;
158 :
159 0 : bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
160 0 : MOZ_ASSERT(script == iter);
161 0 : for (unsigned i = 0; i < script->length(); i++) {
162 0 : BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
163 0 : if (!site)
164 : continue;
165 :
166 : Breakpoint* nextbp;
167 0 : for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
168 0 : nextbp = bp->nextInSite();
169 0 : GCPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef();
170 :
171 : // If we are sweeping, then we expect the script and the
172 : // debugger object to be swept in the same sweep group, except
173 : // if the breakpoint was added after we computed the sweep
174 : // groups. In this case both script and debugger object must be
175 : // live.
176 0 : MOZ_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(),
177 : dbgobj->zone()->isGCSweeping() ||
178 : (!scriptGone && dbgobj->asTenured().isMarkedAny()));
179 :
180 0 : bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj);
181 0 : MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef()));
182 0 : if (dying)
183 0 : bp->destroy(fop);
184 : }
185 : }
186 : }
187 : }
188 :
189 : void
190 0 : Zone::sweepWeakMaps()
191 : {
192 : /* Finalize unreachable (key,value) pairs in all weak maps. */
193 0 : WeakMapBase::sweepZone(this);
194 0 : }
195 :
196 : void
197 0 : Zone::discardJitCode(FreeOp* fop, bool discardBaselineCode)
198 : {
199 0 : if (!jitZone())
200 : return;
201 :
202 0 : if (isPreservingCode())
203 : return;
204 :
205 0 : if (discardBaselineCode) {
206 : #ifdef DEBUG
207 : /* Assert no baseline scripts are marked as active. */
208 0 : for (auto script = cellIter<JSScript>(); !script.done(); script.next())
209 0 : MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
210 : #endif
211 :
212 : /* Mark baseline scripts on the stack as active. */
213 0 : jit::MarkActiveBaselineScripts(this);
214 : }
215 :
216 : /* Only mark OSI points if code is being discarded. */
217 0 : jit::InvalidateAll(fop, this);
218 :
219 0 : for (auto script = cellIter<JSScript>(); !script.done(); script.next()) {
220 0 : jit::FinishInvalidation(fop, script);
221 :
222 : /*
223 : * Discard baseline script if it's not marked as active. Note that
224 : * this also resets the active flag.
225 : */
226 0 : if (discardBaselineCode)
227 0 : jit::FinishDiscardBaselineScript(fop, script);
228 :
229 : /*
230 : * Warm-up counter for scripts are reset on GC. After discarding code we
231 : * need to let it warm back up to get information such as which
232 : * opcodes are setting array holes or accessing getter properties.
233 : */
234 0 : script->resetWarmUpCounter();
235 :
236 : /*
237 : * Make it impossible to use the control flow graphs cached on the
238 : * BaselineScript. They get deleted.
239 : */
240 0 : if (script->hasBaselineScript())
241 0 : script->baselineScript()->setControlFlowGraph(nullptr);
242 : }
243 :
244 : /*
245 : * When scripts contains pointers to nursery things, the store buffer
246 : * can contain entries that point into the optimized stub space. Since
247 : * this method can be called outside the context of a GC, this situation
248 : * could result in us trying to mark invalid store buffer entries.
249 : *
250 : * Defer freeing any allocated blocks until after the next minor GC.
251 : */
252 0 : if (discardBaselineCode) {
253 0 : jitZone()->optimizedStubSpace()->freeAllAfterMinorGC(this);
254 0 : jitZone()->purgeIonCacheIRStubInfo();
255 : }
256 :
257 : /*
258 : * Free all control flow graphs that are cached on BaselineScripts.
259 : * Assuming this happens on the main thread and all control flow
260 : * graph reads happen on the main thread, this is safe.
261 : */
262 0 : jitZone()->cfgSpace()->lifoAlloc().freeAll();
263 : }
264 :
265 : #ifdef JSGC_HASH_TABLE_CHECKS
266 : void
267 0 : JS::Zone::checkUniqueIdTableAfterMovingGC()
268 : {
269 0 : for (auto r = uniqueIds().all(); !r.empty(); r.popFront())
270 0 : js::gc::CheckGCThingAfterMovingGC(r.front().key());
271 0 : }
272 : #endif
273 :
274 : uint64_t
275 137172 : Zone::gcNumber()
276 : {
277 : // Zones in use by exclusive threads are not collected, and threads using
278 : // them cannot access the main runtime's gcNumber without racing.
279 273380 : return usedByHelperThread() ? 0 : runtimeFromMainThread()->gc.gcNumber();
280 : }
281 :
282 : js::jit::JitZone*
283 0 : Zone::createJitZone(JSContext* cx)
284 : {
285 0 : MOZ_ASSERT(!jitZone_);
286 :
287 10 : if (!cx->runtime()->getJitRuntime(cx))
288 : return nullptr;
289 :
290 30 : UniquePtr<jit::JitZone> jitZone(cx->new_<js::jit::JitZone>());
291 10 : if (!jitZone || !jitZone->init(cx))
292 : return nullptr;
293 :
294 20 : jitZone_ = jitZone.release();
295 20 : return jitZone_;
296 : }
297 :
298 : bool
299 0 : Zone::hasMarkedRealms()
300 : {
301 0 : for (RealmsInZoneIter realm(this); !realm.done(); realm.next()) {
302 0 : if (realm->marked())
303 0 : return true;
304 : }
305 0 : return false;
306 : }
307 :
308 : bool
309 0 : Zone::canCollect()
310 : {
311 : // The atoms zone cannot be collected while off-thread parsing is taking
312 : // place.
313 0 : if (isAtomsZone())
314 0 : return !runtimeFromAnyThread()->hasHelperThreadZones();
315 :
316 : // Zones that will be or are currently used by other threads cannot be
317 : // collected.
318 0 : return !createdForHelperThread();
319 : }
320 :
321 : void
322 0 : Zone::notifyObservingDebuggers()
323 : {
324 0 : JSRuntime* rt = runtimeFromMainThread();
325 0 : JSContext* cx = rt->mainContextFromOwnThread();
326 :
327 0 : for (RealmsInZoneIter realms(this); !realms.done(); realms.next()) {
328 0 : RootedGlobalObject global(cx, realms->unsafeUnbarrieredMaybeGlobal());
329 0 : if (!global)
330 0 : continue;
331 :
332 0 : GlobalObject::DebuggerVector* dbgs = global->getDebuggers();
333 0 : if (!dbgs)
334 : continue;
335 :
336 0 : for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) {
337 0 : if (!r.front()->debuggeeIsBeingCollected(rt->gc.majorGCCount())) {
338 : #ifdef DEBUG
339 : fprintf(stderr,
340 : "OOM while notifying observing Debuggers of a GC: The onGarbageCollection\n"
341 0 : "hook will not be fired for this GC for some Debuggers!\n");
342 : #endif
343 0 : return;
344 : }
345 : }
346 : }
347 : }
348 :
349 : bool
350 0 : Zone::isOnList() const
351 : {
352 0 : return listNext_ != NotOnList;
353 : }
354 :
355 : Zone*
356 0 : Zone::nextZone() const
357 : {
358 0 : MOZ_ASSERT(isOnList());
359 0 : return listNext_;
360 : }
361 :
362 : void
363 0 : Zone::clearTables()
364 : {
365 0 : MOZ_ASSERT(regExps.empty());
366 :
367 0 : if (baseShapes().initialized())
368 0 : baseShapes().clear();
369 0 : if (initialShapes().initialized())
370 5 : initialShapes().clear();
371 5 : }
372 :
373 : void
374 0 : Zone::fixupAfterMovingGC()
375 : {
376 0 : fixupInitialShapeTable();
377 0 : }
378 :
379 : bool
380 84 : Zone::addTypeDescrObject(JSContext* cx, HandleObject obj)
381 : {
382 : // Type descriptor objects are always tenured so we don't need post barriers
383 : // on the set.
384 0 : MOZ_ASSERT(!IsInsideNursery(obj));
385 :
386 0 : if (!typeDescrObjects().put(obj)) {
387 0 : ReportOutOfMemory(cx);
388 0 : return false;
389 : }
390 :
391 : return true;
392 : }
393 :
394 : void
395 0 : Zone::deleteEmptyCompartment(JS::Compartment* comp)
396 : {
397 5 : MOZ_ASSERT(comp->zone() == this);
398 0 : MOZ_ASSERT(arenas.checkEmptyArenaLists());
399 :
400 0 : MOZ_ASSERT(compartments().length() == 1);
401 5 : MOZ_ASSERT(compartments()[0] == comp);
402 0 : MOZ_ASSERT(comp->realms().length() == 1);
403 :
404 5 : Realm* realm = comp->realms()[0];
405 0 : FreeOp* fop = runtimeFromMainThread()->defaultFreeOp();
406 0 : realm->destroy(fop);
407 5 : comp->destroy(fop);
408 :
409 0 : compartments().clear();
410 5 : }
411 :
412 : void
413 0 : Zone::setHelperThreadOwnerContext(JSContext* cx)
414 : {
415 15 : MOZ_ASSERT_IF(cx, TlsContext.get() == cx);
416 0 : helperThreadOwnerContext_ = cx;
417 10 : }
418 :
419 : bool
420 0 : Zone::ownedByCurrentHelperThread()
421 : {
422 26431 : MOZ_ASSERT(usedByHelperThread());
423 0 : MOZ_ASSERT(TlsContext.get());
424 0 : return helperThreadOwnerContext_ == TlsContext.get();
425 : }
426 :
427 0 : void Zone::releaseAtoms()
428 : {
429 2442 : MOZ_ASSERT(hasKeptAtoms());
430 :
431 0 : keepAtomsCount--;
432 :
433 3663 : if (!hasKeptAtoms() && purgeAtomsDeferred) {
434 0 : atomCache().clearAndShrink();
435 0 : purgeAtomsDeferred = false;
436 : }
437 0 : }
438 :
439 : void
440 0 : Zone::purgeAtomCacheOrDefer()
441 : {
442 0 : if (hasKeptAtoms()) {
443 0 : purgeAtomsDeferred = true;
444 0 : return;
445 : }
446 :
447 0 : atomCache().clearAndShrink();
448 : }
449 :
450 : void
451 0 : Zone::traceAtomCache(JSTracer* trc)
452 : {
453 0 : MOZ_ASSERT(hasKeptAtoms());
454 0 : for (auto r = atomCache().all(); !r.empty(); r.popFront()) {
455 0 : JSAtom* atom = r.front().asPtrUnbarriered();
456 0 : TraceRoot(trc, &atom, "kept atom");
457 0 : MOZ_ASSERT(r.front().asPtrUnbarriered() == atom);
458 : }
459 0 : }
460 :
461 0 : ZoneList::ZoneList()
462 8 : : head(nullptr), tail(nullptr)
463 8 : {}
464 :
465 0 : ZoneList::ZoneList(Zone* zone)
466 0 : : head(zone), tail(zone)
467 : {
468 0 : MOZ_RELEASE_ASSERT(!zone->isOnList());
469 0 : zone->listNext_ = nullptr;
470 0 : }
471 :
472 0 : ZoneList::~ZoneList()
473 : {
474 0 : MOZ_ASSERT(isEmpty());
475 0 : }
476 :
477 : void
478 0 : ZoneList::check() const
479 : {
480 : #ifdef DEBUG
481 0 : MOZ_ASSERT((head == nullptr) == (tail == nullptr));
482 0 : if (!head)
483 : return;
484 :
485 : Zone* zone = head;
486 0 : for (;;) {
487 0 : MOZ_ASSERT(zone && zone->isOnList());
488 0 : if (zone == tail)
489 : break;
490 0 : zone = zone->listNext_;
491 : }
492 0 : MOZ_ASSERT(!zone->listNext_);
493 : #endif
494 : }
495 :
496 : bool
497 0 : ZoneList::isEmpty() const
498 : {
499 0 : return head == nullptr;
500 : }
501 :
502 : Zone*
503 0 : ZoneList::front() const
504 : {
505 0 : MOZ_ASSERT(!isEmpty());
506 0 : MOZ_ASSERT(head->isOnList());
507 0 : return head;
508 : }
509 :
510 : void
511 0 : ZoneList::append(Zone* zone)
512 : {
513 0 : ZoneList singleZone(zone);
514 0 : transferFrom(singleZone);
515 0 : }
516 :
517 : void
518 0 : ZoneList::transferFrom(ZoneList& other)
519 : {
520 0 : check();
521 0 : other.check();
522 0 : MOZ_ASSERT(tail != other.tail);
523 :
524 : if (tail)
525 : tail->listNext_ = other.head;
526 : else
527 : head = other.head;
528 : tail = other.tail;
529 :
530 : other.head = nullptr;
531 : other.tail = nullptr;
532 : }
533 :
534 : Zone*
535 : ZoneList::removeFront()
536 : {
537 : MOZ_ASSERT(!isEmpty());
538 : check();
539 :
540 : Zone* front = head;
541 : head = head->listNext_;
542 : if (!head)
543 : tail = nullptr;
544 :
545 : front->listNext_ = Zone::NotOnList;
546 :
547 : return front;
548 : }
549 :
550 : void
551 : ZoneList::clear()
552 : {
553 : while (!isEmpty())
554 : removeFront();
555 : }
556 :
557 : JS_PUBLIC_API(void)
558 : JS::shadow::RegisterWeakCache(JS::Zone* zone, detail::WeakCacheBase* cachep)
559 : {
560 : zone->registerWeakCache(cachep);
561 : }
|