| /* |
| * Copyright (C) 2013 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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/PartitionAlloc.h" |
| |
| #include <string.h> |
| |
| #ifndef NDEBUG |
| #include <stdio.h> |
| #endif |
| |
| // Two partition pages are used as guard / metadata page so make sure the super |
| // page size is bigger. |
| COMPILE_ASSERT(WTF::kPartitionPageSize * 4 <= WTF::kSuperPageSize, ok_super_page_size); |
| COMPILE_ASSERT(!(WTF::kSuperPageSize % WTF::kPartitionPageSize), ok_super_page_multiple); |
| // Four system pages gives us room to hack out a still-guard-paged piece |
| // of metadata in the middle of a guard partition page. |
| COMPILE_ASSERT(WTF::kSystemPageSize * 4 <= WTF::kPartitionPageSize, ok_partition_page_size); |
| COMPILE_ASSERT(!(WTF::kPartitionPageSize % WTF::kSystemPageSize), ok_partition_page_multiple); |
| COMPILE_ASSERT(sizeof(WTF::PartitionPage) <= WTF::kPageMetadataSize, PartitionPage_not_too_big); |
| COMPILE_ASSERT(sizeof(WTF::PartitionSuperPageExtentEntry) <= WTF::kPageMetadataSize, PartitionSuperPageExtentEntry_not_too_big); |
| COMPILE_ASSERT(WTF::kPageMetadataSize * WTF::kNumPartitionPagesPerSuperPage <= WTF::kSystemPageSize, page_metadata_fits_in_hole); |
| |
| namespace WTF { |
| |
| static size_t partitionBucketPageSize(size_t size) |
| { |
| double bestWasteRatio = 1.0f; |
| size_t bestPages = 0; |
| for (size_t i = kNumSystemPagesPerPartitionPage - 1; i <= kNumSystemPagesPerPartitionPage; ++i) { |
| size_t pageSize = kSystemPageSize * i; |
| size_t numSlots = pageSize / size; |
| size_t waste = pageSize - (numSlots * size); |
| // Leave a page unfaulted is not free; the page will occupy an empty page table entry. |
| // Make a simple attempt to account for that. |
| waste += sizeof(void*) * (kNumSystemPagesPerPartitionPage - i); |
| double wasteRatio = (double) waste / (double) pageSize; |
| if (wasteRatio < bestWasteRatio) { |
| bestWasteRatio = wasteRatio; |
| bestPages = i; |
| } |
| } |
| ASSERT(bestPages > 0); |
| return bestPages * kSystemPageSize; |
| } |
| |
| static ALWAYS_INLINE void partitionPageMarkFree(PartitionPage* page) |
| { |
| ASSERT(!partitionPageIsFree(page)); |
| page->numAllocatedSlots = -1; |
| } |
| |
| WTF_EXPORT void partitionAllocInit(PartitionRoot* root, size_t numBuckets, size_t maxAllocation) |
| { |
| ASSERT(!root->initialized); |
| root->initialized = true; |
| root->lock = 0; |
| root->totalSizeOfSuperPages = 0; |
| root->numBuckets = numBuckets; |
| root->maxAllocation = maxAllocation; |
| size_t i; |
| for (i = 0; i < root->numBuckets; ++i) { |
| PartitionBucket* bucket = &root->buckets()[i]; |
| bucket->root = root; |
| bucket->activePagesHead = &root->seedPage; |
| bucket->freePagesHead = 0; |
| bucket->numFullPages = 0; |
| size_t size = partitionBucketSize(bucket); |
| bucket->pageSize = partitionBucketPageSize(size); |
| } |
| |
| root->nextSuperPage = 0; |
| root->nextPartitionPage = 0; |
| root->nextPartitionPageEnd = 0; |
| root->currentExtent = &root->firstExtent; |
| root->firstExtent.superPageBase = 0; |
| root->firstExtent.superPagesEnd = 0; |
| root->firstExtent.next = 0; |
| root->seedPage.bucket = &root->seedBucket; |
| root->seedPage.activePageNext = 0; |
| root->seedPage.numAllocatedSlots = 0; |
| root->seedPage.numUnprovisionedSlots = 0; |
| partitionPageSetFreelistHead(&root->seedPage, 0); |
| // We mark the seed page as free to make sure it is skipped by our logic to |
| // find a new active page. |
| partitionPageMarkFree(&root->seedPage); |
| |
| root->seedBucket.root = root; |
| root->seedBucket.activePagesHead = 0; |
| root->seedBucket.freePagesHead = 0; |
| root->seedBucket.numFullPages = 0; |
| } |
| |
| static bool partitionAllocShutdownBucket(PartitionBucket* bucket) |
| { |
| // Failure here indicates a memory leak. |
| bool noLeaks = !bucket->numFullPages; |
| PartitionPage* page = bucket->activePagesHead; |
| if (page == &bucket->root->seedPage) |
| return noLeaks; |
| do { |
| if (page->numAllocatedSlots) |
| noLeaks = false; |
| page = page->activePageNext; |
| } while (page); |
| |
| return noLeaks; |
| } |
| |
| bool partitionAllocShutdown(PartitionRoot* root) |
| { |
| bool noLeaks = true; |
| ASSERT(root->initialized); |
| root->initialized = false; |
| size_t i; |
| for (i = 0; i < root->numBuckets; ++i) { |
| PartitionBucket* bucket = &root->buckets()[i]; |
| if (!partitionAllocShutdownBucket(bucket)) |
| noLeaks = false; |
| } |
| |
| // Now that we've examined all partition pages in all buckets, it's safe |
| // to free all our super pages. We first collect the super page pointers |
| // on the stack because some of them are themselves store in super pages. |
| char* superPages[kMaxPartitionSize / kSuperPageSize]; |
| size_t numSuperPages = 0; |
| PartitionSuperPageExtentEntry* entry = &root->firstExtent; |
| while (entry) { |
| char* superPage = entry->superPageBase; |
| while (superPage != entry->superPagesEnd) { |
| superPages[numSuperPages] = superPage; |
| numSuperPages++; |
| superPage += kSuperPageSize; |
| } |
| entry = entry->next; |
| } |
| ASSERT(numSuperPages == root->totalSizeOfSuperPages / kSuperPageSize); |
| for (size_t i = 0; i < numSuperPages; ++i) { |
| SuperPageBitmap::unregisterSuperPage(superPages[i]); |
| freePages(superPages[i], kSuperPageSize); |
| } |
| |
| return noLeaks; |
| } |
| |
| static ALWAYS_INLINE void* partitionAllocPage(PartitionRoot* root) |
| { |
| if (LIKELY(root->nextPartitionPage != 0)) { |
| // In this case, we can still hand out pages from a previous |
| // super page allocation. |
| char* ret = root->nextPartitionPage; |
| root->nextPartitionPage += kPartitionPageSize; |
| if (UNLIKELY(root->nextPartitionPage == root->nextPartitionPageEnd)) { |
| // We ran out, need to get more pages next time. |
| root->nextPartitionPage = 0; |
| root->nextPartitionPageEnd = 0; |
| } |
| return ret; |
| } |
| |
| // Need a new super page. |
| root->totalSizeOfSuperPages += kSuperPageSize; |
| RELEASE_ASSERT(root->totalSizeOfSuperPages <= kMaxPartitionSize); |
| char* requestedAddress = root->nextSuperPage; |
| char* superPage = reinterpret_cast<char*>(allocPages(requestedAddress, kSuperPageSize, kSuperPageSize)); |
| SuperPageBitmap::registerSuperPage(superPage); |
| root->nextSuperPage = superPage + kSuperPageSize; |
| char* ret = superPage + kPartitionPageSize; |
| root->nextPartitionPage = ret + kPartitionPageSize; |
| root->nextPartitionPageEnd = root->nextSuperPage - kPartitionPageSize; |
| // Make the first partition page in the super page a guard page, but leave a // hole in the middle. |
| // This is where we put page metadata and also a tiny amount of extent |
| // metadata. |
| setSystemPagesInaccessible(superPage, kSystemPageSize); |
| setSystemPagesInaccessible(superPage + (kSystemPageSize * 2), kPartitionPageSize - (kSystemPageSize * 2)); |
| // Also make the last partition page a guard page. |
| setSystemPagesInaccessible(superPage + (kSuperPageSize - kPartitionPageSize), kPartitionPageSize); |
| |
| // If we were after a specific address, but didn't get it, assume that |
| // the system chose a lousy address and re-randomize the next |
| // allocation. |
| if (requestedAddress && requestedAddress != superPage) |
| root->nextSuperPage = 0; |
| |
| // We allocated a new super page so update super page metadata. |
| // First check if this is a new extent or not. |
| PartitionSuperPageExtentEntry* currentExtent = root->currentExtent; |
| bool isNewExtent = (superPage != requestedAddress); |
| if (UNLIKELY(isNewExtent)) { |
| if (currentExtent->superPageBase) { |
| // We already have a super page, so need to allocate metadata in the linked list. |
| PartitionSuperPageExtentEntry* newEntry = reinterpret_cast<PartitionSuperPageExtentEntry*>(partitionSuperPageToMetadataArea(currentExtent->superPageBase)); |
| newEntry->next = 0; |
| currentExtent->next = newEntry; |
| currentExtent = newEntry; |
| root->currentExtent = newEntry; |
| } |
| currentExtent->superPageBase = superPage; |
| currentExtent->superPagesEnd = superPage + kSuperPageSize; |
| } else { |
| // We allocated next to an existing extent so just nudge the size up a little. |
| currentExtent->superPagesEnd += kSuperPageSize; |
| ASSERT(ret >= currentExtent->superPageBase && ret < currentExtent->superPagesEnd); |
| } |
| |
| return ret; |
| } |
| |
| static ALWAYS_INLINE void partitionUnusePage(PartitionPage* page) |
| { |
| void* addr = partitionPageToPointer(page); |
| decommitSystemPages(addr, kPartitionPageSize); |
| } |
| |
| static ALWAYS_INLINE size_t partitionBucketSlots(const PartitionBucket* bucket) |
| { |
| return bucket->pageSize / partitionBucketSize(bucket); |
| } |
| |
| static ALWAYS_INLINE void partitionPageReset(PartitionPage* page, PartitionBucket* bucket) |
| { |
| ASSERT(page != &bucket->root->seedPage); |
| page->numAllocatedSlots = 0; |
| page->numUnprovisionedSlots = partitionBucketSlots(bucket); |
| ASSERT(page->numUnprovisionedSlots > 1); |
| page->bucket = bucket; |
| // NULLing the freelist is not strictly necessary but it makes an ASSERT in partitionPageFillFreelist simpler. |
| partitionPageSetFreelistHead(page, 0); |
| } |
| |
| static ALWAYS_INLINE char* partitionPageAllocAndFillFreelist(PartitionPage* page) |
| { |
| ASSERT(page != &page->bucket->root->seedPage); |
| size_t numSlots = page->numUnprovisionedSlots; |
| ASSERT(numSlots); |
| PartitionBucket* bucket = page->bucket; |
| // We should only get here when _every_ slot is either used or unprovisioned. |
| // (The third state is "on the freelist". If we have a non-empty freelist, we should not get here.) |
| ASSERT(numSlots + page->numAllocatedSlots == partitionBucketSlots(bucket)); |
| // Similarly, make explicitly sure that the freelist is empty. |
| ASSERT(!partitionPageFreelistHead(page)); |
| |
| size_t size = partitionBucketSize(bucket); |
| char* base = reinterpret_cast<char*>(partitionPageToPointer(page)); |
| char* returnObject = base + (size * page->numAllocatedSlots); |
| char* nextFreeObject = returnObject + size; |
| char* subPageLimit = reinterpret_cast<char*>((reinterpret_cast<uintptr_t>(returnObject) + kSystemPageSize) & kSystemPageBaseMask); |
| |
| size_t numNewFreelistEntries = 0; |
| if (LIKELY(subPageLimit > nextFreeObject)) |
| numNewFreelistEntries = (subPageLimit - nextFreeObject) / size; |
| |
| // We always return an object slot -- that's the +1 below. |
| // We do not neccessarily create any new freelist entries, because we cross sub page boundaries frequently for large bucket sizes. |
| numSlots -= (numNewFreelistEntries + 1); |
| page->numUnprovisionedSlots = numSlots; |
| page->numAllocatedSlots++; |
| |
| if (LIKELY(numNewFreelistEntries)) { |
| PartitionFreelistEntry* entry = reinterpret_cast<PartitionFreelistEntry*>(nextFreeObject); |
| partitionPageSetFreelistHead(page, entry); |
| while (--numNewFreelistEntries) { |
| nextFreeObject += size; |
| PartitionFreelistEntry* nextEntry = reinterpret_cast<PartitionFreelistEntry*>(nextFreeObject); |
| entry->next = partitionFreelistMask(nextEntry); |
| entry = nextEntry; |
| } |
| entry->next = partitionFreelistMask(0); |
| } else { |
| partitionPageSetFreelistHead(page, 0); |
| } |
| return returnObject; |
| } |
| |
| // This helper function scans the active page list for a suitable new active |
| // page, starting at the page passed in. When it finds a suitable new active |
| // page (one that has free slots), it is also set as the new active page. If |
| // there is no suitable new active page, the current active page is set to null. |
| static ALWAYS_INLINE void partitionSetNewActivePage(PartitionBucket* bucket, PartitionPage* page) |
| { |
| ASSERT(page == &bucket->root->seedPage || page->bucket == bucket); |
| for (; page; page = page->activePageNext) { |
| // Skip over free pages. We need this check first so that we can trust |
| // that the page union field really containts a freelist pointer. |
| if (UNLIKELY(partitionPageIsFree(page))) |
| continue; |
| |
| // Page is usable if it has something on the freelist, or unprovisioned |
| // slots that can be turned into a freelist. |
| if (LIKELY(partitionPageFreelistHead(page) != 0) || LIKELY(page->numUnprovisionedSlots)) |
| break; |
| // If we get here, we found a full page. Skip over it too, and also tag |
| // it as full (via a negative value). We need it tagged so that free'ing |
| // can tell, and move it back into the active page list. |
| ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots(bucket))); |
| page->numAllocatedSlots = -page->numAllocatedSlots; |
| ++bucket->numFullPages; |
| } |
| |
| bucket->activePagesHead = page; |
| } |
| |
| static ALWAYS_INLINE void partitionPageSetFreePageNext(PartitionPage* page, PartitionPage* nextFree) |
| { |
| ASSERT(partitionPageIsFree(page)); |
| page->u.freePageNext = nextFree; |
| } |
| |
| static ALWAYS_INLINE PartitionPage* partitionPageFreePageNext(PartitionPage* page) |
| { |
| ASSERT(partitionPageIsFree(page)); |
| return page->u.freePageNext; |
| } |
| |
| void* partitionAllocSlowPath(PartitionBucket* bucket) |
| { |
| // The slow path is called when the freelist is empty. |
| ASSERT(!partitionPageFreelistHead(bucket->activePagesHead)); |
| |
| // First, look for a usable page in the existing active pages list. |
| PartitionPage* page = bucket->activePagesHead; |
| partitionSetNewActivePage(bucket, page); |
| page = bucket->activePagesHead; |
| if (LIKELY(page != 0)) { |
| if (LIKELY(partitionPageFreelistHead(page) != 0)) { |
| PartitionFreelistEntry* ret = partitionPageFreelistHead(page); |
| partitionPageSetFreelistHead(page, partitionFreelistMask(ret->next)); |
| page->numAllocatedSlots++; |
| return ret; |
| } |
| ASSERT(page->numUnprovisionedSlots); |
| return partitionPageAllocAndFillFreelist(page); |
| } |
| |
| // Second, look in our list of freed but reserved pages. |
| PartitionPage* newPage = bucket->freePagesHead; |
| if (LIKELY(newPage != 0)) { |
| ASSERT(newPage != &bucket->root->seedPage); |
| bucket->freePagesHead = partitionPageFreePageNext(newPage); |
| } else { |
| // Third. If we get here, we need a brand new page. |
| void* rawNewPage = partitionAllocPage(bucket->root); |
| newPage = partitionPointerToPageNoAlignmentCheck(rawNewPage); |
| } |
| |
| newPage->activePageNext = 0; |
| partitionPageReset(newPage, bucket); |
| bucket->activePagesHead = newPage; |
| return partitionPageAllocAndFillFreelist(newPage); |
| } |
| |
| void partitionFreeSlowPath(PartitionPage* page) |
| { |
| PartitionBucket* bucket = page->bucket; |
| ASSERT(page != &bucket->root->seedPage); |
| ASSERT(bucket->activePagesHead != &bucket->root->seedPage); |
| if (LIKELY(page->numAllocatedSlots == 0)) { |
| // Page became fully unused. |
| // If it's the current page, change it! |
| if (LIKELY(page == bucket->activePagesHead)) { |
| PartitionPage* newPage = 0; |
| if (page->activePageNext) { |
| partitionSetNewActivePage(bucket, page->activePageNext); |
| newPage = bucket->activePagesHead; |
| } |
| |
| ASSERT(newPage != page); |
| if (UNLIKELY(!newPage)) { |
| // We do not free the last active page in a bucket. |
| // To do so is a serious performance issue as we can get into |
| // a repeating state change between 0 and 1 objects of a given |
| // size allocated; and each state change incurs either a system |
| // call or a page fault, or more. |
| page->activePageNext = 0; |
| bucket->activePagesHead = page; |
| return; |
| } |
| bucket->activePagesHead = newPage; |
| } |
| |
| partitionUnusePage(page); |
| // We actually leave the freed page in the active list as well as |
| // putting it on the free list. We'll evict it from the active list soon |
| // enough in partitionAllocSlowPath. Pulling this trick enables us to |
| // use a singly-linked page list for all cases, which is critical in |
| // keeping the page metadata structure down to 32-bytes in size. |
| partitionPageMarkFree(page); |
| partitionPageSetFreePageNext(page, bucket->freePagesHead); |
| bucket->freePagesHead = page; |
| } else { |
| // Ensure that the page is full. That's the only valid case if we |
| // arrive here. |
| ASSERT(page->numAllocatedSlots < 0); |
| // Fully used page became partially used. It must be put back on the |
| // non-full page list. Also make it the current page to increase the |
| // chances of it being filled up again. The old current page will be |
| // the next page. |
| page->numAllocatedSlots = -page->numAllocatedSlots - 2; |
| ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots(bucket) - 1)); |
| page->activePageNext = bucket->activePagesHead; |
| bucket->activePagesHead = page; |
| --bucket->numFullPages; |
| // Note: there's an opportunity here to look to see if the old active |
| // page is now freeable. |
| } |
| } |
| |
| void* partitionReallocGeneric(PartitionRoot* root, void* ptr, size_t newSize) |
| { |
| RELEASE_ASSERT(newSize <= QuantizedAllocation::kMaxUnquantizedAllocation); |
| #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) |
| return realloc(ptr, newSize); |
| #else |
| size_t oldIndex; |
| if (LIKELY(partitionPointerIsValid(root, ptr))) { |
| void* realPtr = partitionCookieFreePointerAdjust(ptr); |
| PartitionBucket* bucket = partitionPointerToPage(realPtr)->bucket; |
| ASSERT(bucket->root == root); |
| oldIndex = bucket - root->buckets(); |
| } else { |
| oldIndex = root->numBuckets; |
| } |
| |
| size_t allocSize = QuantizedAllocation::quantizedSize(newSize); |
| allocSize = partitionCookieSizeAdjustAdd(allocSize); |
| size_t newIndex = allocSize >> kBucketShift; |
| if (newIndex > root->numBuckets) |
| newIndex = root->numBuckets; |
| |
| if (oldIndex == newIndex) { |
| // Same bucket. But kNumBuckets indicates the fastMalloc "bucket" so in |
| // that case we're not done; we have to forward to fastRealloc. |
| if (oldIndex == root->numBuckets) |
| return WTF::fastRealloc(ptr, newSize); |
| return ptr; |
| } |
| // This realloc cannot be resized in-place. Sadness. |
| void* ret = partitionAllocGeneric(root, newSize); |
| size_t copySize = oldIndex << kBucketShift; |
| copySize = partitionCookieSizeAdjustSubtract(copySize); |
| if (newSize < copySize) |
| copySize = newSize; |
| memcpy(ret, ptr, copySize); |
| partitionFreeGeneric(root, ptr); |
| return ret; |
| #endif |
| } |
| |
| #if CPU(32BIT) |
| unsigned char SuperPageBitmap::s_bitmap[1 << (32 - kSuperPageShift - 3)]; |
| |
| static int bitmapLock = 0; |
| |
| void SuperPageBitmap::registerSuperPage(void* ptr) |
| { |
| ASSERT(!isPointerInSuperPage(ptr)); |
| uintptr_t raw = reinterpret_cast<uintptr_t>(ptr); |
| raw >>= kSuperPageShift; |
| size_t byteIndex = raw >> 3; |
| size_t bit = raw & 7; |
| ASSERT(byteIndex < sizeof(s_bitmap)); |
| // The read/modify/write is not guaranteed atomic, so take a lock. |
| spinLockLock(&bitmapLock); |
| s_bitmap[byteIndex] |= (1 << bit); |
| spinLockUnlock(&bitmapLock); |
| } |
| |
| void SuperPageBitmap::unregisterSuperPage(void* ptr) |
| { |
| ASSERT(isPointerInSuperPage(ptr)); |
| uintptr_t raw = reinterpret_cast<uintptr_t>(ptr); |
| raw >>= kSuperPageShift; |
| size_t byteIndex = raw >> 3; |
| size_t bit = raw & 7; |
| ASSERT(byteIndex < sizeof(s_bitmap)); |
| // The read/modify/write is not guaranteed atomic, so take a lock. |
| spinLockLock(&bitmapLock); |
| s_bitmap[byteIndex] &= ~(1 << bit); |
| spinLockUnlock(&bitmapLock); |
| } |
| #endif |
| |
| #ifndef NDEBUG |
| |
| void partitionDumpStats(const PartitionRoot& root) |
| { |
| size_t i; |
| size_t totalLive = 0; |
| size_t totalResident = 0; |
| size_t totalFreeable = 0; |
| for (i = 0; i < root.numBuckets; ++i) { |
| const PartitionBucket& bucket = root.buckets()[i]; |
| if (bucket.activePagesHead == &bucket.root->seedPage && !bucket.freePagesHead && !bucket.numFullPages) { |
| // Empty bucket with no freelist or full pages. Skip reporting it. |
| continue; |
| } |
| size_t numFreePages = 0; |
| PartitionPage* freePages = bucket.freePagesHead; |
| while (freePages) { |
| ++numFreePages; |
| freePages = partitionPageFreePageNext(freePages); |
| } |
| size_t bucketSlotSize = partitionBucketSize(&bucket); |
| size_t bucketNumSlots = partitionBucketSlots(&bucket); |
| size_t bucketUsefulStorage = bucketSlotSize * bucketNumSlots; |
| size_t bucketWaste = bucket.pageSize - bucketUsefulStorage; |
| size_t numActiveBytes = bucket.numFullPages * bucketUsefulStorage; |
| size_t numResidentBytes = bucket.numFullPages * bucket.pageSize; |
| size_t numFreeableBytes = 0; |
| size_t numActivePages = 0; |
| const PartitionPage* page = bucket.activePagesHead; |
| do { |
| if (page != &bucket.root->seedPage) { |
| ++numActivePages; |
| numActiveBytes += (page->numAllocatedSlots * bucketSlotSize); |
| size_t pageBytesResident = (bucketNumSlots - page->numUnprovisionedSlots) * bucketSlotSize; |
| // Round up to system page size. |
| pageBytesResident = (pageBytesResident + kSystemPageOffsetMask) & kSystemPageBaseMask; |
| numResidentBytes += pageBytesResident; |
| if (!page->numAllocatedSlots) |
| numFreeableBytes += pageBytesResident; |
| } |
| page = page->activePageNext; |
| } while (page != bucket.activePagesHead); |
| totalLive += numActiveBytes; |
| totalResident += numResidentBytes; |
| totalFreeable += numFreeableBytes; |
| printf("bucket size %zu (pageSize %zu waste %zu): %zu alloc/%zu commit/%zu freeable bytes, %zu/%zu/%zu full/active/free pages\n", bucketSlotSize, static_cast<size_t>(bucket.pageSize), bucketWaste, numActiveBytes, numResidentBytes, numFreeableBytes, static_cast<size_t>(bucket.numFullPages), numActivePages, numFreePages); |
| } |
| printf("total live: %zu bytes\n", totalLive); |
| printf("total resident: %zu bytes\n", totalResident); |
| printf("total freeable: %zu bytes\n", totalFreeable); |
| fflush(stdout); |
| } |
| |
| #endif // !NDEBUG |
| |
| } // namespace WTF |
| |