gc-info-table.cc 4.52 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/heap/cppgc/gc-info-table.h"

#include <algorithm>
#include <limits>
#include <memory>

11
#include "include/cppgc/internal/gc-info.h"
12 13 14 15 16 17 18 19 20
#include "include/cppgc/platform.h"
#include "src/base/bits.h"
#include "src/base/lazy-instance.h"

namespace cppgc {
namespace internal {

namespace {

21 22 23 24 25
// GCInfoTable::table_, the table which holds GCInfos, is maintained as a
// contiguous array reserved upfront. Subparts of the array are (re-)committed
// as read/write or read-only in OS pages, whose size is a power of 2. To avoid
// having GCInfos that cross the boundaries between these subparts we force the
// size of GCInfo to be a power of 2 as well.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
constexpr size_t kEntrySize = sizeof(GCInfo);
static_assert(v8::base::bits::IsPowerOfTwo(kEntrySize),
              "GCInfoTable entries size must be power of "
              "two");

}  // namespace

GCInfoTable* GlobalGCInfoTable::global_table_ = nullptr;
constexpr GCInfoIndex GCInfoTable::kMaxIndex;
constexpr GCInfoIndex GCInfoTable::kMinIndex;
constexpr GCInfoIndex GCInfoTable::kInitialWantedLimit;

void GlobalGCInfoTable::Create(PageAllocator* page_allocator) {
  static v8::base::LeakyObject<GCInfoTable> table(page_allocator);
  if (!global_table_) {
    global_table_ = table.get();
  }
}

GCInfoTable::GCInfoTable(PageAllocator* page_allocator)
    : page_allocator_(page_allocator),
      table_(static_cast<decltype(table_)>(page_allocator_->AllocatePages(
          nullptr, MaxTableSize(), page_allocator_->AllocatePageSize(),
          PageAllocator::kNoAccess))),
      read_only_table_end_(reinterpret_cast<uint8_t*>(table_)) {
  CHECK(table_);
  Resize();
}

GCInfoTable::~GCInfoTable() {
  page_allocator_->ReleasePages(const_cast<GCInfo*>(table_), MaxTableSize(), 0);
}

size_t GCInfoTable::MaxTableSize() const {
  return RoundUp(GCInfoTable::kMaxIndex * kEntrySize,
                 page_allocator_->AllocatePageSize());
}

GCInfoIndex GCInfoTable::InitialTableLimit() const {
  // Different OSes have different page sizes, so we have to choose the minimum
  // of memory wanted and OS page size.
  constexpr size_t memory_wanted = kInitialWantedLimit * kEntrySize;
  const size_t initial_limit =
      RoundUp(memory_wanted, page_allocator_->AllocatePageSize()) / kEntrySize;
  CHECK_GT(std::numeric_limits<GCInfoIndex>::max(), initial_limit);
  return static_cast<GCInfoIndex>(
      std::min(static_cast<size_t>(kMaxIndex), initial_limit));
}

void GCInfoTable::Resize() {
  const GCInfoIndex new_limit = (limit_) ? 2 * limit_ : InitialTableLimit();
  CHECK_GT(new_limit, limit_);
  const size_t old_committed_size = limit_ * kEntrySize;
  const size_t new_committed_size = new_limit * kEntrySize;
  CHECK(table_);
  CHECK_EQ(0u, new_committed_size % page_allocator_->AllocatePageSize());
  CHECK_GE(MaxTableSize(), new_committed_size);
  // Recommit new area as read/write.
  uint8_t* current_table_end =
      reinterpret_cast<uint8_t*>(table_) + old_committed_size;
  const size_t table_size_delta = new_committed_size - old_committed_size;
  CHECK(page_allocator_->SetPermissions(current_table_end, table_size_delta,
                                        PageAllocator::kReadWrite));
  // Recommit old area as read-only.
  if (read_only_table_end_ != current_table_end) {
    DCHECK_GT(current_table_end, read_only_table_end_);
    const size_t read_only_delta = current_table_end - read_only_table_end_;
    CHECK(page_allocator_->SetPermissions(read_only_table_end_, read_only_delta,
                                          PageAllocator::kRead));
    read_only_table_end_ += read_only_delta;
  }

  // Check that newly-committed memory is zero-initialized.
  CheckMemoryIsZeroed(reinterpret_cast<uintptr_t*>(current_table_end),
                      table_size_delta / sizeof(uintptr_t));

  limit_ = new_limit;
}

void GCInfoTable::CheckMemoryIsZeroed(uintptr_t* base, size_t len) {
#if DEBUG
  for (size_t i = 0; i < len; ++i) {
    DCHECK(!base[i]);
  }
#endif  // DEBUG
}

GCInfoIndex GCInfoTable::RegisterNewGCInfo(const GCInfo& info) {
  // Ensuring a new index involves current index adjustment as well as
  // potentially resizing the table. For simplicity we use a lock.
  v8::base::MutexGuard guard(&table_mutex_);

  if (current_index_ == limit_) {
    Resize();
  }

  GCInfoIndex new_index = current_index_++;
  CHECK_LT(new_index, GCInfoTable::kMaxIndex);
  table_[new_index] = info;
  return new_index;
}

}  // namespace internal
}  // namespace cppgc