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