Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
deps: V8: backport fe81545e6d14
Original commit message:

    [api] Adding total allocated bytes in HeapStatistics

    This change exposes total allocated bytes in v8::HeapStatistics API by
    introducing a new total_allocated_bytes() method that tracks all heap
    allocations since an Isolate creation.

    The implementation adds:
    - uint64_t total_allocated_bytes_ field to HeapStatistics.
    - An atomic total allocation counter is stored in the Heap class.
    - The counter is incremented whenever a RestLab is called. This approach can overestimate the total allocation for cases where the LAB is not fully used, but the leftover compared to the LAB itself is quite small, so it seems tolerable.

    Design doc reference:
    https://docs.google.com/document/d/1O4JPsoaxTQsX_7T5Fz4rsGeHMiM16jUrvDuq9FrtbNM

    Change-Id: Ic531698aaeb1578f943b7fdd346b9159ffd9b6c9
    Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6996467
    Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
    Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
    Commit-Queue: Dmitry Bezhetskov <dima00782@gmail.com>
    Cr-Commit-Position: refs/heads/main@{#103296}

Refs: v8/v8@fe81545
Co-authored-by: Caio Lima <caiolima@igalia.com>
PR-URL: #60429
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
  • Loading branch information
caiolima and caiolima committed Nov 21, 2025
commit d5fdc00951f3b96553ef1017bc6685800a463f50
8 changes: 8 additions & 0 deletions deps/v8/include/v8-statistics.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ class V8_EXPORT HeapStatistics {
size_t number_of_native_contexts() { return number_of_native_contexts_; }
size_t number_of_detached_contexts() { return number_of_detached_contexts_; }

/**
* Returns the total number of bytes allocated since the Isolate was created.
* This includes all heap objects allocated in any space (new, old, code,
* etc.).
*/
uint64_t total_allocated_bytes() { return total_allocated_bytes_; }

/**
* Returns a 0/1 boolean, which signifies whether the V8 overwrite heap
* garbage with a bit pattern.
Expand All @@ -175,6 +182,7 @@ class V8_EXPORT HeapStatistics {
size_t number_of_detached_contexts_;
size_t total_global_handles_size_;
size_t used_global_handles_size_;
uint64_t total_allocated_bytes_;

friend class V8;
friend class Isolate;
Expand Down
4 changes: 3 additions & 1 deletion deps/v8/src/api/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6560,7 +6560,8 @@ HeapStatistics::HeapStatistics()
peak_malloced_memory_(0),
does_zap_garbage_(false),
number_of_native_contexts_(0),
number_of_detached_contexts_(0) {}
number_of_detached_contexts_(0),
total_allocated_bytes_(0) {}

HeapSpaceStatistics::HeapSpaceStatistics()
: space_name_(nullptr),
Expand Down Expand Up @@ -10353,6 +10354,7 @@ void Isolate::GetHeapStatistics(HeapStatistics* heap_statistics) {
heap_statistics->number_of_native_contexts_ = heap->NumberOfNativeContexts();
heap_statistics->number_of_detached_contexts_ =
heap->NumberOfDetachedContexts();
heap_statistics->total_allocated_bytes_ = heap->GetTotalAllocatedBytes();
heap_statistics->does_zap_garbage_ = i::heap::ShouldZapGarbage();

#if V8_ENABLE_WEBASSEMBLY
Expand Down
30 changes: 24 additions & 6 deletions deps/v8/src/heap/heap-allocator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,42 @@ AllocationResult HeapAllocator::AllocateRawLargeInternal(
int size_in_bytes, AllocationType allocation, AllocationOrigin origin,
AllocationAlignment alignment) {
DCHECK_GT(size_in_bytes, heap_->MaxRegularHeapObjectSize(allocation));
AllocationResult allocation_result;
switch (allocation) {
case AllocationType::kYoung:
return new_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
allocation_result =
new_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
break;
case AllocationType::kOld:
return lo_space()->AllocateRaw(local_heap_, size_in_bytes);
allocation_result =
lo_space()->AllocateRaw(local_heap_, size_in_bytes);
break;
case AllocationType::kCode:
return code_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
allocation_result =
code_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
break;
case AllocationType::kSharedOld:
return shared_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
allocation_result =
shared_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
break;
case AllocationType::kTrusted:
return trusted_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
allocation_result =
trusted_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
break;
case AllocationType::kSharedTrusted:
return shared_trusted_lo_space()->AllocateRaw(local_heap_, size_in_bytes);
allocation_result = shared_trusted_lo_space()->AllocateRaw(
local_heap_, size_in_bytes);
break;
case AllocationType::kMap:
case AllocationType::kReadOnly:
case AllocationType::kSharedMap:
UNREACHABLE();
}
if (!allocation_result.IsFailure()) {
int allocated_size = ALIGN_TO_ALLOCATION_ALIGNMENT(size_in_bytes);
heap_->AddTotalAllocatedBytes(allocated_size);
}
return allocation_result;
}

namespace {
Expand Down
4 changes: 4 additions & 0 deletions deps/v8/src/heap/heap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7446,6 +7446,10 @@ int Heap::NextStackTraceId() {
return last_id;
}

uint64_t Heap::GetTotalAllocatedBytes() {
return total_allocated_bytes_.load(std::memory_order_relaxed);
}

EmbedderStackStateScope::EmbedderStackStateScope(
Heap* heap, EmbedderStackStateOrigin origin, StackState stack_state)
: heap_(heap),
Expand Down
7 changes: 7 additions & 0 deletions deps/v8/src/heap/heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -1635,6 +1635,11 @@ class Heap final {
bool ShouldUseBackgroundThreads() const;
bool ShouldUseIncrementalMarking() const;

void AddTotalAllocatedBytes(size_t size) {
total_allocated_bytes_.fetch_add(size, std::memory_order_relaxed);
}
uint64_t GetTotalAllocatedBytes();

HeapAllocator* allocator() { return heap_allocator_; }
const HeapAllocator* allocator() const { return heap_allocator_; }

Expand Down Expand Up @@ -2409,6 +2414,8 @@ class Heap final {
// actually finished.
bool is_full_gc_during_loading_ = false;

std::atomic<uint64_t> total_allocated_bytes_ = 0;

// Classes in "heap" can be friends.
friend class ActivateMemoryReducerTask;
friend class AlwaysAllocateScope;
Expand Down
6 changes: 6 additions & 0 deletions deps/v8/src/heap/main-allocator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ void MainAllocator::ResetLab(Address start, Address end, Address extended_end) {
MemoryChunkMetadata::UpdateHighWaterMark(top());
}

// This is going to overestimate a bit of the total allocated bytes, since the
// LAB was not used yet. However the leftover compared to the LAB itself is
// quite small, so it seems tolerable.
if (local_heap_) {
local_heap_->heap()->AddTotalAllocatedBytes(end - start);
}
allocation_info().Reset(start, end);

if (SupportsPendingAllocation()) {
Expand Down
146 changes: 146 additions & 0 deletions deps/v8/test/cctest/test-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17492,6 +17492,152 @@ TEST(GetHeapSpaceStatistics) {
CHECK_EQ(total_physical_size, heap_statistics.total_physical_size());
}

UNINITIALIZED_TEST(GetHeapTotalAllocatedBytes) {
// This test is incompatible with concurrent allocation, which may occur
// while collecting the statistics and break the final `CHECK_EQ`s.
if (i::v8_flags.stress_concurrent_allocation) return;

v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);

const uint32_t number_of_elements = 1;
const uint32_t allocation_size = i::FixedArray::SizeFor(number_of_elements);
const uint32_t trusted_allocation_size =
i::TrustedFixedArray::SizeFor(number_of_elements);
const uint32_t lo_number_of_elements = 256 * 1024;
const uint32_t lo_allocation_size =
i::FixedArray::SizeFor(lo_number_of_elements);
const uint32_t trusted_lo_allocation_size =
i::TrustedFixedArray::SizeFor(lo_number_of_elements);
const uint32_t expected_allocation_size =
allocation_size * 2 + lo_allocation_size * 2 + trusted_allocation_size +
trusted_lo_allocation_size;

{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
LocalContext env(isolate);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

v8::HeapStatistics heap_stats_before;
isolate->GetHeapStatistics(&heap_stats_before);
size_t initial_allocated = heap_stats_before.total_allocated_bytes();

i::MaybeHandle<i::FixedArray> young_alloc =
i_isolate->factory()->TryNewFixedArray(number_of_elements,
i::AllocationType::kYoung);
USE(young_alloc);
i::MaybeHandle<i::FixedArray> old_alloc =
i_isolate->factory()->TryNewFixedArray(number_of_elements,
i::AllocationType::kOld);
USE(old_alloc);
i::Handle<i::TrustedFixedArray> trusted_alloc =
i_isolate->factory()->NewTrustedFixedArray(number_of_elements,
i::AllocationType::kTrusted);
USE(trusted_alloc);
i::MaybeHandle<i::FixedArray> old_lo_alloc =
i_isolate->factory()->TryNewFixedArray(lo_number_of_elements,
i::AllocationType::kOld);
USE(old_lo_alloc);

{
v8::HandleScope inner_handle_scope(isolate);
auto young_lo_alloc = i_isolate->factory()->TryNewFixedArray(
lo_number_of_elements, i::AllocationType::kYoung);
USE(young_lo_alloc);
}

auto trusted_lo_alloc = i_isolate->factory()->NewTrustedFixedArray(
lo_number_of_elements, i::AllocationType::kTrusted);
USE(trusted_lo_alloc);

v8::HeapStatistics heap_stats_after;
isolate->GetHeapStatistics(&heap_stats_after);
uint64_t final_allocated = heap_stats_after.total_allocated_bytes();

CHECK_GT(final_allocated, initial_allocated);
uint64_t allocated_diff = final_allocated - initial_allocated;
CHECK_GE(allocated_diff, expected_allocation_size);

// This either tests counting happening when a LAB freed and validate
// there's no double counting on evacuated/promoted objects.
v8::internal::heap::InvokeAtomicMajorGC(i_isolate->heap());

v8::HeapStatistics heap_stats_after_gc;
isolate->GetHeapStatistics(&heap_stats_after_gc);
uint64_t total_allocation_after_gc =
heap_stats_after_gc.total_allocated_bytes();

CHECK_EQ(total_allocation_after_gc, final_allocated);
}

isolate->Dispose();
}

#if V8_CAN_CREATE_SHARED_HEAP_BOOL

UNINITIALIZED_TEST(GetHeapTotalAllocatedBytesSharedSpaces) {
// This test is incompatible with concurrent allocation, which may occur
// while collecting the statistics and break the final `CHECK_EQ`s.
if (i::v8_flags.stress_concurrent_allocation) return;
if (COMPRESS_POINTERS_IN_MULTIPLE_CAGES_BOOL) return;

i::v8_flags.shared_heap = true;
i::FlagList::EnforceFlagImplications();

v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);

{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
LocalContext env(isolate);

v8::HeapStatistics heap_stats_before;
isolate->GetHeapStatistics(&heap_stats_before);
size_t initial_allocated = heap_stats_before.total_allocated_bytes();

i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);

const uint32_t number_of_elements = 1;
const uint32_t allocation_size = i::FixedArray::SizeFor(number_of_elements);
const uint32_t trusted_allocation_size =
i::TrustedFixedArray::SizeFor(number_of_elements);
const uint32_t lo_number_of_elements = 256 * 1024;
const uint32_t lo_allocation_size =
i::FixedArray::SizeFor(lo_number_of_elements);
const uint32_t expected_allocation_size =
allocation_size + trusted_allocation_size + lo_allocation_size;

i::MaybeHandle<i::FixedArray> shared_alloc =
i_isolate->factory()->TryNewFixedArray(number_of_elements,
i::AllocationType::kSharedOld);
USE(shared_alloc);
i::Handle<i::TrustedFixedArray> shared_trusted_alloc =
i_isolate->factory()->NewTrustedFixedArray(
number_of_elements, i::AllocationType::kSharedTrusted);
USE(shared_trusted_alloc);
i::MaybeHandle<i::FixedArray> shared_lo_alloc =
i_isolate->factory()->TryNewFixedArray(lo_number_of_elements,
i::AllocationType::kSharedOld);
USE(shared_lo_alloc);

v8::HeapStatistics heap_stats_after;
isolate->GetHeapStatistics(&heap_stats_after);
uint64_t final_allocated = heap_stats_after.total_allocated_bytes();

CHECK_GT(final_allocated, initial_allocated);
uint64_t allocated_diff = final_allocated - initial_allocated;
CHECK_GE(allocated_diff, expected_allocation_size);
}

isolate->Dispose();
}

#endif // V8_CAN_CREATE_SHARED_HEAP_BOOL

TEST(NumberOfNativeContexts) {
static const size_t kNumTestContexts = 10;
i::Isolate* isolate = CcTest::i_isolate();
Expand Down