diff --git a/src/base/platform/memory.h b/src/base/platform/memory.h index 0a68ee840f..3a38473cd6 100644 --- a/src/base/platform/memory.h +++ b/src/base/platform/memory.h @@ -111,6 +111,8 @@ inline void AlignedFree(void* ptr) { // `AllocateAtLeast()` for a safe version. inline size_t MallocUsableSize(void* ptr) { #if V8_OS_WIN + // |_msize| cannot handle a null pointer. + if (!ptr) return 0; return _msize(ptr); #elif V8_OS_DARWIN return malloc_size(ptr); @@ -130,7 +132,7 @@ struct AllocationResult { // Allocates at least `n * sizeof(T)` uninitialized storage but may allocate // more which is indicated by the return value. Mimics C++23 -// `allocate_ate_least()`. +// `allocate_at_least()`. template V8_NODISCARD AllocationResult AllocateAtLeast(size_t n) { const size_t min_wanted_size = n * sizeof(T); @@ -140,13 +142,14 @@ V8_NODISCARD AllocationResult AllocateAtLeast(size_t n) { #else // V8_HAS_MALLOC_USABLE_SIZE const size_t usable_size = MallocUsableSize(memory); #if V8_USE_UNDEFINED_BEHAVIOR_SANITIZER + if (memory == nullptr) + return {nullptr, 0}; // UBSan (specifically, -fsanitize=bounds) assumes that any access outside // of the requested size for malloc is UB and will trap in ud2 instructions. // This can be worked around by using `Realloc()` on the specific memory - // region, assuming that the allocator doesn't actually reallocate the - // buffer. + // region. if (usable_size != min_wanted_size) { - CHECK_EQ(static_cast(Realloc(memory, usable_size)), memory); + memory = static_cast(Realloc(memory, usable_size)); } #endif // V8_USE_UNDEFINED_BEHAVIOR_SANITIZER return {memory, usable_size}; diff --git a/src/utils/allocation.cc b/src/utils/allocation.cc index 501467166a..a585bfed68 100644 --- a/src/utils/allocation.cc +++ b/src/utils/allocation.cc @@ -129,6 +129,16 @@ void* AllocWithRetry(size_t size, MallocFn malloc_fn) { return result; } +base::AllocationResult AllocAtLeastWithRetry(size_t size) { + base::AllocationResult result = {nullptr, 0u}; + for (int i = 0; i < kAllocationTries; ++i) { + result = base::AllocateAtLeast(size); + if (V8_LIKELY(result.ptr != nullptr)) break; + OnCriticalMemoryPressure(); + } + return {result.ptr, result.count}; +} + void* AlignedAllocWithRetry(size_t size, size_t alignment) { void* result = nullptr; for (int i = 0; i < kAllocationTries; ++i) { diff --git a/src/utils/allocation.h b/src/utils/allocation.h index b3aaf21d5e..87e30b43bd 100644 --- a/src/utils/allocation.h +++ b/src/utils/allocation.h @@ -94,6 +94,10 @@ using MallocFn = void* (*)(size_t); // Call free to release memory allocated with this function. void* AllocWithRetry(size_t size, MallocFn = base::Malloc); +// Performs a malloc, with retry logic on failure. Returns nullptr on failure. +// Call free to release memory allocated with this function. +base::AllocationResult AllocAtLeastWithRetry(size_t size); + V8_EXPORT_PRIVATE void* AlignedAllocWithRetry(size_t size, size_t alignment); V8_EXPORT_PRIVATE void AlignedFree(void* ptr); diff --git a/src/zone/accounting-allocator.cc b/src/zone/accounting-allocator.cc index d87981d632..24d2878ae3 100644 --- a/src/zone/accounting-allocator.cc +++ b/src/zone/accounting-allocator.cc @@ -91,7 +91,9 @@ Segment* AccountingAllocator::AllocateSegment(size_t bytes, kZonePageSize, PageAllocator::kReadWrite); } else { - memory = AllocWithRetry(bytes, zone_backing_malloc_); + auto result = AllocAtLeastWithRetry(bytes); + memory = result.ptr; + bytes = result.count; } if (memory == nullptr) return nullptr; @@ -115,7 +117,7 @@ void AccountingAllocator::ReturnSegment(Segment* segment, if (COMPRESS_ZONES_BOOL && supports_compression) { FreePages(bounded_page_allocator_.get(), segment, segment_size); } else { - zone_backing_free_(segment); + free(segment); } } diff --git a/test/cctest/test-allocation.cc b/test/cctest/test-allocation.cc index fa43733fd0..2a8d032c55 100644 --- a/test/cctest/test-allocation.cc +++ b/test/cctest/test-allocation.cc @@ -96,6 +96,8 @@ TEST_WITH_PLATFORM(AccountingAllocatorOOM, AllocationPlatform) { CHECK_EQ(result == nullptr, platform.oom_callback_called); } +// We use |AllocateAtLeast| in the accounting allocator, so we check only that +// we have _at least_ the expected amount of memory allocated. TEST_WITH_PLATFORM(AccountingAllocatorCurrentAndMax, AllocationPlatform) { v8::internal::AccountingAllocator allocator; static constexpr size_t kAllocationSizes[] = {51, 231, 27}; @@ -108,8 +110,8 @@ TEST_WITH_PLATFORM(AccountingAllocatorCurrentAndMax, AllocationPlatform) { for (size_t size : kAllocationSizes) { segments.push_back(allocator.AllocateSegment(size, support_compression)); CHECK_NOT_NULL(segments.back()); - CHECK_EQ(size, segments.back()->total_size()); - expected_current += size; + CHECK_LE(size, segments.back()->total_size()); + expected_current += segments.back()->total_size(); if (expected_current > expected_max) expected_max = expected_current; CHECK_EQ(expected_current, allocator.GetCurrentMemoryUsage()); CHECK_EQ(expected_max, allocator.GetMaxMemoryUsage());