Commit 4132d725 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

cppgc: Add GCInfo machinery

GCInfo and its related infrastructure is used to record information
about types. Currently, we store finalization and vtable information.
Future changes will introduce naming and tracing, similar to Oilpan in
Blink.

Information is stored in a process-wide global table that is
maintained at runtime. For static builds such information can be
recorded in the binary without the runtime overhead which is future
work.

This ports `third_party/blink/renderer/platform/heap/gc_info.{h,cc}`
on a semantic level. In addition to adjusting to V8's needs, we also
re-commit the already filled parts of the info table as read-only when
possible, making it harder to override type information.

Bug: chromium:1056170
Change-Id: Ib01eb24e6f8a94a4a647efde7af37689f8c20ba2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2111214
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarHannes Payer <hpayer@chromium.org>
Reviewed-by: 's avatarOmer Katz <omerkatz@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66847}
parent 499dcead
......@@ -3925,8 +3925,12 @@ v8_source_set("cppgc_base") {
sources = [
"include/cppgc/finalizer-trait.h",
"include/cppgc/gc-info.h",
"include/cppgc/platform.h",
"include/v8config.h",
"src/heap/cppgc/cppgc.cc",
"src/heap/cppgc/gc-info-table.cc",
"src/heap/cppgc/gc-info-table.h",
"src/heap/cppgc/gc-info.cc",
]
configs = [ ":internal_config" ]
......
// 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.
#ifndef INCLUDE_CPPGC_GC_INFO_H_
#define INCLUDE_CPPGC_GC_INFO_H_
#include <stdint.h>
#include "include/cppgc/finalizer-trait.h"
#include "include/v8config.h"
namespace cppgc {
namespace internal {
using GCInfoIndex = uint16_t;
class V8_EXPORT RegisteredGCInfoIndex final {
public:
RegisteredGCInfoIndex(FinalizationCallback finalization_callback,
bool has_v_table);
GCInfoIndex GetIndex() const { return index_; }
private:
const GCInfoIndex index_;
};
// Trait determines how the garbage collector treats objects wrt. to traversing,
// finalization, and naming.
template <typename T>
struct GCInfoTrait {
static GCInfoIndex Index() {
static_assert(sizeof(T), "T must be fully defined");
static const RegisteredGCInfoIndex registered_index(
FinalizerTrait<T>::kCallback, std::is_polymorphic<T>::value);
return registered_index.GetIndex();
}
};
} // namespace internal
} // namespace cppgc
#endif // INCLUDE_CPPGC_GC_INFO_H_
// 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.
#ifndef INCLUDE_CPPGC_PLATFORM_H_
#define INCLUDE_CPPGC_PLATFORM_H_
#include "include/v8-platform.h"
namespace cppgc {
// TODO(v8:10346): Put PageAllocator in a non-V8 include header to avoid
// depending on namespace v8.
using PageAllocator = v8::PageAllocator;
} // namespace cppgc
#endif // INCLUDE_CPPGC_PLATFORM_H_
......@@ -144,11 +144,11 @@ V8_INLINE Dest bit_cast(Source const& source) {
// Extract from 3.2.2 of C++11 spec:
// [...] A non-placement deallocation function for a class is
// odr-used by the definition of the destructor of that class, [...]
#define DISALLOW_NEW_AND_DELETE() \
void* operator new(size_t) { base::OS::Abort(); } \
void* operator new[](size_t) { base::OS::Abort(); } \
void operator delete(void*, size_t) { base::OS::Abort(); } \
void operator delete[](void*, size_t) { base::OS::Abort(); }
#define DISALLOW_NEW_AND_DELETE() \
void* operator new(size_t) { v8::base::OS::Abort(); } \
void* operator new[](size_t) { v8::base::OS::Abort(); } \
void operator delete(void*, size_t) { v8::base::OS::Abort(); } \
void operator delete[](void*, size_t) { v8::base::OS::Abort(); }
// Define V8_USE_ADDRESS_SANITIZER macro.
#if defined(__has_feature)
......
// 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>
#include "include/cppgc/gc-info.h"
#include "include/cppgc/platform.h"
#include "src/base/bits.h"
#include "src/base/lazy-instance.h"
namespace cppgc {
namespace internal {
namespace {
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
// 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.
#ifndef V8_HEAP_CPPGC_GC_INFO_TABLE_H_
#define V8_HEAP_CPPGC_GC_INFO_TABLE_H_
#include <stdint.h>
#include "include/cppgc/gc-info.h"
#include "include/cppgc/platform.h"
#include "include/v8config.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
namespace cppgc {
namespace internal {
// GCInfo contains metadata for objects that are instantiated from classes that
// inherit from GarbageCollected.
struct GCInfo final {
FinalizationCallback finalize;
bool has_v_table;
};
class V8_EXPORT GCInfoTable final {
public:
// At maximum |kMaxIndex - 1| indices are supported.
//
// We assume that 14 bits are enough to represent all possible types.
//
// For Blink during telemetry runs, we see about 1,000 different types;
// looking at the output of the Oilpan GC clang plugin, there appear to be at
// most about 6,000 types. Thus 14 bits should be more than twice as many bits
// as we will ever need. Different contexts may require adjusting this limit.
static constexpr GCInfoIndex kMaxIndex = 1 << 14;
// Minimum index returned. Values smaller |kMinIndex| may be used as
// sentinels.
static constexpr GCInfoIndex kMinIndex = 1;
// (Light) experimentation suggests that Blink doesn't need more than this
// while handling content on popular web properties.
static constexpr GCInfoIndex kInitialWantedLimit = 512;
// Refer through GlobalGCInfoTable for retrieving the global table outside
// of testing code.
explicit GCInfoTable(PageAllocator* page_allocator);
~GCInfoTable();
GCInfoIndex RegisterNewGCInfo(const GCInfo& info);
const GCInfo& GCInfoFromIndex(GCInfoIndex index) const {
DCHECK_GE(index, kMinIndex);
DCHECK_LT(index, kMaxIndex);
DCHECK(table_);
return table_[index];
}
GCInfoIndex NumberOfGCInfosForTesting() const { return current_index_; }
GCInfoIndex LimitForTesting() const { return limit_; }
GCInfo& TableSlotForTesting(GCInfoIndex index) { return table_[index]; }
private:
void Resize();
GCInfoIndex InitialTableLimit() const;
size_t MaxTableSize() const;
void CheckMemoryIsZeroed(uintptr_t* base, size_t len);
PageAllocator* page_allocator_;
// Holds the per-class GCInfo descriptors; each HeapObjectHeader keeps an
// index into this table.
GCInfo* table_;
uint8_t* read_only_table_end_;
// Current index used when requiring a new GCInfo object.
GCInfoIndex current_index_ = kMinIndex;
// The limit (exclusive) of the currently allocated table.
GCInfoIndex limit_ = 0;
v8::base::Mutex table_mutex_;
DISALLOW_COPY_AND_ASSIGN(GCInfoTable);
};
class V8_EXPORT GlobalGCInfoTable final {
public:
// Sets up a singleton table that can be acquired using Get().
static void Create(PageAllocator* page_allocator);
// Accessors for the singleton table.
static GCInfoTable& GetMutable() { return *global_table_; }
static const GCInfoTable& Get() { return *global_table_; }
static const GCInfo& GCInfoFromIndex(GCInfoIndex index) {
return Get().GCInfoFromIndex(index);
}
private:
// Singleton for each process. Retrieved through Get().
static GCInfoTable* global_table_;
DISALLOW_NEW_AND_DELETE()
DISALLOW_COPY_AND_ASSIGN(GlobalGCInfoTable);
};
} // namespace internal
} // namespace cppgc
#endif // V8_HEAP_CPPGC_GC_INFO_TABLE_H_
......@@ -2,15 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/cppgc/gc-info.h"
#include "include/v8config.h"
#include "src/heap/cppgc/gc-info-table.h"
namespace cppgc {
namespace internal {
V8_EXPORT void Dummy() {
// TODO(mlippautz): Placeholder to force building a library. Remove as soon as
// actual code is available.
}
RegisteredGCInfoIndex::RegisteredGCInfoIndex(
FinalizationCallback finalization_callback, bool has_v_table)
: index_(GlobalGCInfoTable::GetMutable().RegisterNewGCInfo(
{finalization_callback, has_v_table})) {}
} // namespace internal
} // namespace cppgc
......@@ -43,7 +43,10 @@ v8_executable("cppgc_unittests") {
v8_source_set("cppgc_unittests_sources") {
testonly = true
sources = [ "heap/cppgc/finalizer-trait_unittest.cc" ]
sources = [
"heap/cppgc/finalizer-trait_unittest.cc",
"heap/cppgc/gc-info_unittest.cc",
]
configs = [
"../..:external_config",
......
// 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 "include/cppgc/gc-info.h"
#include "include/cppgc/platform.h"
#include "src/base/page-allocator.h"
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/gc-info-table.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
TEST(GCInfoTableTest, InitialEmpty) {
v8::base::PageAllocator page_allocator;
GCInfoTable table(&page_allocator);
EXPECT_EQ(GCInfoTable::kMinIndex, table.NumberOfGCInfosForTesting());
}
TEST(GCInfoTableTest, ResizeToMaxIndex) {
v8::base::PageAllocator page_allocator;
GCInfoTable table(&page_allocator);
GCInfo info = {nullptr, false};
for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex;
i++) {
GCInfoIndex index = table.RegisterNewGCInfo(info);
EXPECT_EQ(i, index);
}
}
TEST(GCInfoTableDeathTest, MoreThanMaxIndexInfos) {
v8::base::PageAllocator page_allocator;
GCInfoTable table(&page_allocator);
GCInfo info = {nullptr, false};
// Create GCInfoTable::kMaxIndex entries.
for (GCInfoIndex i = GCInfoTable::kMinIndex; i < GCInfoTable::kMaxIndex;
i++) {
table.RegisterNewGCInfo(info);
}
EXPECT_DEATH(table.RegisterNewGCInfo(info), "");
}
TEST(GCInfoTableDeathTest, OldTableAreaIsReadOnly) {
v8::base::PageAllocator page_allocator;
GCInfoTable table(&page_allocator);
GCInfo info = {nullptr, false};
// Use up all slots until limit.
GCInfoIndex limit = table.LimitForTesting();
// Bail out if initial limit is already the maximum because of large committed
// pages. In this case, nothing can be comitted as read-only.
if (limit == GCInfoTable::kMaxIndex) {
return;
}
for (GCInfoIndex i = GCInfoTable::kMinIndex; i < limit; i++) {
table.RegisterNewGCInfo(info);
}
EXPECT_EQ(limit, table.LimitForTesting());
table.RegisterNewGCInfo(info);
EXPECT_NE(limit, table.LimitForTesting());
// Old area is now read-only.
auto& first_slot = table.TableSlotForTesting(GCInfoTable::kMinIndex);
EXPECT_DEATH(first_slot.finalize = nullptr, "");
}
namespace {
class ThreadRegisteringGCInfoObjects final : public v8::base::Thread {
public:
ThreadRegisteringGCInfoObjects(GCInfoTable* table,
GCInfoIndex num_registrations)
: v8::base::Thread(Options("Thread registering GCInfo objects.")),
table_(table),
num_registrations_(num_registrations) {}
void Run() final {
GCInfo info = {nullptr, false};
for (GCInfoIndex i = 0; i < num_registrations_; i++) {
table_->RegisterNewGCInfo(info);
}
}
private:
GCInfoTable* table_;
GCInfoIndex num_registrations_;
};
} // namespace
TEST(GCInfoTableTest, MultiThreadedResizeToMaxIndex) {
constexpr size_t num_threads = 4;
constexpr size_t main_thread_initialized = 2;
constexpr size_t gc_infos_to_register =
(GCInfoTable::kMaxIndex - 1) -
(GCInfoTable::kMinIndex + main_thread_initialized);
static_assert(gc_infos_to_register % num_threads == 0,
"must sum up to kMaxIndex");
constexpr size_t gc_infos_per_thread = gc_infos_to_register / num_threads;
v8::base::PageAllocator page_allocator;
GCInfoTable table(&page_allocator);
GCInfo info = {nullptr, false};
for (size_t i = 0; i < main_thread_initialized; i++) {
table.RegisterNewGCInfo(info);
}
v8::base::Thread* threads[num_threads];
for (size_t i = 0; i < num_threads; i++) {
threads[i] =
new ThreadRegisteringGCInfoObjects(&table, gc_infos_per_thread);
}
for (size_t i = 0; i < num_threads; i++) {
CHECK(threads[i]->Start());
}
for (size_t i = 0; i < num_threads; i++) {
threads[i]->Join();
delete threads[i];
}
}
// Tests using the global table and GCInfoTrait.
namespace {
class BasicType final {};
class OtherBasicType final {};
} // namespace
TEST(GCInfoTraitTest, IndexInBounds) {
v8::base::PageAllocator page_allocator;
GlobalGCInfoTable::Create(&page_allocator);
const GCInfoIndex index = GCInfoTrait<BasicType>::Index();
EXPECT_GT(GCInfoTable::kMaxIndex, index);
EXPECT_LE(GCInfoTable::kMinIndex, index);
}
TEST(GCInfoTraitTest, TraitReturnsSameIndexForSameType) {
v8::base::PageAllocator page_allocator;
GlobalGCInfoTable::Create(&page_allocator);
const GCInfoIndex index1 = GCInfoTrait<BasicType>::Index();
const GCInfoIndex index2 = GCInfoTrait<BasicType>::Index();
EXPECT_EQ(index1, index2);
}
TEST(GCInfoTraitTest, TraitReturnsDifferentIndexForDifferentTypes) {
v8::base::PageAllocator page_allocator;
GlobalGCInfoTable::Create(&page_allocator);
const GCInfoIndex index1 = GCInfoTrait<BasicType>::Index();
const GCInfoIndex index2 = GCInfoTrait<OtherBasicType>::Index();
EXPECT_NE(index1, index2);
}
} // namespace internal
} // namespace cppgc
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment