/* -*- 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/. */ #ifndef gc_ArenaList_inl_h #define gc_ArenaList_inl_h #include "gc/ArenaList.h" #include "gc/Heap.h" #include "gc/Zone.h" inline js::gc::ArenaList::ArenaList() { clear(); } inline js::gc::ArenaList::ArenaList(ArenaList&& other) { moveFrom(other); } inline js::gc::ArenaList::~ArenaList() { MOZ_ASSERT(isEmpty()); } void js::gc::ArenaList::moveFrom(ArenaList& other) { other.check(); head_ = other.head_; cursorp_ = other.isCursorAtHead() ? &head_ : other.cursorp_; other.clear(); check(); } js::gc::ArenaList& js::gc::ArenaList::operator=(ArenaList&& other) { MOZ_ASSERT(isEmpty()); moveFrom(other); return *this; } inline js::gc::ArenaList::ArenaList(Arena* head, Arena* arenaBeforeCursor) : head_(head), cursorp_(arenaBeforeCursor ? &arenaBeforeCursor->next : &head_) { check(); } // This does checking just of |head_| and |cursorp_|. void js::gc::ArenaList::check() const { #ifdef DEBUG // If the list is empty, it must have this form. MOZ_ASSERT_IF(!head_, cursorp_ == &head_); // If there's an arena following the cursor, it must not be full. Arena* cursor = *cursorp_; MOZ_ASSERT_IF(cursor, cursor->hasFreeThings()); #endif } void js::gc::ArenaList::clear() { head_ = nullptr; cursorp_ = &head_; check(); } bool js::gc::ArenaList::isEmpty() const { check(); return !head_; } js::gc::Arena* js::gc::ArenaList::head() const { check(); return head_; } bool js::gc::ArenaList::isCursorAtHead() const { check(); return cursorp_ == &head_; } bool js::gc::ArenaList::isCursorAtEnd() const { check(); return !*cursorp_; } js::gc::Arena* js::gc::ArenaList::arenaAfterCursor() const { check(); return *cursorp_; } js::gc::Arena* js::gc::ArenaList::takeNextArena() { check(); Arena* arena = *cursorp_; if (!arena) { return nullptr; } cursorp_ = &arena->next; check(); return arena; } void js::gc::ArenaList::insertAtCursor(Arena* a) { check(); a->next = *cursorp_; *cursorp_ = a; // At this point, the cursor is sitting before |a|. Move it after |a| // if necessary. if (!a->hasFreeThings()) { cursorp_ = &a->next; } check(); } void js::gc::ArenaList::insertBeforeCursor(Arena* a) { check(); a->next = *cursorp_; *cursorp_ = a; cursorp_ = &a->next; check(); } js::gc::ArenaList& js::gc::ArenaList::insertListWithCursorAtEnd( ArenaList& other) { check(); other.check(); MOZ_ASSERT(other.isCursorAtEnd()); if (other.isEmpty()) { return *this; } // Insert the full arenas of |other| after those of |this|. *other.cursorp_ = *cursorp_; *cursorp_ = other.head_; cursorp_ = other.cursorp_; check(); other.clear(); return *this; } js::gc::Arena* js::gc::ArenaList::takeFirstArena() { check(); Arena* arena = head_; if (!arena) { return nullptr; } head_ = arena->next; arena->next = nullptr; if (cursorp_ == &arena->next) { cursorp_ = &head_; } check(); return arena; } js::gc::SortedArenaList::SortedArenaList(js::gc::AllocKind allocKind) : thingsPerArena_(Arena::thingsPerArena(allocKind)) { #ifdef DEBUG MOZ_ASSERT(thingsPerArena_ <= MaxThingsPerArena); MOZ_ASSERT(emptyIndex() < BucketCount); allocKind_ = allocKind; #endif } size_t js::gc::SortedArenaList::index(size_t nfree, bool* frontOut) const { // Get the bucket index to use for arenas with |nfree| free things and set // |frontOut| to indicate whether to prepend or append to the bucket. MOZ_ASSERT(nfree <= thingsPerArena_); // Full arenas go in the first bucket on their own. if (nfree == 0) { *frontOut = false; return 0; } // Empty arenas go in the last bucket on their own. if (nfree == thingsPerArena_) { *frontOut = false; return emptyIndex(); } // All other arenas are alternately added to the front and back of successive // buckets as |nfree| increases. *frontOut = (nfree % 2) != 0; size_t index = (nfree + 1) / 2; MOZ_ASSERT(index != 0); MOZ_ASSERT(index != emptyIndex()); return index; } size_t js::gc::SortedArenaList::emptyIndex() const { // Get the bucket index to use for empty arenas. This must have its own // bucket so they can be removed with extractEmptyTo. return bucketsUsed() - 1; } size_t js::gc::SortedArenaList::bucketsUsed() const { // Get the total number of buckets used for the current alloc kind. return HowMany(thingsPerArena_ - 1, 2) + 2; } void js::gc::SortedArenaList::insertAt(Arena* arena, size_t nfree) { MOZ_ASSERT(!isConvertedToArenaList); MOZ_ASSERT(nfree <= thingsPerArena_); bool front; size_t i = index(nfree, &front); MOZ_ASSERT(i < BucketCount); if (front) { buckets[i].pushFront(arena); } else { buckets[i].pushBack(arena); } } void js::gc::SortedArenaList::extractEmptyTo(Arena** destListHeadPtr) { MOZ_ASSERT(!isConvertedToArenaList); check(); Bucket& bucket = buckets[emptyIndex()]; if (!bucket.isEmpty()) { Arena* tail = *destListHeadPtr; Arena* bucketLast = bucket.last(); *destListHeadPtr = bucket.release(); bucketLast->next = tail; } MOZ_ASSERT(bucket.isEmpty()); } js::gc::ArenaList js::gc::SortedArenaList::convertToArenaList( Arena* maybeBucketLastOut[BucketCount]) { #ifdef DEBUG MOZ_ASSERT(!isConvertedToArenaList); isConvertedToArenaList = true; check(); #endif if (maybeBucketLastOut) { for (size_t i = 0; i < BucketCount; i++) { maybeBucketLastOut[i] = buckets[i].last(); } } // The cursor of the returned ArenaList needs to be between the last full // arena and the first arena with space. Record that here. Arena* lastFullArena = buckets[0].last(); Bucket result; for (size_t i = 0; i < bucketsUsed(); ++i) { result.append(std::move(buckets[i])); } return ArenaList(result.release(), lastFullArena); } void js::gc::SortedArenaList::restoreFromArenaList( ArenaList& list, Arena* bucketLast[BucketCount]) { #ifdef DEBUG MOZ_ASSERT(isConvertedToArenaList); isConvertedToArenaList = false; #endif // Group the ArenaList elements into SinglyLinkedList buckets, where the // boundaries between buckets are retrieved from |bucketLast|. Arena* remaining = list.head(); list.clear(); for (size_t i = 0; i < bucketsUsed(); i++) { MOZ_ASSERT(buckets[i].isEmpty()); if (bucketLast[i]) { Arena* first = remaining; Arena* last = bucketLast[i]; remaining = last->next; last->next = nullptr; new (&buckets[i]) Bucket(first, last); } } check(); } void js::gc::SortedArenaList::check() const { #ifdef DEBUG const auto& fullBucket = buckets[0]; for (auto arena = fullBucket.iter(); !arena.done(); arena.next()) { MOZ_ASSERT(arena->getAllocKind() == allocKind()); MOZ_ASSERT(!arena->hasFreeThings()); } for (size_t i = 1; i < emptyIndex(); i++) { const auto& bucket = buckets[i]; size_t lastFree = 0; for (auto arena = bucket.iter(); !arena.done(); arena.next()) { MOZ_ASSERT(arena->getAllocKind() == allocKind()); size_t nfree = arena->countFreeCells(); MOZ_ASSERT(nfree == i * 2 - 1 || nfree == i * 2); MOZ_ASSERT(nfree >= lastFree); lastFree = nfree; } } const auto& emptyBucket = buckets[emptyIndex()]; for (auto arena = emptyBucket.iter(); !arena.done(); arena.next()) { MOZ_ASSERT(arena->getAllocKind() == allocKind()); MOZ_ASSERT(arena->isEmpty()); } for (size_t i = emptyIndex() + 1; i < BucketCount; i++) { MOZ_ASSERT(buckets[i].isEmpty()); } #endif } #ifdef DEBUG bool js::gc::FreeLists::allEmpty() const { for (auto i : AllAllocKinds()) { if (!isEmpty(i)) { return false; } } return true; } bool js::gc::FreeLists::isEmpty(AllocKind kind) const { return freeLists_[kind]->isEmpty(); } #endif void js::gc::FreeLists::clear() { for (auto i : AllAllocKinds()) { #ifdef DEBUG auto old = freeLists_[i]; if (!old->isEmpty()) { old->getArena()->checkNoMarkedFreeCells(); } #endif freeLists_[i] = &emptySentinel; } } js::gc::TenuredCell* js::gc::FreeLists::allocate(AllocKind kind) { return freeLists_[kind]->allocate(Arena::thingSize(kind)); } void js::gc::FreeLists::unmarkPreMarkedFreeCells(AllocKind kind) { FreeSpan* freeSpan = freeLists_[kind]; if (!freeSpan->isEmpty()) { freeSpan->getArena()->unmarkPreMarkedFreeCells(); } } JSRuntime* js::gc::ArenaLists::runtime() { return zone_->runtimeFromMainThread(); } JSRuntime* js::gc::ArenaLists::runtimeFromAnyThread() { return zone_->runtimeFromAnyThread(); } js::gc::Arena* js::gc::ArenaLists::getFirstArena(AllocKind thingKind) const { return arenaList(thingKind).head(); } js::gc::Arena* js::gc::ArenaLists::getFirstCollectingArena( AllocKind thingKind) const { return collectingArenaList(thingKind).head(); } js::gc::Arena* js::gc::ArenaLists::getArenaAfterCursor( AllocKind thingKind) const { return arenaList(thingKind).arenaAfterCursor(); } bool js::gc::ArenaLists::arenaListsAreEmpty() const { for (auto i : AllAllocKinds()) { /* * The arena cannot be empty if the background finalization is not yet * done. */ if (concurrentUse(i) == ConcurrentUse::BackgroundFinalize) { return false; } if (!arenaList(i).isEmpty()) { return false; } } return true; } bool js::gc::ArenaLists::doneBackgroundFinalize(AllocKind kind) const { return concurrentUse(kind) != ConcurrentUse::BackgroundFinalize; } bool js::gc::ArenaLists::needBackgroundFinalizeWait(AllocKind kind) const { return concurrentUse(kind) == ConcurrentUse::BackgroundFinalize; } void js::gc::ArenaLists::clearFreeLists() { freeLists().clear(); } MOZ_ALWAYS_INLINE js::gc::TenuredCell* js::gc::ArenaLists::allocateFromFreeList( AllocKind thingKind) { return freeLists().allocate(thingKind); } void js::gc::ArenaLists::unmarkPreMarkedFreeCells() { for (auto i : AllAllocKinds()) { freeLists().unmarkPreMarkedFreeCells(i); } } void js::gc::ArenaLists::checkEmptyFreeLists() { MOZ_ASSERT(freeLists().allEmpty()); } void js::gc::ArenaLists::checkEmptyArenaLists() { #ifdef DEBUG for (auto i : AllAllocKinds()) { checkEmptyArenaList(i); } #endif } #endif // gc_ArenaList_inl_h