[cppgc] Use global OOM handler for GCInfoTable

GCInfoTable is a process-global table storing Oilpan type information.
Table operations may fail in OOM scenarios which were previously just
caught in regular CHECKs. Change to use a global OOM handler that is set
up to use V8's handler.

Bug: chromium:1283199
Change-Id: Id33263ef7cd4028d60a071f5ab3b165e59ac9593
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3745368
Reviewed-by: Anton Bikineev <bikineev@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81540}
This commit is contained in:
Michael Lippautz 2022-07-05 17:54:05 +02:00 committed by V8 LUCI CQ
parent 32e6b48fca
commit 16b5842425
9 changed files with 73 additions and 30 deletions

View File

@ -240,6 +240,11 @@ void FatalOutOfMemoryHandlerImpl(const std::string& reason,
static_cast<v8::internal::CppHeap*>(heap)->isolate(), reason.c_str());
}
void GlobalFatalOutOfMemoryHandlerImpl(const std::string& reason,
const SourceLocation&, HeapBase* heap) {
V8::FatalProcessOutOfMemory(nullptr, reason.c_str());
}
} // namespace
class UnifiedHeapMarker final : public cppgc::internal::MarkerBase {
@ -428,6 +433,12 @@ v8::metrics::Recorder::ContextId CppHeap::MetricRecorderAdapter::GetContextId()
GetIsolate()->native_context());
}
// static
void CppHeap::InitializeOncePerProcess() {
cppgc::internal::GetGlobalOOMHandler().SetCustomHandler(
&GlobalFatalOutOfMemoryHandlerImpl);
}
CppHeap::CppHeap(
v8::Platform* platform,
const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>& custom_spaces,

View File

@ -101,6 +101,8 @@ class V8_EXPORT_PRIVATE CppHeap final
pause_scope_;
};
static void InitializeOncePerProcess();
static CppHeap* From(v8::CppHeap* heap) {
return static_cast<CppHeap*>(heap);
}

View File

@ -6,6 +6,7 @@
#include <map>
#include "src/heap/cppgc/platform.h"
#include "v8config.h" // NOLINT(build/include_directory)
#if !defined(CPPGC_CAGED_HEAP)
@ -73,8 +74,7 @@ VirtualMemory ReserveCagedHeap(PageAllocator& platform_allocator) {
if (memory.IsReserved()) return memory;
}
FATAL("Fatal process out of memory: Failed to reserve memory for caged heap");
UNREACHABLE();
GetGlobalOOMHandler()("Oilpan: CagedHeap reservation.");
}
} // namespace
@ -111,12 +111,13 @@ CagedHeap::CagedHeap(PageAllocator& platform_allocator)
CageBaseGlobalUpdater::UpdateCageBase(CagedHeapBase::g_heap_base_);
#endif // defined(CPPGC_POINTER_COMPRESSION)
const bool is_not_oom = platform_allocator.SetPermissions(
cage_start,
RoundUp(sizeof(CagedHeapLocalData), platform_allocator.CommitPageSize()),
PageAllocator::kReadWrite);
// Failing to commit the reservation means that we are out of memory.
CHECK(is_not_oom);
if (!platform_allocator.SetPermissions(
cage_start,
RoundUp(sizeof(CagedHeapLocalData),
platform_allocator.CommitPageSize()),
PageAllocator::kReadWrite)) {
GetGlobalOOMHandler()("Oilpan: CagedHeap commit CageHeapLocalData.");
}
const CagedAddress caged_heap_start = RoundUp(
reinterpret_cast<CagedAddress>(cage_start) + sizeof(CagedHeapLocalData),

View File

@ -13,6 +13,7 @@
#include "src/base/bits.h"
#include "src/base/lazy-instance.h"
#include "src/base/page-allocator.h"
#include "src/heap/cppgc/platform.h"
namespace cppgc {
namespace internal {
@ -32,38 +33,43 @@ static_assert(v8::base::bits::IsPowerOfTwo(kEntrySize),
} // namespace
GCInfoTable* GlobalGCInfoTable::global_table_ = nullptr;
constexpr GCInfoIndex GCInfoTable::kMaxIndex;
constexpr GCInfoIndex GCInfoTable::kMinIndex;
constexpr GCInfoIndex GCInfoTable::kInitialWantedLimit;
// static
void GlobalGCInfoTable::Initialize(PageAllocator* page_allocator) {
DCHECK_NOT_NULL(page_allocator);
static v8::base::LeakyObject<GCInfoTable> table(page_allocator);
void GlobalGCInfoTable::Initialize(PageAllocator& page_allocator) {
static v8::base::LeakyObject<GCInfoTable> table(page_allocator,
GetGlobalOOMHandler());
if (!global_table_) {
global_table_ = table.get();
} else {
CHECK_EQ(page_allocator, global_table_->allocator());
CHECK_EQ(&page_allocator, &global_table_->allocator());
}
}
GCInfoTable::GCInfoTable(PageAllocator* page_allocator)
GCInfoTable::GCInfoTable(PageAllocator& page_allocator,
FatalOutOfMemoryHandler& oom_handler)
: page_allocator_(page_allocator),
table_(static_cast<decltype(table_)>(page_allocator_->AllocatePages(
nullptr, MaxTableSize(), page_allocator_->AllocatePageSize(),
oom_handler_(oom_handler),
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_);
if (!table_) {
oom_handler_("Oilpan: GCInfoTable initial reservation.");
}
Resize();
}
GCInfoTable::~GCInfoTable() {
page_allocator_->ReleasePages(const_cast<GCInfo*>(table_), MaxTableSize(), 0);
page_allocator_.ReleasePages(const_cast<GCInfo*>(table_), MaxTableSize(), 0);
}
size_t GCInfoTable::MaxTableSize() const {
return RoundUp(GCInfoTable::kMaxIndex * kEntrySize,
page_allocator_->AllocatePageSize());
page_allocator_.AllocatePageSize());
}
GCInfoIndex GCInfoTable::InitialTableLimit() const {
@ -71,7 +77,7 @@ GCInfoIndex GCInfoTable::InitialTableLimit() const {
// 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;
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));
@ -83,20 +89,23 @@ void GCInfoTable::Resize() {
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_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));
if (!page_allocator_.SetPermissions(current_table_end, table_size_delta,
PageAllocator::kReadWrite)) {
oom_handler_("Oilpan: GCInfoTable resize.");
}
// 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));
CHECK(page_allocator_.SetPermissions(read_only_table_end_, read_only_delta,
PageAllocator::kRead));
read_only_table_end_ += read_only_delta;
}

View File

@ -14,6 +14,7 @@
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/platform.h"
namespace cppgc {
namespace internal {
@ -49,7 +50,8 @@ class V8_EXPORT GCInfoTable final {
// Refer through GlobalGCInfoTable for retrieving the global table outside
// of testing code.
explicit GCInfoTable(PageAllocator* page_allocator);
GCInfoTable(PageAllocator& page_allocator,
FatalOutOfMemoryHandler& oom_handler);
~GCInfoTable();
GCInfoTable(const GCInfoTable&) = delete;
GCInfoTable& operator=(const GCInfoTable&) = delete;
@ -68,7 +70,7 @@ class V8_EXPORT GCInfoTable final {
GCInfoIndex LimitForTesting() const { return limit_; }
GCInfo& TableSlotForTesting(GCInfoIndex index) { return table_[index]; }
PageAllocator* allocator() const { return page_allocator_; }
PageAllocator& allocator() const { return page_allocator_; }
private:
void Resize();
@ -78,7 +80,8 @@ class V8_EXPORT GCInfoTable final {
void CheckMemoryIsZeroed(uintptr_t* base, size_t len);
PageAllocator* page_allocator_;
PageAllocator& page_allocator_;
FatalOutOfMemoryHandler& oom_handler_;
// Holds the per-class GCInfo descriptors; each HeapObjectHeader keeps an
// index into this table.
GCInfo* table_;
@ -99,7 +102,7 @@ class V8_EXPORT GlobalGCInfoTable final {
// Sets up the table with the provided `page_allocator`. Will use an internal
// allocator in case no PageAllocator is provided. May be called multiple
// times with the same `page_allocator` argument.
static void Initialize(PageAllocator* page_allocator);
static void Initialize(PageAllocator& page_allocator);
// Accessors for the singleton table.
static GCInfoTable& GetMutable() { return *global_table_; }

View File

@ -48,6 +48,11 @@ void FatalOutOfMemoryHandler::SetCustomHandler(Callback* callback) {
custom_handler_ = callback;
}
FatalOutOfMemoryHandler& GetGlobalOOMHandler() {
static FatalOutOfMemoryHandler oom_handler;
return oom_handler;
}
} // namespace internal
namespace {
@ -91,7 +96,7 @@ void InitializeProcess(PageAllocator* page_allocator) {
auto& allocator = GetAllocator(page_allocator);
CHECK(!g_page_allocator);
internal::GlobalGCInfoTable::Initialize(&allocator);
internal::GlobalGCInfoTable::Initialize(allocator);
#if defined(CPPGC_CAGED_HEAP)
internal::CagedHeap::InitializeIfNeeded(allocator);
#endif // defined(CPPGC_CAGED_HEAP)

View File

@ -37,6 +37,9 @@ class V8_EXPORT_PRIVATE FatalOutOfMemoryHandler final {
Callback* custom_handler_ = nullptr;
};
// Gets the global OOM handler that is not bound to any specific Heap instance.
FatalOutOfMemoryHandler& GetGlobalOOMHandler();
} // namespace internal
} // namespace cppgc

View File

@ -103,6 +103,12 @@ void V8::InitializePlatform(v8::Platform* platform) {
v8::internal::ETWJITInterface::Register();
}
#endif
// Initialization needs to happen on platform-level, as this sets up some
// cppgc internals that are needed to allow gracefully failing during cppgc
// platform setup.
CppHeap::InitializeOncePerProcess();
AdvanceStartupState(V8StartupState::kPlatformInitialized);
}

View File

@ -10,6 +10,7 @@
#include "src/base/page-allocator.h"
#include "src/base/platform/platform.h"
#include "src/heap/cppgc/gc-info-table.h"
#include "src/heap/cppgc/platform.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -22,7 +23,8 @@ constexpr GCInfo GetEmptyGCInfo() { return {nullptr, nullptr, nullptr, false}; }
class GCInfoTableTest : public ::testing::Test {
public:
GCInfoTableTest() : table_(std::make_unique<GCInfoTable>(&page_allocator_)) {}
GCInfoTableTest()
: table_(std::make_unique<GCInfoTable>(page_allocator_, oom_handler_)) {}
GCInfoIndex RegisterNewGCInfoForTesting(const GCInfo& info) {
// Unused registered index will result in registering a new index.
@ -35,6 +37,7 @@ class GCInfoTableTest : public ::testing::Test {
private:
v8::base::PageAllocator page_allocator_;
FatalOutOfMemoryHandler oom_handler_;
std::unique_ptr<GCInfoTable> table_;
};