Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  Heap.cpp   Sprache: C

 
/* -*- 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
 *  - ArenaChunk
 *  - 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) >= 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
};

bool Arena::allocated() const {
  size_t arenaIndex = ArenaChunk::arenaIndex(this);
  size_t pageIndex = ArenaChunk::arenaToPageIndex(arenaIndex);
  bool result = !chunk()->decommittedPages[pageIndex] &&
                !chunk()->freeCommittedArenas[arenaIndex] &&
                IsValidAllocKind(allocKind);
  MOZ_ASSERT_IF(result, zone_);
  MOZ_ASSERT_IF(result, (uintptr_t(zone_) & 7) == 0);
  return result;
}

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_assert(ArenaZoneOffset == offsetof(Arena, zone_),
                "The hardcoded API zone offset must match the actual offset.");
  static_assert(sizeof(Arena) == ArenaSize,
                "ArenaSize must match the actual size of the Arena structure.");
  static_assert(
      offsetof(Arena, data) == ArenaHeaderSize,
      "ArenaHeaderSize must match the actual size of the header fields.");
}

/* 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:\n"this);
  for (auto arena = iter(); !arena.done(); arena.next()) {
    fprintf(stderr, " %p %zu", arena.get(), arena->countFreeCells());
    if (arena->isEmpty()) {
      fprintf(stderr, " (empty)");
    }
    if (arena->isFull()) {
      fprintf(stderr, " (full)");
    }
    fprintf(stderr, "\n");
  }
}
#endif

AutoGatherSweptArenas::AutoGatherSweptArenas(JS::Zone* zone, AllocKind kind) {
  GCRuntime& gc = zone->runtimeFromMainThread()->gc;
  sortedList = gc.maybeGetForegroundFinalizedArenas(zone, kind);
  if (!sortedList) {
    return;
  }

  // Link individual sorted arena lists together for iteration, saving the
  // internal state so we can restore it later.
  linked = sortedList->convertToArenaList(bucketLastPointers);
}

AutoGatherSweptArenas::~AutoGatherSweptArenas() {
  if (!sortedList) {
    MOZ_ASSERT(linked.isEmpty());
    return;
  }

  sortedList->restoreFromArenaList(linked, bucketLastPointers);
}

FreeLists::FreeLists() {
  for (auto i : AllAllocKinds()) {
    freeLists_[i] = &emptySentinel;
  }
}

ArenaLists::ArenaLists(Zone* zone)
    : zone_(zone),
      gcCompactPropMapArenasToUpdate(nullptr),
      gcNormalPropMapArenasToUpdate(nullptr),
      savedEmptyArenas(nullptr) {
  for (auto i : AllAllocKinds()) {
    concurrentUse(i) = ConcurrentUse::None;
  }
}

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);
    runtime()->gc.releaseArenaList(arenaList(i), lock);
  }

  runtime()->gc.releaseArenas(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()) {
    arenaList(kind).prepend(std::move(collectingArenaList(kind)));
    MOZ_ASSERT(collectingArenaList(kind).isEmpty());
  }
}

Arena* ArenaLists::takeSweptEmptyArenas() {
  Arena* arenas = savedEmptyArenas;
  savedEmptyArenas = nullptr;
  return arenas;
}

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(!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 ArenaChunk::canDecommitPage(size_t pageIndex) const {
  if (decommittedPages[pageIndex]) {
    return false;
  }

  size_t arenaIndex = pageToArenaIndex(pageIndex);
  for (size_t i = 0; i < ArenasPerPage; i++) {
    if (!freeCommittedArenas[arenaIndex + i]) {
      return false;
    }
  }

  return true;
}

void ArenaChunk::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 ArenaChunk::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;

  verify();

  updateChunkListAfterFree(gc, 1, lock);
}

bool ArenaChunk::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 = pageToArenaIndex(pageIndex) + 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 = pageToArenaIndex(pageIndex) + i;
      MOZ_ASSERT(!freeCommittedArenas[arenaIndex]);
      freeCommittedArenas[arenaIndex] = true;
    }
    info.numArenasFreeCommitted += ArenasPerPage;
  }

  info.numArenasFree += ArenasPerPage;
  updateChunkListAfterFree(gc, ArenasPerPage, lock);

  verify();

  return ok;
}

void ArenaChunk::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 = pageToArenaIndex(i) + j;
      MOZ_ASSERT(freeCommittedArenas[arenaIndex]);
      freeCommittedArenas[arenaIndex] = false;
    }
    info.numArenasFreeCommitted -= ArenasPerPage;
  }

  verify();
}

void ArenaChunk::updateChunkListAfterAlloc(GCRuntime* gc,
                                           const AutoLockGC& lock) {
  if (MOZ_UNLIKELY(info.numArenasFree == ArenasPerChunk - 1)) {
    gc->emptyChunks(lock).remove(this);
    gc->availableChunks(lock).push(this);
    return;
  }

  if (MOZ_UNLIKELY(!hasAvailableArenas())) {
    gc->availableChunks(lock).remove(this);
    gc->fullChunks(lock).push(this);
  }
}

void ArenaChunk::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);
  }
}

ArenaChunk* ChunkPool::pop() {
  MOZ_ASSERT(bool(head_) == bool(count_));
  if (!count_) {
    return nullptr;
  }
  return remove(head_);
}

void ChunkPool::push(ArenaChunk* chunk) {
  MOZ_ASSERT(!chunk->info.next);
  MOZ_ASSERT(!chunk->info.prev);

  chunk->info.next = head_;
  if (head_) {
    head_->info.prev = chunk;
  }
  head_ = chunk;
  ++count_;
}

ArenaChunk* ChunkPool::remove(ArenaChunk* 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.
    ArenaChunk* prev = nullptr;
    for (ArenaChunk* cur = head_; cur; cur = cur->info.next) {
      cur->info.prev = prev;
      prev = cur;
    }
  }

  MOZ_ASSERT(verify());
  MOZ_ASSERT(isSorted());
}

ArenaChunk* ChunkPool::mergeSort(ArenaChunk* list, size_t count) {
  MOZ_ASSERT(bool(list) == bool(count));

  if (count < 2) {
    return list;
  }

  size_t half = count / 2;

  // Split;
  ArenaChunk* front = list;
  ArenaChunk* back;
  {
    ArenaChunk* 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;
  ArenaChunk** 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 (ArenaChunk* 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(ArenaChunk* chunk) const {
  verify();
  for (ArenaChunk* 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 (ArenaChunk* 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 (ArenaChunk* chunk = head_; chunk; chunk = chunk->info.next) {
    chunk->verify();
  }
}

void ArenaChunk::verify() const {
  // Check the mark bits for each arena are aligned to the cache line size.
  static_assert((offsetof(ArenaChunk, arenas) % ArenaSize) == 0);
  constexpr size_t CellBytesPerMarkByte = CellBytesPerMarkBit * 8;
  static_assert((ArenaSize % CellBytesPerMarkByte) == 0);
  constexpr size_t MarkBytesPerArena = ArenaSize / CellBytesPerMarkByte;
  static_assert((MarkBytesPerArena % TypicalCacheLineSize) == 0);
  static_assert((offsetof(ArenaChunk, markBits) % TypicalCacheLineSize) == 0);

  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[arenaToPageIndex(i)] && freeCommittedArenas[i]));
  }
}

#endif

void ChunkPool::Iter::next() {
  MOZ_ASSERT(!done());
  current_ = current_->info.next;
}

Messung V0.5
C=95 H=91 G=92

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge