| // 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 <stdlib.h> // 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 <malloc.h> // NOLINT | |
| #endif | |
| #if defined(LEAK_SANITIZER) | |
| #include <sanitizer/lsan_interface.h> | |
| #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<v8::PageAllocator**>(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<v8::PageAllocator*, InitializePageAllocator>::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<char>(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<char>(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<byte*>(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<Address>( | |
| 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<void*>(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<void*>(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 |