1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "wtf/PartitionAlloc.h"
33
34 #include <string.h>
35
36 #ifndef NDEBUG
37 #include <stdio.h>
38 #endif
39
40 // Two partition pages are used as guard / metadata page so make sure the super
41 // page size is bigger.
42 COMPILE_ASSERT(WTF::kPartitionPageSize * 4 <= WTF::kSuperPageSize, ok_super_page_size);
43 COMPILE_ASSERT(!(WTF::kSuperPageSize % WTF::kPartitionPageSize), ok_super_page_multiple);
44 // Four system pages gives us room to hack out a still-guard-paged piece
45 // of metadata in the middle of a guard partition page.
46 COMPILE_ASSERT(WTF::kSystemPageSize * 4 <= WTF::kPartitionPageSize, ok_partition_page_size);
47 COMPILE_ASSERT(!(WTF::kPartitionPageSize % WTF::kSystemPageSize), ok_partition_page_multiple);
48 COMPILE_ASSERT(sizeof(WTF::PartitionPage) <= WTF::kPageMetadataSize, PartitionPage_not_too_big);
49 COMPILE_ASSERT(sizeof(WTF::PartitionSuperPageExtentEntry) <= WTF::kPageMetadataSize, PartitionSuperPageExtentEntry_not_too_big);
50 COMPILE_ASSERT(WTF::kPageMetadataSize * WTF::kNumPartitionPagesPerSuperPage <= WTF::kSystemPageSize, page_metadata_fits_in_hole);
51
52 namespace WTF {
53
partitionBucketPageSize(size_t size)54 static size_t partitionBucketPageSize(size_t size)
55 {
56 double bestWasteRatio = 1.0f;
57 size_t bestPages = 0;
58 for (size_t i = kNumSystemPagesPerPartitionPage - 1; i <= kNumSystemPagesPerPartitionPage; ++i) {
59 size_t pageSize = kSystemPageSize * i;
60 size_t numSlots = pageSize / size;
61 size_t waste = pageSize - (numSlots * size);
62 // Leave a page unfaulted is not free; the page will occupy an empty page table entry.
63 // Make a simple attempt to account for that.
64 waste += sizeof(void*) * (kNumSystemPagesPerPartitionPage - i);
65 double wasteRatio = (double) waste / (double) pageSize;
66 if (wasteRatio < bestWasteRatio) {
67 bestWasteRatio = wasteRatio;
68 bestPages = i;
69 }
70 }
71 ASSERT(bestPages > 0);
72 return bestPages * kSystemPageSize;
73 }
74
partitionPageMarkFree(PartitionPage * page)75 static ALWAYS_INLINE void partitionPageMarkFree(PartitionPage* page)
76 {
77 ASSERT(!partitionPageIsFree(page));
78 page->numAllocatedSlots = -1;
79 }
80
partitionAllocInit(PartitionRoot * root,size_t numBuckets,size_t maxAllocation)81 WTF_EXPORT void partitionAllocInit(PartitionRoot* root, size_t numBuckets, size_t maxAllocation)
82 {
83 ASSERT(!root->initialized);
84 root->initialized = true;
85 root->lock = 0;
86 root->totalSizeOfSuperPages = 0;
87 root->numBuckets = numBuckets;
88 root->maxAllocation = maxAllocation;
89 size_t i;
90 for (i = 0; i < root->numBuckets; ++i) {
91 PartitionBucket* bucket = &root->buckets()[i];
92 bucket->root = root;
93 bucket->activePagesHead = &root->seedPage;
94 bucket->freePagesHead = 0;
95 bucket->numFullPages = 0;
96 size_t size = partitionBucketSize(bucket);
97 bucket->pageSize = partitionBucketPageSize(size);
98 }
99
100 root->nextSuperPage = 0;
101 root->nextPartitionPage = 0;
102 root->nextPartitionPageEnd = 0;
103 root->currentExtent = &root->firstExtent;
104 root->firstExtent.superPageBase = 0;
105 root->firstExtent.superPagesEnd = 0;
106 root->firstExtent.next = 0;
107 root->seedPage.bucket = &root->seedBucket;
108 root->seedPage.activePageNext = 0;
109 root->seedPage.numAllocatedSlots = 0;
110 root->seedPage.numUnprovisionedSlots = 0;
111 partitionPageSetFreelistHead(&root->seedPage, 0);
112 // We mark the seed page as free to make sure it is skipped by our logic to
113 // find a new active page.
114 partitionPageMarkFree(&root->seedPage);
115
116 root->seedBucket.root = root;
117 root->seedBucket.activePagesHead = 0;
118 root->seedBucket.freePagesHead = 0;
119 root->seedBucket.numFullPages = 0;
120 }
121
partitionAllocShutdownBucket(PartitionBucket * bucket)122 static bool partitionAllocShutdownBucket(PartitionBucket* bucket)
123 {
124 // Failure here indicates a memory leak.
125 bool noLeaks = !bucket->numFullPages;
126 PartitionPage* page = bucket->activePagesHead;
127 if (page == &bucket->root->seedPage)
128 return noLeaks;
129 do {
130 if (page->numAllocatedSlots)
131 noLeaks = false;
132 page = page->activePageNext;
133 } while (page);
134
135 return noLeaks;
136 }
137
partitionAllocShutdown(PartitionRoot * root)138 bool partitionAllocShutdown(PartitionRoot* root)
139 {
140 bool noLeaks = true;
141 ASSERT(root->initialized);
142 root->initialized = false;
143 size_t i;
144 for (i = 0; i < root->numBuckets; ++i) {
145 PartitionBucket* bucket = &root->buckets()[i];
146 if (!partitionAllocShutdownBucket(bucket))
147 noLeaks = false;
148 }
149
150 // Now that we've examined all partition pages in all buckets, it's safe
151 // to free all our super pages. We first collect the super page pointers
152 // on the stack because some of them are themselves store in super pages.
153 char* superPages[kMaxPartitionSize / kSuperPageSize];
154 size_t numSuperPages = 0;
155 PartitionSuperPageExtentEntry* entry = &root->firstExtent;
156 while (entry) {
157 char* superPage = entry->superPageBase;
158 while (superPage != entry->superPagesEnd) {
159 superPages[numSuperPages] = superPage;
160 numSuperPages++;
161 superPage += kSuperPageSize;
162 }
163 entry = entry->next;
164 }
165 ASSERT(numSuperPages == root->totalSizeOfSuperPages / kSuperPageSize);
166 for (size_t i = 0; i < numSuperPages; ++i) {
167 SuperPageBitmap::unregisterSuperPage(superPages[i]);
168 freePages(superPages[i], kSuperPageSize);
169 }
170
171 return noLeaks;
172 }
173
partitionAllocPage(PartitionRoot * root)174 static ALWAYS_INLINE void* partitionAllocPage(PartitionRoot* root)
175 {
176 if (LIKELY(root->nextPartitionPage != 0)) {
177 // In this case, we can still hand out pages from a previous
178 // super page allocation.
179 char* ret = root->nextPartitionPage;
180 root->nextPartitionPage += kPartitionPageSize;
181 if (UNLIKELY(root->nextPartitionPage == root->nextPartitionPageEnd)) {
182 // We ran out, need to get more pages next time.
183 root->nextPartitionPage = 0;
184 root->nextPartitionPageEnd = 0;
185 }
186 return ret;
187 }
188
189 // Need a new super page.
190 root->totalSizeOfSuperPages += kSuperPageSize;
191 RELEASE_ASSERT(root->totalSizeOfSuperPages <= kMaxPartitionSize);
192 char* requestedAddress = root->nextSuperPage;
193 char* superPage = reinterpret_cast<char*>(allocPages(requestedAddress, kSuperPageSize, kSuperPageSize));
194 SuperPageBitmap::registerSuperPage(superPage);
195 root->nextSuperPage = superPage + kSuperPageSize;
196 char* ret = superPage + kPartitionPageSize;
197 root->nextPartitionPage = ret + kPartitionPageSize;
198 root->nextPartitionPageEnd = root->nextSuperPage - kPartitionPageSize;
199 // Make the first partition page in the super page a guard page, but leave a // hole in the middle.
200 // This is where we put page metadata and also a tiny amount of extent
201 // metadata.
202 setSystemPagesInaccessible(superPage, kSystemPageSize);
203 setSystemPagesInaccessible(superPage + (kSystemPageSize * 2), kPartitionPageSize - (kSystemPageSize * 2));
204 // Also make the last partition page a guard page.
205 setSystemPagesInaccessible(superPage + (kSuperPageSize - kPartitionPageSize), kPartitionPageSize);
206
207 // If we were after a specific address, but didn't get it, assume that
208 // the system chose a lousy address and re-randomize the next
209 // allocation.
210 if (requestedAddress && requestedAddress != superPage)
211 root->nextSuperPage = 0;
212
213 // We allocated a new super page so update super page metadata.
214 // First check if this is a new extent or not.
215 PartitionSuperPageExtentEntry* currentExtent = root->currentExtent;
216 bool isNewExtent = (superPage != requestedAddress);
217 if (UNLIKELY(isNewExtent)) {
218 if (currentExtent->superPageBase) {
219 // We already have a super page, so need to allocate metadata in the linked list.
220 PartitionSuperPageExtentEntry* newEntry = reinterpret_cast<PartitionSuperPageExtentEntry*>(partitionSuperPageToMetadataArea(currentExtent->superPageBase));
221 newEntry->next = 0;
222 currentExtent->next = newEntry;
223 currentExtent = newEntry;
224 root->currentExtent = newEntry;
225 }
226 currentExtent->superPageBase = superPage;
227 currentExtent->superPagesEnd = superPage + kSuperPageSize;
228 } else {
229 // We allocated next to an existing extent so just nudge the size up a little.
230 currentExtent->superPagesEnd += kSuperPageSize;
231 ASSERT(ret >= currentExtent->superPageBase && ret < currentExtent->superPagesEnd);
232 }
233
234 return ret;
235 }
236
partitionUnusePage(PartitionPage * page)237 static ALWAYS_INLINE void partitionUnusePage(PartitionPage* page)
238 {
239 void* addr = partitionPageToPointer(page);
240 decommitSystemPages(addr, kPartitionPageSize);
241 }
242
partitionBucketSlots(const PartitionBucket * bucket)243 static ALWAYS_INLINE size_t partitionBucketSlots(const PartitionBucket* bucket)
244 {
245 return bucket->pageSize / partitionBucketSize(bucket);
246 }
247
partitionPageReset(PartitionPage * page,PartitionBucket * bucket)248 static ALWAYS_INLINE void partitionPageReset(PartitionPage* page, PartitionBucket* bucket)
249 {
250 ASSERT(page != &bucket->root->seedPage);
251 page->numAllocatedSlots = 0;
252 page->numUnprovisionedSlots = partitionBucketSlots(bucket);
253 ASSERT(page->numUnprovisionedSlots > 1);
254 page->bucket = bucket;
255 // NULLing the freelist is not strictly necessary but it makes an ASSERT in partitionPageFillFreelist simpler.
256 partitionPageSetFreelistHead(page, 0);
257 }
258
partitionPageAllocAndFillFreelist(PartitionPage * page)259 static ALWAYS_INLINE char* partitionPageAllocAndFillFreelist(PartitionPage* page)
260 {
261 ASSERT(page != &page->bucket->root->seedPage);
262 size_t numSlots = page->numUnprovisionedSlots;
263 ASSERT(numSlots);
264 PartitionBucket* bucket = page->bucket;
265 // We should only get here when _every_ slot is either used or unprovisioned.
266 // (The third state is "on the freelist". If we have a non-empty freelist, we should not get here.)
267 ASSERT(numSlots + page->numAllocatedSlots == partitionBucketSlots(bucket));
268 // Similarly, make explicitly sure that the freelist is empty.
269 ASSERT(!partitionPageFreelistHead(page));
270
271 size_t size = partitionBucketSize(bucket);
272 char* base = reinterpret_cast<char*>(partitionPageToPointer(page));
273 char* returnObject = base + (size * page->numAllocatedSlots);
274 char* nextFreeObject = returnObject + size;
275 char* subPageLimit = reinterpret_cast<char*>((reinterpret_cast<uintptr_t>(returnObject) + kSystemPageSize) & kSystemPageBaseMask);
276
277 size_t numNewFreelistEntries = 0;
278 if (LIKELY(subPageLimit > nextFreeObject))
279 numNewFreelistEntries = (subPageLimit - nextFreeObject) / size;
280
281 // We always return an object slot -- that's the +1 below.
282 // We do not neccessarily create any new freelist entries, because we cross sub page boundaries frequently for large bucket sizes.
283 numSlots -= (numNewFreelistEntries + 1);
284 page->numUnprovisionedSlots = numSlots;
285 page->numAllocatedSlots++;
286
287 if (LIKELY(numNewFreelistEntries)) {
288 PartitionFreelistEntry* entry = reinterpret_cast<PartitionFreelistEntry*>(nextFreeObject);
289 partitionPageSetFreelistHead(page, entry);
290 while (--numNewFreelistEntries) {
291 nextFreeObject += size;
292 PartitionFreelistEntry* nextEntry = reinterpret_cast<PartitionFreelistEntry*>(nextFreeObject);
293 entry->next = partitionFreelistMask(nextEntry);
294 entry = nextEntry;
295 }
296 entry->next = partitionFreelistMask(0);
297 } else {
298 partitionPageSetFreelistHead(page, 0);
299 }
300 return returnObject;
301 }
302
303 // This helper function scans the active page list for a suitable new active
304 // page, starting at the page passed in. When it finds a suitable new active
305 // page (one that has free slots), it is also set as the new active page. If
306 // there is no suitable new active page, the current active page is set to null.
partitionSetNewActivePage(PartitionBucket * bucket,PartitionPage * page)307 static ALWAYS_INLINE void partitionSetNewActivePage(PartitionBucket* bucket, PartitionPage* page)
308 {
309 ASSERT(page == &bucket->root->seedPage || page->bucket == bucket);
310 for (; page; page = page->activePageNext) {
311 // Skip over free pages. We need this check first so that we can trust
312 // that the page union field really containts a freelist pointer.
313 if (UNLIKELY(partitionPageIsFree(page)))
314 continue;
315
316 // Page is usable if it has something on the freelist, or unprovisioned
317 // slots that can be turned into a freelist.
318 if (LIKELY(partitionPageFreelistHead(page) != 0) || LIKELY(page->numUnprovisionedSlots))
319 break;
320 // If we get here, we found a full page. Skip over it too, and also tag
321 // it as full (via a negative value). We need it tagged so that free'ing
322 // can tell, and move it back into the active page list.
323 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots(bucket)));
324 page->numAllocatedSlots = -page->numAllocatedSlots;
325 ++bucket->numFullPages;
326 }
327
328 bucket->activePagesHead = page;
329 }
330
partitionPageSetFreePageNext(PartitionPage * page,PartitionPage * nextFree)331 static ALWAYS_INLINE void partitionPageSetFreePageNext(PartitionPage* page, PartitionPage* nextFree)
332 {
333 ASSERT(partitionPageIsFree(page));
334 page->u.freePageNext = nextFree;
335 }
336
partitionPageFreePageNext(PartitionPage * page)337 static ALWAYS_INLINE PartitionPage* partitionPageFreePageNext(PartitionPage* page)
338 {
339 ASSERT(partitionPageIsFree(page));
340 return page->u.freePageNext;
341 }
342
partitionAllocSlowPath(PartitionBucket * bucket)343 void* partitionAllocSlowPath(PartitionBucket* bucket)
344 {
345 // The slow path is called when the freelist is empty.
346 ASSERT(!partitionPageFreelistHead(bucket->activePagesHead));
347
348 // First, look for a usable page in the existing active pages list.
349 PartitionPage* page = bucket->activePagesHead;
350 partitionSetNewActivePage(bucket, page);
351 page = bucket->activePagesHead;
352 if (LIKELY(page != 0)) {
353 if (LIKELY(partitionPageFreelistHead(page) != 0)) {
354 PartitionFreelistEntry* ret = partitionPageFreelistHead(page);
355 partitionPageSetFreelistHead(page, partitionFreelistMask(ret->next));
356 page->numAllocatedSlots++;
357 return ret;
358 }
359 ASSERT(page->numUnprovisionedSlots);
360 return partitionPageAllocAndFillFreelist(page);
361 }
362
363 // Second, look in our list of freed but reserved pages.
364 PartitionPage* newPage = bucket->freePagesHead;
365 if (LIKELY(newPage != 0)) {
366 ASSERT(newPage != &bucket->root->seedPage);
367 bucket->freePagesHead = partitionPageFreePageNext(newPage);
368 } else {
369 // Third. If we get here, we need a brand new page.
370 void* rawNewPage = partitionAllocPage(bucket->root);
371 newPage = partitionPointerToPageNoAlignmentCheck(rawNewPage);
372 }
373
374 newPage->activePageNext = 0;
375 partitionPageReset(newPage, bucket);
376 bucket->activePagesHead = newPage;
377 return partitionPageAllocAndFillFreelist(newPage);
378 }
379
partitionFreeSlowPath(PartitionPage * page)380 void partitionFreeSlowPath(PartitionPage* page)
381 {
382 PartitionBucket* bucket = page->bucket;
383 ASSERT(page != &bucket->root->seedPage);
384 ASSERT(bucket->activePagesHead != &bucket->root->seedPage);
385 if (LIKELY(page->numAllocatedSlots == 0)) {
386 // Page became fully unused.
387 // If it's the current page, change it!
388 if (LIKELY(page == bucket->activePagesHead)) {
389 PartitionPage* newPage = 0;
390 if (page->activePageNext) {
391 partitionSetNewActivePage(bucket, page->activePageNext);
392 newPage = bucket->activePagesHead;
393 }
394
395 ASSERT(newPage != page);
396 if (UNLIKELY(!newPage)) {
397 // We do not free the last active page in a bucket.
398 // To do so is a serious performance issue as we can get into
399 // a repeating state change between 0 and 1 objects of a given
400 // size allocated; and each state change incurs either a system
401 // call or a page fault, or more.
402 page->activePageNext = 0;
403 bucket->activePagesHead = page;
404 return;
405 }
406 bucket->activePagesHead = newPage;
407 }
408
409 partitionUnusePage(page);
410 // We actually leave the freed page in the active list as well as
411 // putting it on the free list. We'll evict it from the active list soon
412 // enough in partitionAllocSlowPath. Pulling this trick enables us to
413 // use a singly-linked page list for all cases, which is critical in
414 // keeping the page metadata structure down to 32-bytes in size.
415 partitionPageMarkFree(page);
416 partitionPageSetFreePageNext(page, bucket->freePagesHead);
417 bucket->freePagesHead = page;
418 } else {
419 // Ensure that the page is full. That's the only valid case if we
420 // arrive here.
421 ASSERT(page->numAllocatedSlots < 0);
422 // Fully used page became partially used. It must be put back on the
423 // non-full page list. Also make it the current page to increase the
424 // chances of it being filled up again. The old current page will be
425 // the next page.
426 page->numAllocatedSlots = -page->numAllocatedSlots - 2;
427 ASSERT(page->numAllocatedSlots == static_cast<int>(partitionBucketSlots(bucket) - 1));
428 page->activePageNext = bucket->activePagesHead;
429 bucket->activePagesHead = page;
430 --bucket->numFullPages;
431 // Note: there's an opportunity here to look to see if the old active
432 // page is now freeable.
433 }
434 }
435
partitionReallocGeneric(PartitionRoot * root,void * ptr,size_t newSize)436 void* partitionReallocGeneric(PartitionRoot* root, void* ptr, size_t newSize)
437 {
438 RELEASE_ASSERT(newSize <= QuantizedAllocation::kMaxUnquantizedAllocation);
439 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
440 return realloc(ptr, newSize);
441 #else
442 size_t oldIndex;
443 if (LIKELY(partitionPointerIsValid(root, ptr))) {
444 void* realPtr = partitionCookieFreePointerAdjust(ptr);
445 PartitionBucket* bucket = partitionPointerToPage(realPtr)->bucket;
446 ASSERT(bucket->root == root);
447 oldIndex = bucket - root->buckets();
448 } else {
449 oldIndex = root->numBuckets;
450 }
451
452 size_t allocSize = QuantizedAllocation::quantizedSize(newSize);
453 allocSize = partitionCookieSizeAdjustAdd(allocSize);
454 size_t newIndex = allocSize >> kBucketShift;
455 if (newIndex > root->numBuckets)
456 newIndex = root->numBuckets;
457
458 if (oldIndex == newIndex) {
459 // Same bucket. But kNumBuckets indicates the fastMalloc "bucket" so in
460 // that case we're not done; we have to forward to fastRealloc.
461 if (oldIndex == root->numBuckets)
462 return WTF::fastRealloc(ptr, newSize);
463 return ptr;
464 }
465 // This realloc cannot be resized in-place. Sadness.
466 void* ret = partitionAllocGeneric(root, newSize);
467 size_t copySize = oldIndex << kBucketShift;
468 copySize = partitionCookieSizeAdjustSubtract(copySize);
469 if (newSize < copySize)
470 copySize = newSize;
471 memcpy(ret, ptr, copySize);
472 partitionFreeGeneric(root, ptr);
473 return ret;
474 #endif
475 }
476
477 #if CPU(32BIT)
478 unsigned char SuperPageBitmap::s_bitmap[1 << (32 - kSuperPageShift - 3)];
479
480 static int bitmapLock = 0;
481
registerSuperPage(void * ptr)482 void SuperPageBitmap::registerSuperPage(void* ptr)
483 {
484 ASSERT(!isPointerInSuperPage(ptr));
485 uintptr_t raw = reinterpret_cast<uintptr_t>(ptr);
486 raw >>= kSuperPageShift;
487 size_t byteIndex = raw >> 3;
488 size_t bit = raw & 7;
489 ASSERT(byteIndex < sizeof(s_bitmap));
490 // The read/modify/write is not guaranteed atomic, so take a lock.
491 spinLockLock(&bitmapLock);
492 s_bitmap[byteIndex] |= (1 << bit);
493 spinLockUnlock(&bitmapLock);
494 }
495
unregisterSuperPage(void * ptr)496 void SuperPageBitmap::unregisterSuperPage(void* ptr)
497 {
498 ASSERT(isPointerInSuperPage(ptr));
499 uintptr_t raw = reinterpret_cast<uintptr_t>(ptr);
500 raw >>= kSuperPageShift;
501 size_t byteIndex = raw >> 3;
502 size_t bit = raw & 7;
503 ASSERT(byteIndex < sizeof(s_bitmap));
504 // The read/modify/write is not guaranteed atomic, so take a lock.
505 spinLockLock(&bitmapLock);
506 s_bitmap[byteIndex] &= ~(1 << bit);
507 spinLockUnlock(&bitmapLock);
508 }
509 #endif
510
511 #ifndef NDEBUG
512
partitionDumpStats(const PartitionRoot & root)513 void partitionDumpStats(const PartitionRoot& root)
514 {
515 size_t i;
516 size_t totalLive = 0;
517 size_t totalResident = 0;
518 size_t totalFreeable = 0;
519 for (i = 0; i < root.numBuckets; ++i) {
520 const PartitionBucket& bucket = root.buckets()[i];
521 if (bucket.activePagesHead == &bucket.root->seedPage && !bucket.freePagesHead && !bucket.numFullPages) {
522 // Empty bucket with no freelist or full pages. Skip reporting it.
523 continue;
524 }
525 size_t numFreePages = 0;
526 PartitionPage* freePages = bucket.freePagesHead;
527 while (freePages) {
528 ++numFreePages;
529 freePages = partitionPageFreePageNext(freePages);
530 }
531 size_t bucketSlotSize = partitionBucketSize(&bucket);
532 size_t bucketNumSlots = partitionBucketSlots(&bucket);
533 size_t bucketUsefulStorage = bucketSlotSize * bucketNumSlots;
534 size_t bucketWaste = bucket.pageSize - bucketUsefulStorage;
535 size_t numActiveBytes = bucket.numFullPages * bucketUsefulStorage;
536 size_t numResidentBytes = bucket.numFullPages * bucket.pageSize;
537 size_t numFreeableBytes = 0;
538 size_t numActivePages = 0;
539 const PartitionPage* page = bucket.activePagesHead;
540 do {
541 if (page != &bucket.root->seedPage) {
542 ++numActivePages;
543 numActiveBytes += (page->numAllocatedSlots * bucketSlotSize);
544 size_t pageBytesResident = (bucketNumSlots - page->numUnprovisionedSlots) * bucketSlotSize;
545 // Round up to system page size.
546 pageBytesResident = (pageBytesResident + kSystemPageOffsetMask) & kSystemPageBaseMask;
547 numResidentBytes += pageBytesResident;
548 if (!page->numAllocatedSlots)
549 numFreeableBytes += pageBytesResident;
550 }
551 page = page->activePageNext;
552 } while (page != bucket.activePagesHead);
553 totalLive += numActiveBytes;
554 totalResident += numResidentBytes;
555 totalFreeable += numFreeableBytes;
556 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);
557 }
558 printf("total live: %zu bytes\n", totalLive);
559 printf("total resident: %zu bytes\n", totalResident);
560 printf("total freeable: %zu bytes\n", totalFreeable);
561 fflush(stdout);
562 }
563
564 #endif // !NDEBUG
565
566 } // namespace WTF
567
568