blob: eef89b77e7bddfcd6765d673aa565a0677cd70a4 [file] [log] [blame]
/*
* Copyright (C) 2011-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <wtf/MetaAllocator.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/WTFConfig.h>
namespace WTF {
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(MetaAllocatorHandle);
DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(MetaAllocatorFreeSpace);
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(MetaAllocatorFreeSpace);
MetaAllocator::~MetaAllocator()
{
for (FreeSpaceNode* node = m_freeSpaceSizeMap.first(); node;) {
FreeSpaceNode* next = node->successor();
m_freeSpaceSizeMap.remove(node);
freeFreeSpaceNode(node);
node = next;
}
#ifndef NDEBUG
ASSERT(!m_mallocBalance);
#endif
}
void MetaAllocatorTracker::notify(MetaAllocatorHandle& handle)
{
m_allocations.insert(&handle);
}
void MetaAllocatorTracker::release(MetaAllocatorHandle& handle)
{
m_allocations.remove(&handle);
}
void MetaAllocator::release(const LockHolder&, MetaAllocatorHandle& handle)
{
if (handle.sizeInBytes()) {
MemoryPtr start = handle.start();
size_t sizeInBytes = handle.sizeInBytes();
decrementPageOccupancy(start.untaggedPtr(), sizeInBytes);
addFreeSpaceFromReleasedHandle(FreeSpacePtr(start), sizeInBytes);
}
if (UNLIKELY(!!m_tracker))
m_tracker->release(handle);
}
MetaAllocatorHandle::MetaAllocatorHandle(MetaAllocator& allocator, MetaAllocatorHandle::MemoryPtr start, size_t sizeInBytes)
: m_allocator(allocator)
, m_start(start)
, m_end(start + sizeInBytes)
{
ASSERT(start);
ASSERT(sizeInBytes);
}
MetaAllocatorHandle::~MetaAllocatorHandle()
{
Locker locker { allocator().m_lock };
allocator().release(locker, *this);
}
void MetaAllocatorHandle::shrink(size_t newSizeInBytes)
{
size_t sizeInBytes = this->sizeInBytes();
ASSERT(newSizeInBytes <= sizeInBytes);
MetaAllocator& allocator = this->allocator();
Locker locker { allocator.m_lock };
newSizeInBytes = allocator.roundUp(newSizeInBytes);
ASSERT(newSizeInBytes <= sizeInBytes);
if (newSizeInBytes == sizeInBytes)
return;
MemoryPtr freeStart = m_start + newSizeInBytes;
size_t freeSize = sizeInBytes - newSizeInBytes;
uintptr_t freeStartValue = freeStart.untaggedPtr<uintptr_t>();
uintptr_t freeEnd = freeStartValue + freeSize;
uintptr_t firstCompletelyFreePage = roundUpToMultipleOf(allocator.m_pageSize, freeStartValue);
if (firstCompletelyFreePage < freeEnd)
allocator.decrementPageOccupancy(reinterpret_cast<void*>(firstCompletelyFreePage), freeSize - (firstCompletelyFreePage - freeStartValue));
allocator.addFreeSpaceFromReleasedHandle(MetaAllocator::FreeSpacePtr(freeStart), freeSize);
m_end = freeStart;
}
void MetaAllocatorHandle::dump(PrintStream& out) const
{
out.print(RawPointer(start().untaggedPtr()), "...", RawPointer(end().untaggedPtr()));
}
MetaAllocator::MetaAllocator(Lock& lock, size_t allocationGranule, size_t pageSize)
: m_allocationGranule(allocationGranule)
, m_pageSize(pageSize)
, m_bytesAllocated(0)
, m_bytesReserved(0)
, m_bytesCommitted(0)
, m_lock(lock)
#ifndef NDEBUG
, m_mallocBalance(0)
#endif
#if ENABLE(META_ALLOCATOR_PROFILE)
, m_numAllocations(0)
, m_numFrees(0)
#endif
{
for (m_logPageSize = 0; m_logPageSize < 32; ++m_logPageSize) {
if (static_cast<size_t>(1) << m_logPageSize == m_pageSize)
break;
}
ASSERT(static_cast<size_t>(1) << m_logPageSize == m_pageSize);
for (m_logAllocationGranule = 0; m_logAllocationGranule < 32; ++m_logAllocationGranule) {
if (static_cast<size_t>(1) << m_logAllocationGranule == m_allocationGranule)
break;
}
ASSERT(static_cast<size_t>(1) << m_logAllocationGranule == m_allocationGranule);
}
RefPtr<MetaAllocatorHandle> MetaAllocator::allocate(const LockHolder&, size_t sizeInBytes)
{
if (!sizeInBytes)
return nullptr;
sizeInBytes = roundUp(sizeInBytes);
FreeSpacePtr start = findAndRemoveFreeSpace(sizeInBytes);
if (!start) {
size_t requestedNumberOfPages = (sizeInBytes + m_pageSize - 1) >> m_logPageSize;
size_t numberOfPages = requestedNumberOfPages;
start = allocateNewSpace(numberOfPages);
if (!start)
return nullptr;
ASSERT(numberOfPages >= requestedNumberOfPages);
size_t roundedUpSize = numberOfPages << m_logPageSize;
ASSERT(roundedUpSize >= sizeInBytes);
m_bytesReserved += roundedUpSize;
if (roundedUpSize > sizeInBytes) {
FreeSpacePtr freeSpaceStart = start + sizeInBytes;
size_t freeSpaceSize = roundedUpSize - sizeInBytes;
addFreeSpace(freeSpaceStart, freeSpaceSize);
}
}
incrementPageOccupancy(start.untaggedPtr(), sizeInBytes);
m_bytesAllocated += sizeInBytes;
#if ENABLE(META_ALLOCATOR_PROFILE)
m_numAllocations++;
#endif
auto handle = adoptRef(*new MetaAllocatorHandle(*this, MemoryPtr(start), sizeInBytes));
if (UNLIKELY(!!m_tracker))
m_tracker->notify(*handle.ptr());
return handle;
}
MetaAllocator::Statistics MetaAllocator::currentStatistics(const LockHolder&)
{
Statistics result;
result.bytesAllocated = m_bytesAllocated;
result.bytesReserved = m_bytesReserved;
result.bytesCommitted = m_bytesCommitted;
return result;
}
MetaAllocator::FreeSpacePtr MetaAllocator::findAndRemoveFreeSpace(size_t sizeInBytes)
{
FreeSpaceNode* node = m_freeSpaceSizeMap.findLeastGreaterThanOrEqual(sizeInBytes);
if (!node)
return nullptr;
size_t nodeSizeInBytes = node->sizeInBytes();
RELEASE_ASSERT(nodeSizeInBytes >= sizeInBytes);
m_freeSpaceSizeMap.remove(node);
FreeSpacePtr result;
if (nodeSizeInBytes == sizeInBytes) {
// Easy case: perfect fit, so just remove the node entirely.
result = node->m_start;
m_freeSpaceStartAddressMap.remove(node->m_start);
m_freeSpaceEndAddressMap.remove(node->m_end);
freeFreeSpaceNode(node);
} else {
// Try to be a good citizen and ensure that the returned chunk of memory
// straddles as few pages as possible, but only insofar as doing so will
// not increase fragmentation. The intuition is that minimizing
// fragmentation is a strictly higher priority than minimizing the number
// of committed pages, since in the long run, smaller fragmentation means
// fewer committed pages and fewer failures in general.
uintptr_t nodeStartAsInt = node->m_start.untaggedPtr<uintptr_t>();
uintptr_t firstPage = nodeStartAsInt >> m_logPageSize;
uintptr_t lastPage = (nodeStartAsInt + nodeSizeInBytes - 1) >> m_logPageSize;
uintptr_t lastPageForLeftAllocation = (nodeStartAsInt + sizeInBytes - 1) >> m_logPageSize;
uintptr_t firstPageForRightAllocation = (nodeStartAsInt + nodeSizeInBytes - sizeInBytes) >> m_logPageSize;
if (lastPageForLeftAllocation - firstPage + 1 <= lastPage - firstPageForRightAllocation + 1) {
// Allocate in the left side of the returned chunk, and slide the node to the right.
result = node->m_start;
m_freeSpaceStartAddressMap.remove(node->m_start);
node->m_start += sizeInBytes;
RELEASE_ASSERT(nodeStartAsInt < node->m_start.untaggedPtr<uintptr_t>() && node->m_start.untaggedPtr<uintptr_t>() < node->m_end.untaggedPtr<uintptr_t>());
m_freeSpaceSizeMap.insert(node);
m_freeSpaceStartAddressMap.add(node->m_start, node);
} else {
// Allocate in the right size of the returned chunk, and slide the node to the left;
result = node->m_end - sizeInBytes;
m_freeSpaceEndAddressMap.remove(node->m_end);
node->m_end = result;
m_freeSpaceSizeMap.insert(node);
m_freeSpaceEndAddressMap.add(result, node);
}
}
#if ENABLE(META_ALLOCATOR_PROFILE)
dumpProfile();
#endif
return result;
}
void MetaAllocator::addFreeSpaceFromReleasedHandle(FreeSpacePtr start, size_t sizeInBytes)
{
#if ENABLE(META_ALLOCATOR_PROFILE)
m_numFrees++;
#endif
m_bytesAllocated -= sizeInBytes;
addFreeSpace(start, sizeInBytes);
}
void MetaAllocator::addFreshFreeSpace(void* start, size_t sizeInBytes)
{
Config::AssertNotFrozenScope assertNotFrozenScope;
Locker locker { m_lock };
m_bytesReserved += sizeInBytes;
addFreeSpace(FreeSpacePtr::makeFromRawPointer(start), sizeInBytes);
}
size_t MetaAllocator::debugFreeSpaceSize()
{
#ifndef NDEBUG
Locker locker { m_lock };
size_t result = 0;
for (FreeSpaceNode* node = m_freeSpaceSizeMap.first(); node; node = node->successor())
result += node->sizeInBytes();
return result;
#else
CRASH();
return 0;
#endif
}
void MetaAllocator::addFreeSpace(FreeSpacePtr start, size_t sizeInBytes)
{
FreeSpacePtr end = start + sizeInBytes;
HashMap<FreeSpacePtr, FreeSpaceNode*>::iterator leftNeighbor = m_freeSpaceEndAddressMap.find(start);
HashMap<FreeSpacePtr, FreeSpaceNode*>::iterator rightNeighbor = m_freeSpaceStartAddressMap.find(end);
if (leftNeighbor != m_freeSpaceEndAddressMap.end()) {
// We have something we can coalesce with on the left. Remove it from the tree, and
// remove its end from the end address map.
ASSERT(leftNeighbor->value->m_end == leftNeighbor->key);
FreeSpaceNode* leftNode = leftNeighbor->value;
FreeSpacePtr leftEnd = leftNode->m_end;
ASSERT(leftEnd == start);
m_freeSpaceSizeMap.remove(leftNode);
m_freeSpaceEndAddressMap.remove(leftEnd);
// Now check if there is also something to coalesce with on the right.
if (rightNeighbor != m_freeSpaceStartAddressMap.end()) {
// Freeing something in the middle of free blocks. Coalesce both left and
// right, whilst removing the right neighbor from the maps.
ASSERT(rightNeighbor->value->m_start == rightNeighbor->key);
FreeSpaceNode* rightNode = rightNeighbor->value;
FreeSpacePtr rightStart = rightNeighbor->key;
size_t rightSize = rightNode->sizeInBytes();
FreeSpacePtr rightEnd = rightNode->m_end;
ASSERT(rightStart == end);
ASSERT(leftNode->m_start + (leftNode->sizeInBytes() + sizeInBytes + rightSize) == rightEnd);
m_freeSpaceSizeMap.remove(rightNode);
m_freeSpaceStartAddressMap.remove(rightStart);
m_freeSpaceEndAddressMap.remove(rightEnd);
freeFreeSpaceNode(rightNode);
leftNode->m_end += (sizeInBytes + rightSize);
m_freeSpaceSizeMap.insert(leftNode);
m_freeSpaceEndAddressMap.add(rightEnd, leftNode);
} else {
leftNode->m_end += sizeInBytes;
m_freeSpaceSizeMap.insert(leftNode);
m_freeSpaceEndAddressMap.add(end, leftNode);
}
} else {
// Cannot coalesce with left; try to see if we can coalesce with right.
if (rightNeighbor != m_freeSpaceStartAddressMap.end()) {
FreeSpaceNode* rightNode = rightNeighbor->value;
FreeSpacePtr rightStart = rightNeighbor->key;
ASSERT(rightStart == end);
ASSERT(start + (sizeInBytes + rightNode->sizeInBytes()) == rightNode->m_end);
m_freeSpaceSizeMap.remove(rightNode);
m_freeSpaceStartAddressMap.remove(rightStart);
rightNode->m_start = start;
m_freeSpaceSizeMap.insert(rightNode);
m_freeSpaceStartAddressMap.add(start, rightNode);
} else {
// Nothing to coalesce with, so create a new free space node and add it.
FreeSpaceNode* node = allocFreeSpaceNode();
node->m_start = start;
node->m_end = start + sizeInBytes;
m_freeSpaceSizeMap.insert(node);
m_freeSpaceStartAddressMap.add(start, node);
m_freeSpaceEndAddressMap.add(end, node);
}
}
#if ENABLE(META_ALLOCATOR_PROFILE)
dumpProfile();
#endif
}
void MetaAllocator::incrementPageOccupancy(void* address, size_t sizeInBytes)
{
uintptr_t firstPage = reinterpret_cast<uintptr_t>(address) >> m_logPageSize;
uintptr_t lastPage = (reinterpret_cast<uintptr_t>(address) + sizeInBytes - 1) >> m_logPageSize;
uintptr_t currentPageStart = 0;
size_t count = 0;
auto flushNeedPages = [&] {
if (!currentPageStart)
return;
notifyNeedPage(reinterpret_cast<void*>(currentPageStart << m_logPageSize), count);
currentPageStart = 0;
count = 0;
};
for (uintptr_t page = firstPage; page <= lastPage; ++page) {
auto result = m_pageOccupancyMap.add(page, 1);
if (result.isNewEntry) {
m_bytesCommitted += m_pageSize;
if (!currentPageStart)
currentPageStart = page;
++count;
} else {
result.iterator->value++;
flushNeedPages();
}
}
flushNeedPages();
}
void MetaAllocator::decrementPageOccupancy(void* address, size_t sizeInBytes)
{
uintptr_t firstPage = reinterpret_cast<uintptr_t>(address) >> m_logPageSize;
uintptr_t lastPage = (reinterpret_cast<uintptr_t>(address) + sizeInBytes - 1) >> m_logPageSize;
uintptr_t currentPageStart = 0;
size_t count = 0;
auto flushFreePages = [&] {
if (!currentPageStart)
return;
notifyPageIsFree(reinterpret_cast<void*>(currentPageStart << m_logPageSize), count);
currentPageStart = 0;
count = 0;
};
for (uintptr_t page = firstPage; page <= lastPage; ++page) {
HashMap<uintptr_t, size_t>::iterator iter = m_pageOccupancyMap.find(page);
ASSERT(iter != m_pageOccupancyMap.end());
if (!--(iter->value)) {
m_pageOccupancyMap.remove(iter);
m_bytesCommitted -= m_pageSize;
if (!currentPageStart)
currentPageStart = page;
++count;
} else
flushFreePages();
}
flushFreePages();
}
bool MetaAllocator::isInAllocatedMemory(const AbstractLocker&, void* address)
{
ASSERT(m_lock.isLocked());
uintptr_t page = reinterpret_cast<uintptr_t>(address) >> m_logPageSize;
return m_pageOccupancyMap.contains(page);
}
size_t MetaAllocator::roundUp(size_t sizeInBytes)
{
if (std::numeric_limits<size_t>::max() - m_allocationGranule <= sizeInBytes)
CRASH();
return (sizeInBytes + m_allocationGranule - 1) & ~(m_allocationGranule - 1);
}
MetaAllocator::FreeSpaceNode* MetaAllocator::allocFreeSpaceNode()
{
#ifndef NDEBUG
m_mallocBalance++;
#endif
return new (NotNull, MetaAllocatorFreeSpaceMalloc::malloc(sizeof(FreeSpaceNode))) FreeSpaceNode();
}
void MetaAllocator::freeFreeSpaceNode(FreeSpaceNode* node)
{
#ifndef NDEBUG
m_mallocBalance--;
#endif
MetaAllocatorFreeSpaceMalloc::free(node);
}
#if ENABLE(META_ALLOCATOR_PROFILE)
void MetaAllocator::dumpProfile()
{
dataLogF(
"%d: MetaAllocator(%p): num allocations = %u, num frees = %u, allocated = %lu, reserved = %lu, committed = %lu\n",
getCurrentProcessID(), this, m_numAllocations, m_numFrees, m_bytesAllocated, m_bytesReserved, m_bytesCommitted);
}
#endif
} // namespace WTF