Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/src/gc/Zone.cpp
Line
Count
Source (jump to first uncovered line)
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-inl.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
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(this),
37
    tenuredAllocsSinceMinorGC_(0),
38
    types(this),
39
    gcWeakMapList_(this),
40
    compartments_(),
41
    gcGrayRoots_(this),
42
    gcWeakRefs_(this),
43
    weakCaches_(this),
44
    gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
45
    typeDescrObjects_(this, 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(0),
65
#endif
66
    jitZone_(this, nullptr),
67
    gcScheduled_(false),
68
    gcScheduledSaved_(false),
69
    gcPreserveCode_(false),
70
    keepShapeTables_(this, false),
71
    listNext_(NotOnList)
72
9
{
73
9
    /* Ensure that there are no vtables to mess us up here. */
74
9
    MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone*>(this) ==
75
9
               static_cast<JS::shadow::Zone*>(this));
76
9
77
9
    AutoLockGC lock(rt);
78
9
    threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables, rt->gc.schedulingState, lock);
79
9
    setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
80
9
    jitCodeCounter.setMax(jit::MaxCodeBytesPerProcess * 0.8, lock);
81
9
}
82
83
Zone::~Zone()
84
0
{
85
0
    MOZ_ASSERT(helperThreadUse_ == HelperThreadUse::None);
86
0
87
0
    JSRuntime* rt = runtimeFromAnyThread();
88
0
    if (this == rt->gc.systemZone) {
89
0
        rt->gc.systemZone = nullptr;
90
0
    }
91
0
92
0
    js_delete(debuggers.ref());
93
0
    js_delete(jitZone_.ref());
94
0
95
#ifdef DEBUG
96
    // Avoid assertions failures warning that not everything has been destroyed
97
    // if the embedding leaked GC things.
98
    if (!rt->gc.shutdownCollectedEverything()) {
99
        gcWeakMapList().clear();
100
        regExps().clear();
101
    }
102
#endif
103
}
104
105
bool
106
Zone::init(bool isSystemArg)
107
9
{
108
9
    isSystem = isSystemArg;
109
9
    regExps_.ref() = make_unique<RegExpZone>(this);
110
9
    return regExps_.ref() && gcWeakKeys().init();
111
9
}
112
113
void
114
Zone::setNeedsIncrementalBarrier(bool needs)
115
72
{
116
72
    MOZ_ASSERT_IF(needs, canCollect());
117
72
    needsIncrementalBarrier_ = needs;
118
72
}
119
120
void
121
Zone::beginSweepTypes(bool releaseTypes)
122
18
{
123
18
    types.beginSweep(releaseTypes);
124
18
}
125
126
Zone::DebuggerVector*
127
Zone::getOrCreateDebuggers(JSContext* cx)
128
0
{
129
0
    if (debuggers) {
130
0
        return debuggers;
131
0
    }
132
0
133
0
    debuggers = js_new<DebuggerVector>();
134
0
    if (!debuggers) {
135
0
        ReportOutOfMemory(cx);
136
0
    }
137
0
    return debuggers;
138
0
}
139
140
void
141
Zone::sweepBreakpoints(FreeOp* fop)
142
18
{
143
18
    if (fop->runtime()->debuggerList().isEmpty()) {
144
18
        return;
145
18
    }
146
0
147
0
    /*
148
0
     * Sweep all compartments in a zone at the same time, since there is no way
149
0
     * to iterate over the scripts belonging to a single compartment in a zone.
150
0
     */
151
0
152
0
    MOZ_ASSERT(isGCSweepingOrCompacting());
153
0
    for (auto iter = cellIter<JSScript>(); !iter.done(); iter.next()) {
154
0
        JSScript* script = iter;
155
0
        if (!script->hasAnyBreakpointsOrStepMode()) {
156
0
            continue;
157
0
        }
158
0
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
0
                continue;
165
0
            }
166
0
167
0
            Breakpoint* nextbp;
168
0
            for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
169
0
                nextbp = bp->nextInSite();
170
0
                GCPtrNativeObject& dbgobj = bp->debugger->toJSObjectRef();
171
0
172
0
                // If we are sweeping, then we expect the script and the
173
0
                // debugger object to be swept in the same sweep group, except
174
0
                // if the breakpoint was added after we computed the sweep
175
0
                // groups. In this case both script and debugger object must be
176
0
                // live.
177
0
                MOZ_ASSERT_IF(isGCSweeping() && dbgobj->zone()->isCollecting(),
178
0
                              dbgobj->zone()->isGCSweeping() ||
179
0
                              (!scriptGone && dbgobj->asTenured().isMarkedAny()));
180
0
181
0
                bool dying = scriptGone || IsAboutToBeFinalized(&dbgobj);
182
0
                MOZ_ASSERT_IF(!dying, !IsAboutToBeFinalized(&bp->getHandlerRef()));
183
0
                if (dying) {
184
0
                    bp->destroy(fop);
185
0
                }
186
0
            }
187
0
        }
188
0
    }
189
0
}
190
191
void
192
Zone::sweepWeakMaps()
193
18
{
194
18
    /* Finalize unreachable (key,value) pairs in all weak maps. */
195
18
    WeakMapBase::sweepZone(this);
196
18
}
197
198
void
199
Zone::discardJitCode(FreeOp* fop, bool discardBaselineCode)
200
36
{
201
36
    if (!jitZone()) {
202
0
        return;
203
0
    }
204
36
205
36
    if (isPreservingCode()) {
206
28
        return;
207
28
    }
208
8
209
8
    if (discardBaselineCode) {
210
#ifdef DEBUG
211
        /* Assert no baseline scripts are marked as active. */
212
        for (auto script = cellIter<JSScript>(); !script.done(); script.next()) {
213
            MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
214
        }
215
#endif
216
217
8
        /* Mark baseline scripts on the stack as active. */
218
8
        jit::MarkActiveBaselineScripts(this);
219
8
    }
220
8
221
8
    /* Only mark OSI points if code is being discarded. */
222
8
    jit::InvalidateAll(fop, this);
223
8
224
632
    for (auto script = cellIter<JSScript>(); !script.done(); script.next())  {
225
624
        jit::FinishInvalidation(fop, script);
226
624
227
624
        /*
228
624
         * Discard baseline script if it's not marked as active. Note that
229
624
         * this also resets the active flag.
230
624
         */
231
624
        if (discardBaselineCode) {
232
624
            jit::FinishDiscardBaselineScript(fop, script);
233
624
        }
234
624
235
624
        /*
236
624
         * Warm-up counter for scripts are reset on GC. After discarding code we
237
624
         * need to let it warm back up to get information such as which
238
624
         * opcodes are setting array holes or accessing getter properties.
239
624
         */
240
624
        script->resetWarmUpCounter();
241
624
242
624
        /*
243
624
         * Make it impossible to use the control flow graphs cached on the
244
624
         * BaselineScript. They get deleted.
245
624
         */
246
624
        if (script->hasBaselineScript()) {
247
4
            script->baselineScript()->setControlFlowGraph(nullptr);
248
4
        }
249
624
    }
250
8
251
8
    /*
252
8
     * When scripts contains pointers to nursery things, the store buffer
253
8
     * can contain entries that point into the optimized stub space. Since
254
8
     * this method can be called outside the context of a GC, this situation
255
8
     * could result in us trying to mark invalid store buffer entries.
256
8
     *
257
8
     * Defer freeing any allocated blocks until after the next minor GC.
258
8
     */
259
8
    if (discardBaselineCode) {
260
8
        jitZone()->optimizedStubSpace()->freeAllAfterMinorGC(this);
261
8
        jitZone()->purgeIonCacheIRStubInfo();
262
8
    }
263
8
264
8
    /*
265
8
     * Free all control flow graphs that are cached on BaselineScripts.
266
8
     * Assuming this happens on the main thread and all control flow
267
8
     * graph reads happen on the main thread, this is safe.
268
8
     */
269
8
    jitZone()->cfgSpace()->lifoAlloc().freeAll();
270
8
}
271
272
#ifdef JSGC_HASH_TABLE_CHECKS
273
void
274
JS::Zone::checkUniqueIdTableAfterMovingGC()
275
{
276
    for (auto r = uniqueIds().all(); !r.empty(); r.popFront()) {
277
        js::gc::CheckGCThingAfterMovingGC(r.front().key());
278
    }
279
}
280
#endif
281
282
uint64_t
283
Zone::gcNumber()
284
11.4M
{
285
11.4M
    // Zones in use by exclusive threads are not collected, and threads using
286
11.4M
    // them cannot access the main runtime's gcNumber without racing.
287
11.4M
    return usedByHelperThread() ? 0 : runtimeFromMainThread()->gc.gcNumber();
288
11.4M
}
289
290
js::jit::JitZone*
291
Zone::createJitZone(JSContext* cx)
292
4
{
293
4
    MOZ_ASSERT(!jitZone_);
294
4
295
4
    if (!cx->runtime()->getJitRuntime(cx)) {
296
0
        return nullptr;
297
0
    }
298
4
299
4
    UniquePtr<jit::JitZone> jitZone(cx->new_<js::jit::JitZone>());
300
4
    if (!jitZone) {
301
0
        return nullptr;
302
0
    }
303
4
304
4
    jitZone_ = jitZone.release();
305
4
    return jitZone_;
306
4
}
307
308
bool
309
Zone::hasMarkedRealms()
310
0
{
311
0
    for (RealmsInZoneIter realm(this); !realm.done(); realm.next()) {
312
0
        if (realm->marked()) {
313
0
            return true;
314
0
        }
315
0
    }
316
0
    return false;
317
0
}
318
319
bool
320
Zone::canCollect()
321
918
{
322
918
    // The atoms zone cannot be collected while off-thread parsing is taking
323
918
    // place.
324
918
    if (isAtomsZone()) {
325
300
        return !runtimeFromAnyThread()->hasHelperThreadZones();
326
300
    }
327
618
328
618
    // Zones that will be or are currently used by other threads cannot be
329
618
    // collected.
330
618
    return !createdForHelperThread();
331
618
}
332
333
void
334
Zone::notifyObservingDebuggers()
335
18
{
336
18
    JSRuntime* rt = runtimeFromMainThread();
337
18
    JSContext* cx = rt->mainContextFromOwnThread();
338
18
339
54
    for (RealmsInZoneIter realms(this); !realms.done(); realms.next()) {
340
36
        RootedGlobalObject global(cx, realms->unsafeUnbarrieredMaybeGlobal());
341
36
        if (!global) {
342
0
            continue;
343
0
        }
344
36
345
36
        GlobalObject::DebuggerVector* dbgs = global->getDebuggers();
346
36
        if (!dbgs) {
347
36
            continue;
348
36
        }
349
0
350
0
        for (GlobalObject::DebuggerVector::Range r = dbgs->all(); !r.empty(); r.popFront()) {
351
0
            if (!r.front()->debuggeeIsBeingCollected(rt->gc.majorGCCount())) {
352
#ifdef DEBUG
353
                fprintf(stderr,
354
                        "OOM while notifying observing Debuggers of a GC: The onGarbageCollection\n"
355
                        "hook will not be fired for this GC for some Debuggers!\n");
356
#endif
357
                return;
358
0
            }
359
0
        }
360
0
    }
361
18
}
362
363
bool
364
Zone::isOnList() const
365
18
{
366
18
    return listNext_ != NotOnList;
367
18
}
368
369
Zone*
370
Zone::nextZone() const
371
0
{
372
0
    MOZ_ASSERT(isOnList());
373
0
    return listNext_;
374
0
}
375
376
void
377
Zone::clearTables()
378
0
{
379
0
    MOZ_ASSERT(regExps().empty());
380
0
381
0
    baseShapes().clear();
382
0
    initialShapes().clear();
383
0
}
384
385
void
386
Zone::fixupAfterMovingGC()
387
0
{
388
0
    fixupInitialShapeTable();
389
0
}
390
391
bool
392
Zone::addTypeDescrObject(JSContext* cx, HandleObject obj)
393
0
{
394
0
    // Type descriptor objects are always tenured so we don't need post barriers
395
0
    // on the set.
396
0
    MOZ_ASSERT(!IsInsideNursery(obj));
397
0
398
0
    if (!typeDescrObjects().put(obj)) {
399
0
        ReportOutOfMemory(cx);
400
0
        return false;
401
0
    }
402
0
403
0
    return true;
404
0
}
405
406
void
407
Zone::deleteEmptyCompartment(JS::Compartment* comp)
408
0
{
409
0
    MOZ_ASSERT(comp->zone() == this);
410
0
    MOZ_ASSERT(arenas.checkEmptyArenaLists());
411
0
412
0
    MOZ_ASSERT(compartments().length() == 1);
413
0
    MOZ_ASSERT(compartments()[0] == comp);
414
0
    MOZ_ASSERT(comp->realms().length() == 1);
415
0
416
0
    Realm* realm = comp->realms()[0];
417
0
    FreeOp* fop = runtimeFromMainThread()->defaultFreeOp();
418
0
    realm->destroy(fop);
419
0
    comp->destroy(fop);
420
0
421
0
    compartments().clear();
422
0
}
423
424
void
425
Zone::setHelperThreadOwnerContext(JSContext* cx)
426
0
{
427
0
    MOZ_ASSERT_IF(cx, TlsContext.get() == cx);
428
0
    helperThreadOwnerContext_ = cx;
429
0
}
430
431
bool
432
Zone::ownedByCurrentHelperThread()
433
0
{
434
0
    MOZ_ASSERT(usedByHelperThread());
435
0
    MOZ_ASSERT(TlsContext.get());
436
0
    return helperThreadOwnerContext_ == TlsContext.get();
437
0
}
438
439
void Zone::releaseAtoms()
440
16
{
441
16
    MOZ_ASSERT(hasKeptAtoms());
442
16
443
16
    keepAtomsCount--;
444
16
445
16
    if (!hasKeptAtoms() && purgeAtomsDeferred) {
446
0
        purgeAtomsDeferred = false;
447
0
        purgeAtomCache();
448
0
    }
449
16
}
450
451
void
452
Zone::purgeAtomCacheOrDefer()
453
18
{
454
18
    if (hasKeptAtoms()) {
455
0
        purgeAtomsDeferred = true;
456
0
        return;
457
0
    }
458
18
459
18
    purgeAtomCache();
460
18
}
461
462
void
463
Zone::purgeAtomCache()
464
18
{
465
18
    MOZ_ASSERT(!hasKeptAtoms());
466
18
    MOZ_ASSERT(!purgeAtomsDeferred);
467
18
468
18
    atomCache().clearAndCompact();
469
18
470
18
    // Also purge the dtoa caches so that subsequent lookups populate atom
471
18
    // cache too.
472
54
    for (RealmsInZoneIter r(this); !r.done(); r.next()) {
473
36
        r->dtoaCache.purge();
474
36
    }
475
18
}
476
477
void
478
Zone::traceAtomCache(JSTracer* trc)
479
0
{
480
0
    MOZ_ASSERT(hasKeptAtoms());
481
0
    for (auto r = atomCache().all(); !r.empty(); r.popFront()) {
482
0
        JSAtom* atom = r.front().asPtrUnbarriered();
483
0
        TraceRoot(trc, &atom, "kept atom");
484
0
        MOZ_ASSERT(r.front().asPtrUnbarriered() == atom);
485
0
    }
486
0
}
487
488
void*
489
Zone::onOutOfMemory(js::AllocFunction allocFunc, size_t nbytes, void* reallocPtr)
490
0
{
491
0
    if (!js::CurrentThreadCanAccessRuntime(runtime_)) {
492
0
        return nullptr;
493
0
    }
494
0
    return runtimeFromMainThread()->onOutOfMemory(allocFunc, nbytes, reallocPtr);
495
0
}
496
497
void
498
Zone::reportAllocationOverflow()
499
0
{
500
0
    js::ReportAllocationOverflow(nullptr);
501
0
}
502
503
void
504
JS::Zone::maybeTriggerGCForTooMuchMalloc(js::gc::MemoryCounter& counter, TriggerKind trigger)
505
0
{
506
0
    JSRuntime* rt = runtimeFromAnyThread();
507
0
508
0
    if (!js::CurrentThreadCanAccessRuntime(rt)) {
509
0
        return;
510
0
    }
511
0
512
0
    bool wouldInterruptGC = rt->gc.isIncrementalGCInProgress() && !isCollecting();
513
0
    if (wouldInterruptGC && !counter.shouldResetIncrementalGC(rt->gc.tunables)) {
514
0
        return;
515
0
    }
516
0
517
0
    if (!rt->gc.triggerZoneGC(this, JS::gcreason::TOO_MUCH_MALLOC,
518
0
                              counter.bytes(), counter.maxBytes()))
519
0
    {
520
0
        return;
521
0
    }
522
0
523
0
    counter.recordTrigger(trigger);
524
0
}
525
526
ZoneList::ZoneList()
527
  : head(nullptr), tail(nullptr)
528
42
{}
529
530
ZoneList::ZoneList(Zone* zone)
531
  : head(zone), tail(zone)
532
18
{
533
18
    MOZ_RELEASE_ASSERT(!zone->isOnList());
534
18
    zone->listNext_ = nullptr;
535
18
}
536
537
ZoneList::~ZoneList()
538
54
{
539
54
    MOZ_ASSERT(isEmpty());
540
54
}
541
542
void
543
ZoneList::check() const
544
126
{
545
#ifdef DEBUG
546
    MOZ_ASSERT((head == nullptr) == (tail == nullptr));
547
    if (!head) {
548
        return;
549
    }
550
551
    Zone* zone = head;
552
    for (;;) {
553
        MOZ_ASSERT(zone && zone->isOnList());
554
        if  (zone == tail)
555
            break;
556
        zone = zone->listNext_;
557
    }
558
    MOZ_ASSERT(!zone->listNext_);
559
#endif
560
}
561
562
bool
563
ZoneList::isEmpty() const
564
72
{
565
72
    return head == nullptr;
566
72
}
567
568
Zone*
569
ZoneList::front() const
570
0
{
571
0
    MOZ_ASSERT(!isEmpty());
572
0
    MOZ_ASSERT(head->isOnList());
573
0
    return head;
574
0
}
575
576
void
577
ZoneList::append(Zone* zone)
578
18
{
579
18
    ZoneList singleZone(zone);
580
18
    transferFrom(singleZone);
581
18
}
582
583
void
584
ZoneList::transferFrom(ZoneList& other)
585
54
{
586
54
    check();
587
54
    other.check();
588
54
    MOZ_ASSERT(tail != other.tail);
589
54
590
54
    if (tail) {
591
0
        tail->listNext_ = other.head;
592
54
    } else {
593
54
        head = other.head;
594
54
    }
595
54
    tail = other.tail;
596
54
597
54
    other.head = nullptr;
598
54
    other.tail = nullptr;
599
54
}
600
601
Zone*
602
ZoneList::removeFront()
603
18
{
604
18
    MOZ_ASSERT(!isEmpty());
605
18
    check();
606
18
607
18
    Zone* front = head;
608
18
    head = head->listNext_;
609
18
    if (!head) {
610
18
        tail = nullptr;
611
18
    }
612
18
613
18
    front->listNext_ = Zone::NotOnList;
614
18
615
18
    return front;
616
18
}
617
618
void
619
ZoneList::clear()
620
0
{
621
0
    while (!isEmpty()) {
622
0
        removeFront();
623
0
    }
624
0
}
625
626
JS_PUBLIC_API(void)
627
JS::shadow::RegisterWeakCache(JS::Zone* zone, detail::WeakCacheBase* cachep)
628
67
{
629
67
    zone->registerWeakCache(cachep);
630
67
}