/* * 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 #include #include 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 freeEnd = freeStartValue + freeSize; uintptr_t firstCompletelyFreePage = roundUpToMultipleOf(allocator.m_pageSize, freeStartValue); if (firstCompletelyFreePage < freeEnd) allocator.decrementPageOccupancy(reinterpret_cast(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(1) << m_logPageSize == m_pageSize) break; } ASSERT(static_cast(1) << m_logPageSize == m_pageSize); for (m_logAllocationGranule = 0; m_logAllocationGranule < 32; ++m_logAllocationGranule) { if (static_cast(1) << m_logAllocationGranule == m_allocationGranule) break; } ASSERT(static_cast(1) << m_logAllocationGranule == m_allocationGranule); } RefPtr 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 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() && node->m_start.untaggedPtr() < node->m_end.untaggedPtr()); 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::iterator leftNeighbor = m_freeSpaceEndAddressMap.find(start); HashMap::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(address) >> m_logPageSize; uintptr_t lastPage = (reinterpret_cast(address) + sizeInBytes - 1) >> m_logPageSize; uintptr_t currentPageStart = 0; size_t count = 0; auto flushNeedPages = [&] { if (!currentPageStart) return; notifyNeedPage(reinterpret_cast(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(address) >> m_logPageSize; uintptr_t lastPage = (reinterpret_cast(address) + sizeInBytes - 1) >> m_logPageSize; uintptr_t currentPageStart = 0; size_t count = 0; auto flushFreePages = [&] { if (!currentPageStart) return; notifyPageIsFree(reinterpret_cast(currentPageStart << m_logPageSize), count); currentPageStart = 0; count = 0; }; for (uintptr_t page = firstPage; page <= lastPage; ++page) { HashMap::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(address) >> m_logPageSize; return m_pageOccupancyMap.contains(page); } size_t MetaAllocator::roundUp(size_t sizeInBytes) { if (std::numeric_limits::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