// Copyright 2012 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/allocation.h" #include // For free, malloc. #include "src/base/bits.h" #include "src/base/lazy-instance.h" #include "src/base/logging.h" #include "src/base/page-allocator.h" #include "src/base/platform/platform.h" #include "src/utils.h" #include "src/v8.h" #if V8_LIBC_BIONIC #include // NOLINT #endif #if defined(LEAK_SANITIZER) #include #endif namespace v8 { namespace internal { namespace { void* AlignedAllocInternal(size_t size, size_t alignment) { void* ptr; #if V8_OS_WIN ptr = _aligned_malloc(size, alignment); #elif V8_LIBC_BIONIC // posix_memalign is not exposed in some Android versions, so we fall back to // memalign. See http://code.google.com/p/android/issues/detail?id=35391. ptr = memalign(alignment, size); #else if (posix_memalign(&ptr, alignment, size)) ptr = nullptr; #endif return ptr; } // TODO(bbudge) Simplify this once all embedders implement a page allocator. struct InitializePageAllocator { static void Construct(void* page_allocator_ptr_arg) { auto page_allocator_ptr = reinterpret_cast(page_allocator_ptr_arg); v8::PageAllocator* page_allocator = V8::GetCurrentPlatform()->GetPageAllocator(); if (page_allocator == nullptr) { static v8::base::PageAllocator default_allocator; page_allocator = &default_allocator; } *page_allocator_ptr = page_allocator; } }; static base::LazyInstance::type page_allocator = LAZY_INSTANCE_INITIALIZER; v8::PageAllocator* GetPageAllocator() { return page_allocator.Get(); } // We will attempt allocation this many times. After each failure, we call // OnCriticalMemoryPressure to try to free some memory. const int kAllocationTries = 2; } // namespace void* Malloced::New(size_t size) { void* result = AllocWithRetry(size); if (result == nullptr) { V8::FatalProcessOutOfMemory(nullptr, "Malloced operator new"); } return result; } void Malloced::Delete(void* p) { free(p); } char* StrDup(const char* str) { int length = StrLength(str); char* result = NewArray(length + 1); MemCopy(result, str, length); result[length] = '\0'; return result; } char* StrNDup(const char* str, int n) { int length = StrLength(str); if (n < length) length = n; char* result = NewArray(length + 1); MemCopy(result, str, length); result[length] = '\0'; return result; } void* AllocWithRetry(size_t size) { void* result = nullptr; for (int i = 0; i < kAllocationTries; ++i) { result = malloc(size); if (result != nullptr) break; if (!OnCriticalMemoryPressure(size)) break; } return result; } void* AlignedAlloc(size_t size, size_t alignment) { DCHECK_LE(V8_ALIGNOF(void*), alignment); DCHECK(base::bits::IsPowerOfTwo(alignment)); void* result = nullptr; for (int i = 0; i < kAllocationTries; ++i) { result = AlignedAllocInternal(size, alignment); if (result != nullptr) break; if (!OnCriticalMemoryPressure(size + alignment)) break; } if (result == nullptr) { V8::FatalProcessOutOfMemory(nullptr, "AlignedAlloc"); } return result; } void AlignedFree(void *ptr) { #if V8_OS_WIN _aligned_free(ptr); #elif V8_LIBC_BIONIC // Using free is not correct in general, but for V8_LIBC_BIONIC it is. free(ptr); #else free(ptr); #endif } size_t AllocatePageSize() { return GetPageAllocator()->AllocatePageSize(); } size_t CommitPageSize() { return GetPageAllocator()->CommitPageSize(); } void SetRandomMmapSeed(int64_t seed) { GetPageAllocator()->SetRandomMmapSeed(seed); } void* GetRandomMmapAddr() { return GetPageAllocator()->GetRandomMmapAddr(); } void* AllocatePages(void* address, size_t size, size_t alignment, PageAllocator::Permission access) { DCHECK_EQ(address, AlignedAddress(address, alignment)); DCHECK_EQ(0UL, size & (GetPageAllocator()->AllocatePageSize() - 1)); void* result = nullptr; for (int i = 0; i < kAllocationTries; ++i) { result = GetPageAllocator()->AllocatePages(address, size, alignment, access); if (result != nullptr) break; size_t request_size = size + alignment - AllocatePageSize(); if (!OnCriticalMemoryPressure(request_size)) break; } #if defined(LEAK_SANITIZER) if (result != nullptr) { __lsan_register_root_region(result, size); } #endif return result; } bool FreePages(void* address, const size_t size) { DCHECK_EQ(0UL, size & (GetPageAllocator()->AllocatePageSize() - 1)); bool result = GetPageAllocator()->FreePages(address, size); #if defined(LEAK_SANITIZER) if (result) { __lsan_unregister_root_region(address, size); } #endif return result; } bool ReleasePages(void* address, size_t size, size_t new_size) { DCHECK_LT(new_size, size); bool result = GetPageAllocator()->ReleasePages(address, size, new_size); #if defined(LEAK_SANITIZER) if (result) { __lsan_unregister_root_region(address, size); __lsan_register_root_region(address, new_size); } #endif return result; } bool SetPermissions(void* address, size_t size, PageAllocator::Permission access) { return GetPageAllocator()->SetPermissions(address, size, access); } byte* AllocatePage(void* address, size_t* allocated) { size_t page_size = AllocatePageSize(); void* result = AllocatePages(address, page_size, page_size, PageAllocator::kReadWrite); if (result != nullptr) *allocated = page_size; return static_cast(result); } bool OnCriticalMemoryPressure(size_t length) { // TODO(bbudge) Rework retry logic once embedders implement the more // informative overload. if (!V8::GetCurrentPlatform()->OnCriticalMemoryPressure(length)) { V8::GetCurrentPlatform()->OnCriticalMemoryPressure(); } return true; } VirtualMemory::VirtualMemory() : address_(kNullAddress), size_(0) {} VirtualMemory::VirtualMemory(size_t size, void* hint, size_t alignment) : address_(kNullAddress), size_(0) { size_t page_size = AllocatePageSize(); size_t alloc_size = RoundUp(size, page_size); address_ = reinterpret_cast
( AllocatePages(hint, alloc_size, alignment, PageAllocator::kNoAccess)); if (address_ != kNullAddress) { size_ = alloc_size; } } VirtualMemory::~VirtualMemory() { if (IsReserved()) { Free(); } } void VirtualMemory::Reset() { address_ = kNullAddress; size_ = 0; } bool VirtualMemory::SetPermissions(Address address, size_t size, PageAllocator::Permission access) { CHECK(InVM(address, size)); bool result = v8::internal::SetPermissions(address, size, access); DCHECK(result); return result; } size_t VirtualMemory::Release(Address free_start) { DCHECK(IsReserved()); DCHECK(IsAddressAligned(free_start, CommitPageSize())); // Notice: Order is important here. The VirtualMemory object might live // inside the allocated region. const size_t free_size = size_ - (free_start - address_); CHECK(InVM(free_start, free_size)); DCHECK_LT(address_, free_start); DCHECK_LT(free_start, address_ + size_); CHECK(ReleasePages(reinterpret_cast(address_), size_, size_ - free_size)); size_ -= free_size; return free_size; } void VirtualMemory::Free() { DCHECK(IsReserved()); // Notice: Order is important here. The VirtualMemory object might live // inside the allocated region. Address address = address_; size_t size = size_; CHECK(InVM(address, size)); Reset(); // FreePages expects size to be aligned to allocation granularity. Trimming // may leave size at only commit granularity. Align it here. CHECK(FreePages(reinterpret_cast(address), RoundUp(size, AllocatePageSize()))); } void VirtualMemory::TakeControl(VirtualMemory* from) { DCHECK(!IsReserved()); address_ = from->address_; size_ = from->size_; from->Reset(); } bool AllocVirtualMemory(size_t size, void* hint, VirtualMemory* result) { VirtualMemory vm(size, hint); if (vm.IsReserved()) { result->TakeControl(&vm); return true; } return false; } bool AlignedAllocVirtualMemory(size_t size, size_t alignment, void* hint, VirtualMemory* result) { VirtualMemory vm(size, hint, alignment); if (vm.IsReserved()) { result->TakeControl(&vm); return true; } return false; } } // namespace internal } // namespace v8