diff options
Diffstat (limited to 'js/src/gc/Heap.cpp')
-rw-r--r-- | js/src/gc/Heap.cpp | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/js/src/gc/Heap.cpp b/js/src/gc/Heap.cpp new file mode 100644 index 0000000000..fdf24fbb7c --- /dev/null +++ b/js/src/gc/Heap.cpp @@ -0,0 +1,635 @@ +/* -*- 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/. */ + +/* + * Tenured heap management. + * + * This file contains method definitions for the following classes for code that + * is not specific to a particular phase of GC: + * + * - Arena + * - ArenaList + * - FreeLists + * - ArenaLists + * - TenuredChunk + * - ChunkPool + */ + +#include "gc/Heap-inl.h" + +#include "gc/GCLock.h" +#include "gc/Memory.h" +#include "jit/Assembler.h" +#include "vm/BigIntType.h" +#include "vm/RegExpShared.h" +#include "vm/Scope.h" + +#include "gc/ArenaList-inl.h" +#include "gc/PrivateIterators-inl.h" + +using namespace js; +using namespace js::gc; + +// Check that reserved bits of a Cell are compatible with our typical allocators +// since most derived classes will store a pointer in the first word. +static const size_t MinFirstWordAlignment = 1u << CellFlagBitsReservedForGC; +static_assert(js::detail::LIFO_ALLOC_ALIGN >= MinFirstWordAlignment, + "CellFlagBitsReservedForGC should support LifoAlloc"); +static_assert(CellAlignBytes >= MinFirstWordAlignment, + "CellFlagBitsReservedForGC should support gc::Cell"); +static_assert(js::jit::CodeAlignment >= MinFirstWordAlignment, + "CellFlagBitsReservedForGC should support JIT code"); +static_assert(js::gc::JSClassAlignBytes >= MinFirstWordAlignment, + "CellFlagBitsReservedForGC should support JSClass pointers"); +static_assert(js::ScopeDataAlignBytes >= MinFirstWordAlignment, + "CellFlagBitsReservedForGC should support scope data pointers"); + +#define CHECK_THING_SIZE(allocKind, traceKind, type, sizedType, bgFinal, \ + nursery, compact) \ + static_assert(sizeof(sizedType) >= SortedArenaList::MinThingSize, \ + #sizedType " is smaller than SortedArenaList::MinThingSize!"); \ + static_assert(sizeof(sizedType) >= sizeof(FreeSpan), \ + #sizedType " is smaller than FreeSpan"); \ + static_assert(sizeof(sizedType) % CellAlignBytes == 0, \ + "Size of " #sizedType " is not a multiple of CellAlignBytes"); \ + static_assert(sizeof(sizedType) >= MinCellSize, \ + "Size of " #sizedType " is smaller than the minimum size"); +FOR_EACH_ALLOCKIND(CHECK_THING_SIZE); +#undef CHECK_THING_SIZE + +FreeSpan FreeLists::emptySentinel; + +template <typename T> +struct ArenaLayout { + static constexpr size_t thingSize() { return sizeof(T); } + static constexpr size_t thingsPerArena() { + return (ArenaSize - ArenaHeaderSize) / thingSize(); + } + static constexpr size_t firstThingOffset() { + return ArenaSize - thingSize() * thingsPerArena(); + } +}; + +const uint8_t Arena::ThingSizes[] = { +#define EXPAND_THING_SIZE(_1, _2, _3, sizedType, _4, _5, _6) \ + ArenaLayout<sizedType>::thingSize(), + FOR_EACH_ALLOCKIND(EXPAND_THING_SIZE) +#undef EXPAND_THING_SIZE +}; + +const uint8_t Arena::FirstThingOffsets[] = { +#define EXPAND_FIRST_THING_OFFSET(_1, _2, _3, sizedType, _4, _5, _6) \ + ArenaLayout<sizedType>::firstThingOffset(), + FOR_EACH_ALLOCKIND(EXPAND_FIRST_THING_OFFSET) +#undef EXPAND_FIRST_THING_OFFSET +}; + +const uint8_t Arena::ThingsPerArena[] = { +#define EXPAND_THINGS_PER_ARENA(_1, _2, _3, sizedType, _4, _5, _6) \ + ArenaLayout<sizedType>::thingsPerArena(), + FOR_EACH_ALLOCKIND(EXPAND_THINGS_PER_ARENA) +#undef EXPAND_THINGS_PER_ARENA +}; + +void Arena::unmarkAll() { + MarkBitmapWord* arenaBits = chunk()->markBits.arenaBits(this); + for (size_t i = 0; i < ArenaBitmapWords; i++) { + arenaBits[i] = 0; + } +} + +void Arena::unmarkPreMarkedFreeCells() { + for (ArenaFreeCellIter cell(this); !cell.done(); cell.next()) { + MOZ_ASSERT(cell->isMarkedBlack()); + cell->unmark(); + } +} + +#ifdef DEBUG + +void Arena::checkNoMarkedFreeCells() { + for (ArenaFreeCellIter cell(this); !cell.done(); cell.next()) { + MOZ_ASSERT(!cell->isMarkedAny()); + } +} + +void Arena::checkAllCellsMarkedBlack() { + for (ArenaCellIter cell(this); !cell.done(); cell.next()) { + MOZ_ASSERT(cell->isMarkedBlack()); + } +} + +#endif + +#if defined(DEBUG) || defined(JS_GC_ZEAL) +void Arena::checkNoMarkedCells() { + for (ArenaCellIter cell(this); !cell.done(); cell.next()) { + MOZ_ASSERT(!cell->isMarkedAny()); + } +} +#endif + +/* static */ +void Arena::staticAsserts() { + static_assert(size_t(AllocKind::LIMIT) <= 255, + "All AllocKinds and AllocKind::LIMIT must fit in a uint8_t."); + static_assert(std::size(ThingSizes) == AllocKindCount, + "We haven't defined all thing sizes."); + static_assert(std::size(FirstThingOffsets) == AllocKindCount, + "We haven't defined all offsets."); + static_assert(std::size(ThingsPerArena) == AllocKindCount, + "We haven't defined all counts."); +} + +/* static */ +void Arena::checkLookupTables() { +#ifdef DEBUG + for (size_t i = 0; i < AllocKindCount; i++) { + MOZ_ASSERT( + FirstThingOffsets[i] + ThingsPerArena[i] * ThingSizes[i] == ArenaSize, + "Inconsistent arena lookup table data"); + } +#endif +} + +#ifdef DEBUG +void js::gc::ArenaList::dump() { + fprintf(stderr, "ArenaList %p:", this); + if (cursorp_ == &head_) { + fprintf(stderr, " *"); + } + for (Arena* arena = head(); arena; arena = arena->next) { + fprintf(stderr, " %p", arena); + if (cursorp_ == &arena->next) { + fprintf(stderr, " *"); + } + } + fprintf(stderr, "\n"); +} +#endif + +Arena* ArenaList::removeRemainingArenas(Arena** arenap) { + // This is only ever called to remove arenas that are after the cursor, so + // we don't need to update it. +#ifdef DEBUG + for (Arena* arena = *arenap; arena; arena = arena->next) { + MOZ_ASSERT(cursorp_ != &arena->next); + } +#endif + Arena* remainingArenas = *arenap; + *arenap = nullptr; + check(); + return remainingArenas; +} + +FreeLists::FreeLists() { + for (auto i : AllAllocKinds()) { + freeLists_[i] = &emptySentinel; + } +} + +ArenaLists::ArenaLists(Zone* zone) + : zone_(zone), + incrementalSweptArenaKind(AllocKind::LIMIT), + gcCompactPropMapArenasToUpdate(nullptr), + gcNormalPropMapArenasToUpdate(nullptr), + savedEmptyArenas(nullptr) { + for (auto i : AllAllocKinds()) { + concurrentUse(i) = ConcurrentUse::None; + } +} + +void ReleaseArenas(JSRuntime* rt, Arena* arena, const AutoLockGC& lock) { + Arena* next; + for (; arena; arena = next) { + next = arena->next; + rt->gc.releaseArena(arena, lock); + } +} + +void ReleaseArenaList(JSRuntime* rt, ArenaList& arenaList, + const AutoLockGC& lock) { + ReleaseArenas(rt, arenaList.head(), lock); + arenaList.clear(); +} + +ArenaLists::~ArenaLists() { + AutoLockGC lock(runtime()); + + for (auto i : AllAllocKinds()) { + /* + * We can only call this during the shutdown after the last GC when + * the background finalization is disabled. + */ + MOZ_ASSERT(concurrentUse(i) == ConcurrentUse::None); + ReleaseArenaList(runtime(), arenaList(i), lock); + } + ReleaseArenaList(runtime(), incrementalSweptArenas.ref(), lock); + + ReleaseArenas(runtime(), savedEmptyArenas, lock); +} + +void ArenaLists::moveArenasToCollectingLists() { + checkEmptyFreeLists(); + for (AllocKind kind : AllAllocKinds()) { + MOZ_ASSERT(collectingArenaList(kind).isEmpty()); + collectingArenaList(kind) = std::move(arenaList(kind)); + MOZ_ASSERT(arenaList(kind).isEmpty()); + } +} + +void ArenaLists::mergeArenasFromCollectingLists() { + for (AllocKind kind : AllAllocKinds()) { + collectingArenaList(kind).insertListWithCursorAtEnd(arenaList(kind)); + arenaList(kind) = std::move(collectingArenaList(kind)); + MOZ_ASSERT(collectingArenaList(kind).isEmpty()); + } +} + +Arena* ArenaLists::takeSweptEmptyArenas() { + Arena* arenas = savedEmptyArenas; + savedEmptyArenas = nullptr; + return arenas; +} + +void ArenaLists::setIncrementalSweptArenas(AllocKind kind, + SortedArenaList& arenas) { + incrementalSweptArenaKind = kind; + incrementalSweptArenas.ref().clear(); + incrementalSweptArenas = arenas.toArenaList(); +} + +void ArenaLists::clearIncrementalSweptArenas() { + incrementalSweptArenaKind = AllocKind::LIMIT; + incrementalSweptArenas.ref().clear(); +} + +void ArenaLists::checkGCStateNotInUse() { + // Called before and after collection to check the state is as expected. +#ifdef DEBUG + checkSweepStateNotInUse(); + for (auto i : AllAllocKinds()) { + MOZ_ASSERT(collectingArenaList(i).isEmpty()); + } +#endif +} + +void ArenaLists::checkSweepStateNotInUse() { +#ifdef DEBUG + checkNoArenasToUpdate(); + MOZ_ASSERT(incrementalSweptArenaKind == AllocKind::LIMIT); + MOZ_ASSERT(incrementalSweptArenas.ref().isEmpty()); + MOZ_ASSERT(!savedEmptyArenas); + for (auto i : AllAllocKinds()) { + MOZ_ASSERT(concurrentUse(i) == ConcurrentUse::None); + } +#endif +} + +void ArenaLists::checkNoArenasToUpdate() { + MOZ_ASSERT(!gcCompactPropMapArenasToUpdate); + MOZ_ASSERT(!gcNormalPropMapArenasToUpdate); +} + +void ArenaLists::checkNoArenasToUpdateForKind(AllocKind kind) { +#ifdef DEBUG + switch (kind) { + case AllocKind::COMPACT_PROP_MAP: + MOZ_ASSERT(!gcCompactPropMapArenasToUpdate); + break; + case AllocKind::NORMAL_PROP_MAP: + MOZ_ASSERT(!gcNormalPropMapArenasToUpdate); + break; + default: + break; + } +#endif +} + +inline bool TenuredChunk::canDecommitPage(size_t pageIndex) const { + if (decommittedPages[pageIndex]) { + return false; + } + + size_t arenaIndex = pageIndex * ArenasPerPage; + for (size_t i = 0; i < ArenasPerPage; i++) { + if (!freeCommittedArenas[arenaIndex + i]) { + return false; + } + } + + return true; +} + +void TenuredChunk::decommitFreeArenas(GCRuntime* gc, const bool& cancel, + AutoLockGC& lock) { + MOZ_ASSERT(DecommitEnabled()); + + for (size_t i = 0; i < PagesPerChunk; i++) { + if (cancel) { + break; + } + + if (canDecommitPage(i) && !decommitOneFreePage(gc, i, lock)) { + break; + } + } +} + +void TenuredChunk::recycleArena(Arena* arena, SortedArenaList& dest, + size_t thingsPerArena) { + arena->setAsFullyUnused(); + dest.insertAt(arena, thingsPerArena); +} + +void TenuredChunk::releaseArena(GCRuntime* gc, Arena* arena, + const AutoLockGC& lock) { + MOZ_ASSERT(!arena->allocated()); + MOZ_ASSERT(!freeCommittedArenas[arenaIndex(arena)]); + + freeCommittedArenas[arenaIndex(arena)] = true; + ++info.numArenasFreeCommitted; + ++info.numArenasFree; + gc->updateOnArenaFree(); + + verify(); + + updateChunkListAfterFree(gc, 1, lock); +} + +bool TenuredChunk::decommitOneFreePage(GCRuntime* gc, size_t pageIndex, + AutoLockGC& lock) { + MOZ_ASSERT(DecommitEnabled()); + MOZ_ASSERT(canDecommitPage(pageIndex)); + MOZ_ASSERT(info.numArenasFreeCommitted >= ArenasPerPage); + + // Temporarily mark the page as allocated while we decommit. + for (size_t i = 0; i < ArenasPerPage; i++) { + size_t arenaIndex = pageIndex * ArenasPerPage + i; + MOZ_ASSERT(freeCommittedArenas[arenaIndex]); + freeCommittedArenas[arenaIndex] = false; + } + info.numArenasFreeCommitted -= ArenasPerPage; + info.numArenasFree -= ArenasPerPage; + updateChunkListAfterAlloc(gc, lock); + + verify(); + + bool ok; + { + AutoUnlockGC unlock(lock); + ok = !oom::ShouldFailWithOOM() && + MarkPagesUnusedSoft(pageAddress(pageIndex), PageSize); + } + + // Mark the page as decommited if successful or restore the original free + // state. + if (ok) { + decommittedPages[pageIndex] = true; + } else { + for (size_t i = 0; i < ArenasPerPage; i++) { + size_t arenaIndex = pageIndex * ArenasPerPage + i; + MOZ_ASSERT(!freeCommittedArenas[arenaIndex]); + freeCommittedArenas[arenaIndex] = true; + } + info.numArenasFreeCommitted += ArenasPerPage; + } + + info.numArenasFree += ArenasPerPage; + updateChunkListAfterFree(gc, ArenasPerPage, lock); + + verify(); + + return ok; +} + +void TenuredChunk::decommitFreeArenasWithoutUnlocking(const AutoLockGC& lock) { + MOZ_ASSERT(DecommitEnabled()); + + for (size_t i = 0; i < PagesPerChunk; i++) { + if (!canDecommitPage(i)) { + continue; + } + + MOZ_ASSERT(!decommittedPages[i]); + MOZ_ASSERT(info.numArenasFreeCommitted >= ArenasPerPage); + + if (js::oom::ShouldFailWithOOM() || + !MarkPagesUnusedSoft(pageAddress(i), SystemPageSize())) { + break; + } + + decommittedPages[i] = true; + for (size_t j = 0; j < ArenasPerPage; ++j) { + size_t arenaIndex = i * ArenasPerPage + j; + MOZ_ASSERT(freeCommittedArenas[arenaIndex]); + freeCommittedArenas[arenaIndex] = false; + } + info.numArenasFreeCommitted -= ArenasPerPage; + } + + verify(); +} + +void TenuredChunk::updateChunkListAfterAlloc(GCRuntime* gc, + const AutoLockGC& lock) { + if (MOZ_UNLIKELY(!hasAvailableArenas())) { + gc->availableChunks(lock).remove(this); + gc->fullChunks(lock).push(this); + } +} + +void TenuredChunk::updateChunkListAfterFree(GCRuntime* gc, size_t numArenasFree, + const AutoLockGC& lock) { + if (info.numArenasFree == numArenasFree) { + gc->fullChunks(lock).remove(this); + gc->availableChunks(lock).push(this); + } else if (!unused()) { + MOZ_ASSERT(gc->availableChunks(lock).contains(this)); + } else { + MOZ_ASSERT(unused()); + gc->availableChunks(lock).remove(this); + gc->recycleChunk(this, lock); + } +} + +TenuredChunk* ChunkPool::pop() { + MOZ_ASSERT(bool(head_) == bool(count_)); + if (!count_) { + return nullptr; + } + return remove(head_); +} + +void ChunkPool::push(TenuredChunk* chunk) { + MOZ_ASSERT(!chunk->info.next); + MOZ_ASSERT(!chunk->info.prev); + + chunk->info.next = head_; + if (head_) { + head_->info.prev = chunk; + } + head_ = chunk; + ++count_; +} + +TenuredChunk* ChunkPool::remove(TenuredChunk* chunk) { + MOZ_ASSERT(count_ > 0); + MOZ_ASSERT(contains(chunk)); + + if (head_ == chunk) { + head_ = chunk->info.next; + } + if (chunk->info.prev) { + chunk->info.prev->info.next = chunk->info.next; + } + if (chunk->info.next) { + chunk->info.next->info.prev = chunk->info.prev; + } + chunk->info.next = chunk->info.prev = nullptr; + --count_; + + return chunk; +} + +// We could keep the chunk pool sorted, but that's likely to be more expensive. +// This sort is nlogn, but keeping it sorted is likely to be m*n, with m being +// the number of operations (likely higher than n). +void ChunkPool::sort() { + // Only sort if the list isn't already sorted. + if (!isSorted()) { + head_ = mergeSort(head(), count()); + + // Fixup prev pointers. + TenuredChunk* prev = nullptr; + for (TenuredChunk* cur = head_; cur; cur = cur->info.next) { + cur->info.prev = prev; + prev = cur; + } + } + + MOZ_ASSERT(verify()); + MOZ_ASSERT(isSorted()); +} + +TenuredChunk* ChunkPool::mergeSort(TenuredChunk* list, size_t count) { + MOZ_ASSERT(bool(list) == bool(count)); + + if (count < 2) { + return list; + } + + size_t half = count / 2; + + // Split; + TenuredChunk* front = list; + TenuredChunk* back; + { + TenuredChunk* cur = list; + for (size_t i = 0; i < half - 1; i++) { + MOZ_ASSERT(cur); + cur = cur->info.next; + } + back = cur->info.next; + cur->info.next = nullptr; + } + + front = mergeSort(front, half); + back = mergeSort(back, count - half); + + // Merge + list = nullptr; + TenuredChunk** cur = &list; + while (front || back) { + if (!front) { + *cur = back; + break; + } + if (!back) { + *cur = front; + break; + } + + // Note that the sort is stable due to the <= here. Nothing depends on + // this but it could. + if (front->info.numArenasFree <= back->info.numArenasFree) { + *cur = front; + front = front->info.next; + cur = &(*cur)->info.next; + } else { + *cur = back; + back = back->info.next; + cur = &(*cur)->info.next; + } + } + + return list; +} + +bool ChunkPool::isSorted() const { + uint32_t last = 1; + for (TenuredChunk* cursor = head_; cursor; cursor = cursor->info.next) { + if (cursor->info.numArenasFree < last) { + return false; + } + last = cursor->info.numArenasFree; + } + return true; +} + +#ifdef DEBUG + +bool ChunkPool::contains(TenuredChunk* chunk) const { + verify(); + for (TenuredChunk* cursor = head_; cursor; cursor = cursor->info.next) { + if (cursor == chunk) { + return true; + } + } + return false; +} + +bool ChunkPool::verify() const { + MOZ_ASSERT(bool(head_) == bool(count_)); + uint32_t count = 0; + for (TenuredChunk* cursor = head_; cursor; + cursor = cursor->info.next, ++count) { + MOZ_ASSERT_IF(cursor->info.prev, cursor->info.prev->info.next == cursor); + MOZ_ASSERT_IF(cursor->info.next, cursor->info.next->info.prev == cursor); + } + MOZ_ASSERT(count_ == count); + return true; +} + +void ChunkPool::verifyChunks() const { + for (TenuredChunk* chunk = head_; chunk; chunk = chunk->info.next) { + chunk->verify(); + } +} + +void TenuredChunk::verify() const { + MOZ_ASSERT(info.numArenasFree <= ArenasPerChunk); + MOZ_ASSERT(info.numArenasFreeCommitted <= info.numArenasFree); + + size_t decommittedCount = decommittedPages.Count() * ArenasPerPage; + size_t freeCommittedCount = freeCommittedArenas.Count(); + size_t freeCount = freeCommittedCount + decommittedCount; + + MOZ_ASSERT(freeCount == info.numArenasFree); + MOZ_ASSERT(freeCommittedCount == info.numArenasFreeCommitted); + + for (size_t i = 0; i < ArenasPerChunk; ++i) { + MOZ_ASSERT(!(decommittedPages[pageIndex(i)] && freeCommittedArenas[i])); + MOZ_ASSERT_IF(freeCommittedArenas[i], !arenas[i].allocated()); + } +} + +#endif + +void ChunkPool::Iter::next() { + MOZ_ASSERT(!done()); + current_ = current_->info.next; +} |