| OLD | NEW |
| (Empty) |
| 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 "wtf/PartitionAlloc.h" | |
| 32 | |
| 33 #include <string.h> | |
| 34 | |
| 35 #ifndef NDEBUG | |
| 36 #include <stdio.h> | |
| 37 #endif | |
| 38 | |
| 39 // Two partition pages are used as guard / metadata page so make sure the super | |
| 40 // page size is bigger. | |
| 41 static_assert(WTF::kPartitionPageSize * 4 <= WTF::kSuperPageSize, "ok super page
size"); | |
| 42 static_assert(!(WTF::kSuperPageSize % WTF::kPartitionPageSize), "ok super page m
ultiple"); | |
| 43 // Four system pages gives us room to hack out a still-guard-paged piece | |
| 44 // of metadata in the middle of a guard partition page. | |
| 45 static_assert(WTF::kSystemPageSize * 4 <= WTF::kPartitionPageSize, "ok partition
page size"); | |
| 46 static_assert(!(WTF::kPartitionPageSize % WTF::kSystemPageSize), "ok partition p
age multiple"); | |
| 47 static_assert(sizeof(WTF::PartitionPage) <= WTF::kPageMetadataSize, "PartitionPa
ge should not be too big"); | |
| 48 static_assert(sizeof(WTF::PartitionBucket) <= WTF::kPageMetadataSize, "Partition
Bucket should not be too big"); | |
| 49 static_assert(sizeof(WTF::PartitionSuperPageExtentEntry) <= WTF::kPageMetadataSi
ze, "PartitionSuperPageExtentEntry should not be too big"); | |
| 50 static_assert(WTF::kPageMetadataSize * WTF::kNumPartitionPagesPerSuperPage <= WT
F::kSystemPageSize, "page metadata fits in hole"); | |
| 51 // Check that some of our zanier calculations worked out as expected. | |
| 52 static_assert(WTF::kGenericSmallestBucket == 8, "generic smallest bucket"); | |
| 53 static_assert(WTF::kGenericMaxBucketed == 983040, "generic max bucketed"); | |
| 54 static_assert(WTF::kMaxSystemPagesPerSlotSpan < (1 << 8), "System pages per slot
span must be less than 128."); | |
| 55 | |
| 56 namespace WTF { | |
| 57 | |
| 58 SpinLock PartitionRootBase::gInitializedLock; | |
| 59 bool PartitionRootBase::gInitialized = false; | |
| 60 PartitionPage PartitionRootBase::gSeedPage; | |
| 61 PartitionBucket PartitionRootBase::gPagedBucket; | |
| 62 void (*PartitionRootBase::gOomHandlingFunction)() = nullptr; | |
| 63 PartitionAllocHooks::AllocationHook* PartitionAllocHooks::m_allocationHook = nul
lptr; | |
| 64 PartitionAllocHooks::FreeHook* PartitionAllocHooks::m_freeHook = nullptr; | |
| 65 | |
| 66 static uint8_t partitionBucketNumSystemPages(size_t size) | |
| 67 { | |
| 68 // This works out reasonably for the current bucket sizes of the generic | |
| 69 // allocator, and the current values of partition page size and constants. | |
| 70 // Specifically, we have enough room to always pack the slots perfectly into | |
| 71 // some number of system pages. The only waste is the waste associated with | |
| 72 // unfaulted pages (i.e. wasted address space). | |
| 73 // TODO: we end up using a lot of system pages for very small sizes. For | |
| 74 // example, we'll use 12 system pages for slot size 24. The slot size is | |
| 75 // so small that the waste would be tiny with just 4, or 1, system pages. | |
| 76 // Later, we can investigate whether there are anti-fragmentation benefits | |
| 77 // to using fewer system pages. | |
| 78 double bestWasteRatio = 1.0f; | |
| 79 uint16_t bestPages = 0; | |
| 80 if (size > kMaxSystemPagesPerSlotSpan * kSystemPageSize) { | |
| 81 ASSERT(!(size % kSystemPageSize)); | |
| 82 bestPages = static_cast<uint16_t>(size / kSystemPageSize); | |
| 83 RELEASE_ASSERT(bestPages < (1 << 8)); | |
| 84 return static_cast<uint8_t>(bestPages); | |
| 85 } | |
| 86 ASSERT(size <= kMaxSystemPagesPerSlotSpan * kSystemPageSize); | |
| 87 for (uint16_t i = kNumSystemPagesPerPartitionPage - 1; i <= kMaxSystemPagesP
erSlotSpan; ++i) { | |
| 88 size_t pageSize = kSystemPageSize * i; | |
| 89 size_t numSlots = pageSize / size; | |
| 90 size_t waste = pageSize - (numSlots * size); | |
| 91 // Leaving a page unfaulted is not free; the page will occupy an empty p
age table entry. | |
| 92 // Make a simple attempt to account for that. | |
| 93 size_t numRemainderPages = i & (kNumSystemPagesPerPartitionPage - 1); | |
| 94 size_t numUnfaultedPages = numRemainderPages ? (kNumSystemPagesPerPartit
ionPage - numRemainderPages) : 0; | |
| 95 waste += sizeof(void*) * numUnfaultedPages; | |
| 96 double wasteRatio = (double) waste / (double) pageSize; | |
| 97 if (wasteRatio < bestWasteRatio) { | |
| 98 bestWasteRatio = wasteRatio; | |
| 99 bestPages = i; | |
| 100 } | |
| 101 } | |
| 102 ASSERT(bestPages > 0); | |
| 103 RELEASE_ASSERT(bestPages <= kMaxSystemPagesPerSlotSpan); | |
| 104 return static_cast<uint8_t>(bestPages); | |
| 105 } | |
| 106 | |
| 107 static void partitionAllocBaseInit(PartitionRootBase* root) | |
| 108 { | |
| 109 ASSERT(!root->initialized); | |
| 110 { | |
| 111 SpinLock::Guard guard(PartitionRootBase::gInitializedLock); | |
| 112 if (!PartitionRootBase::gInitialized) { | |
| 113 PartitionRootBase::gInitialized = true; | |
| 114 // We mark the seed page as free to make sure it is skipped by our | |
| 115 // logic to find a new active page. | |
| 116 PartitionRootBase::gPagedBucket.activePagesHead = &PartitionRootGene
ric::gSeedPage; | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 root->initialized = true; | |
| 121 root->totalSizeOfCommittedPages = 0; | |
| 122 root->totalSizeOfSuperPages = 0; | |
| 123 root->totalSizeOfDirectMappedPages = 0; | |
| 124 root->nextSuperPage = 0; | |
| 125 root->nextPartitionPage = 0; | |
| 126 root->nextPartitionPageEnd = 0; | |
| 127 root->firstExtent = 0; | |
| 128 root->currentExtent = 0; | |
| 129 root->directMapList = 0; | |
| 130 | |
| 131 memset(&root->globalEmptyPageRing, '\0', sizeof(root->globalEmptyPageRing)); | |
| 132 root->globalEmptyPageRingIndex = 0; | |
| 133 | |
| 134 // This is a "magic" value so we can test if a root pointer is valid. | |
| 135 root->invertedSelf = ~reinterpret_cast<uintptr_t>(root); | |
| 136 } | |
| 137 | |
| 138 static void partitionBucketInitBase(PartitionBucket* bucket, PartitionRootBase*
root) | |
| 139 { | |
| 140 bucket->activePagesHead = &PartitionRootGeneric::gSeedPage; | |
| 141 bucket->emptyPagesHead = 0; | |
| 142 bucket->decommittedPagesHead = 0; | |
| 143 bucket->numFullPages = 0; | |
| 144 bucket->numSystemPagesPerSlotSpan = partitionBucketNumSystemPages(bucket->sl
otSize); | |
| 145 } | |
| 146 | |
| 147 void partitionAllocGlobalInit(void (*oomHandlingFunction)()) | |
| 148 { | |
| 149 ASSERT(oomHandlingFunction); | |
| 150 PartitionRootBase::gOomHandlingFunction = oomHandlingFunction; | |
| 151 } | |
| 152 | |
| 153 void partitionAllocInit(PartitionRoot* root, size_t numBuckets, size_t maxAlloca
tion) | |
| 154 { | |
| 155 partitionAllocBaseInit(root); | |
| 156 | |
| 157 root->numBuckets = numBuckets; | |
| 158 root->maxAllocation = maxAllocation; | |
| 159 size_t i; | |
| 160 for (i = 0; i < root->numBuckets; ++i) { | |
| 161 PartitionBucket* bucket = &root->buckets()[i]; | |
| 162 if (!i) | |
| 163 bucket->slotSize = kAllocationGranularity; | |
| 164 else | |
| 165 bucket->slotSize = i << kBucketShift; | |
| 166 partitionBucketInitBase(bucket, root); | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 void partitionAllocGenericInit(PartitionRootGeneric* root) | |
| 171 { | |
| 172 SpinLock::Guard guard(root->lock); | |
| 173 | |
| 174 partitionAllocBaseInit(root); | |
| 175 | |
| 176 // Precalculate some shift and mask constants used in the hot path. | |
| 177 // Example: malloc(41) == 101001 binary. | |
| 178 // Order is 6 (1 << 6-1)==32 is highest bit set. | |
| 179 // orderIndex is the next three MSB == 010 == 2. | |
| 180 // subOrderIndexMask is a mask for the remaining bits == 11 (masking to 01 f
or the subOrderIndex). | |
| 181 size_t order; | |
| 182 for (order = 0; order <= kBitsPerSizet; ++order) { | |
| 183 size_t orderIndexShift; | |
| 184 if (order < kGenericNumBucketsPerOrderBits + 1) | |
| 185 orderIndexShift = 0; | |
| 186 else | |
| 187 orderIndexShift = order - (kGenericNumBucketsPerOrderBits + 1); | |
| 188 root->orderIndexShifts[order] = orderIndexShift; | |
| 189 size_t subOrderIndexMask; | |
| 190 if (order == kBitsPerSizet) { | |
| 191 // This avoids invoking undefined behavior for an excessive shift. | |
| 192 subOrderIndexMask = static_cast<size_t>(-1) >> (kGenericNumBucketsPe
rOrderBits + 1); | |
| 193 } else { | |
| 194 subOrderIndexMask = ((static_cast<size_t>(1) << order) - 1) >> (kGen
ericNumBucketsPerOrderBits + 1); | |
| 195 } | |
| 196 root->orderSubIndexMasks[order] = subOrderIndexMask; | |
| 197 } | |
| 198 | |
| 199 // Set up the actual usable buckets first. | |
| 200 // Note that typical values (i.e. min allocation size of 8) will result in | |
| 201 // pseudo buckets (size==9 etc. or more generally, size is not a multiple | |
| 202 // of the smallest allocation granularity). | |
| 203 // We avoid them in the bucket lookup map, but we tolerate them to keep the | |
| 204 // code simpler and the structures more generic. | |
| 205 size_t i, j; | |
| 206 size_t currentSize = kGenericSmallestBucket; | |
| 207 size_t currentIncrement = kGenericSmallestBucket >> kGenericNumBucketsPerOrd
erBits; | |
| 208 PartitionBucket* bucket = &root->buckets[0]; | |
| 209 for (i = 0; i < kGenericNumBucketedOrders; ++i) { | |
| 210 for (j = 0; j < kGenericNumBucketsPerOrder; ++j) { | |
| 211 bucket->slotSize = currentSize; | |
| 212 partitionBucketInitBase(bucket, root); | |
| 213 // Disable psuedo buckets so that touching them faults. | |
| 214 if (currentSize % kGenericSmallestBucket) | |
| 215 bucket->activePagesHead = 0; | |
| 216 currentSize += currentIncrement; | |
| 217 ++bucket; | |
| 218 } | |
| 219 currentIncrement <<= 1; | |
| 220 } | |
| 221 ASSERT(currentSize == 1 << kGenericMaxBucketedOrder); | |
| 222 ASSERT(bucket == &root->buckets[0] + kGenericNumBuckets); | |
| 223 | |
| 224 // Then set up the fast size -> bucket lookup table. | |
| 225 bucket = &root->buckets[0]; | |
| 226 PartitionBucket** bucketPtr = &root->bucketLookups[0]; | |
| 227 for (order = 0; order <= kBitsPerSizet; ++order) { | |
| 228 for (j = 0; j < kGenericNumBucketsPerOrder; ++j) { | |
| 229 if (order < kGenericMinBucketedOrder) { | |
| 230 // Use the bucket of the finest granularity for malloc(0) etc. | |
| 231 *bucketPtr++ = &root->buckets[0]; | |
| 232 } else if (order > kGenericMaxBucketedOrder) { | |
| 233 *bucketPtr++ = &PartitionRootGeneric::gPagedBucket; | |
| 234 } else { | |
| 235 PartitionBucket* validBucket = bucket; | |
| 236 // Skip over invalid buckets. | |
| 237 while (validBucket->slotSize % kGenericSmallestBucket) | |
| 238 validBucket++; | |
| 239 *bucketPtr++ = validBucket; | |
| 240 bucket++; | |
| 241 } | |
| 242 } | |
| 243 } | |
| 244 ASSERT(bucket == &root->buckets[0] + kGenericNumBuckets); | |
| 245 ASSERT(bucketPtr == &root->bucketLookups[0] + ((kBitsPerSizet + 1) * kGeneri
cNumBucketsPerOrder)); | |
| 246 // And there's one last bucket lookup that will be hit for e.g. malloc(-1), | |
| 247 // which tries to overflow to a non-existant order. | |
| 248 *bucketPtr = &PartitionRootGeneric::gPagedBucket; | |
| 249 } | |
| 250 | |
| 251 static bool partitionAllocShutdownBucket(PartitionBucket* bucket) | |
| 252 { | |
| 253 // Failure here indicates a memory leak. | |
| 254 bool foundLeak = bucket->numFullPages; | |
| 255 for (PartitionPage* page = bucket->activePagesHead; page; page = page->nextP
age) | |
| 256 foundLeak |= (page->numAllocatedSlots > 0); | |
| 257 return foundLeak; | |
| 258 } | |
| 259 | |
| 260 static bool partitionAllocBaseShutdown(PartitionRootBase* root) | |
| 261 { | |
| 262 ASSERT(root->initialized); | |
| 263 root->initialized = false; | |
| 264 | |
| 265 // Now that we've examined all partition pages in all buckets, it's safe | |
| 266 // to free all our super pages. Since the super page extent entries are | |
| 267 // stored in the super pages, we need to be careful not to access them | |
| 268 // after we've released the corresponding super page. | |
| 269 PartitionSuperPageExtentEntry* entry = root->firstExtent; | |
| 270 while (entry) { | |
| 271 PartitionSuperPageExtentEntry* nextEntry = entry->next; | |
| 272 char* superPage = entry->superPageBase; | |
| 273 char* superPagesEnd = entry->superPagesEnd; | |
| 274 while (superPage < superPagesEnd) { | |
| 275 freePages(superPage, kSuperPageSize); | |
| 276 superPage += kSuperPageSize; | |
| 277 } | |
| 278 entry = nextEntry; | |
| 279 } | |
| 280 return root->directMapList; | |
| 281 } | |
| 282 | |
| 283 bool partitionAllocShutdown(PartitionRoot* root) | |
| 284 { | |
| 285 bool foundLeak = false; | |
| 286 size_t i; | |
| 287 for (i = 0; i < root->numBuckets; ++i) { | |
| 288 PartitionBucket* bucket = &root->buckets()[i]; | |
| 289 foundLeak |= partitionAllocShutdownBucket(bucket); | |
| 290 } | |
| 291 foundLeak |= partitionAllocBaseShutdown(root); | |
| 292 return !foundLeak; | |
| 293 } | |
| 294 | |
| 295 bool partitionAllocGenericShutdown(PartitionRootGeneric* root) | |
| 296 { | |
| 297 SpinLock::Guard guard(root->lock); | |
| 298 bool foundLeak = false; | |
| 299 size_t i; | |
| 300 for (i = 0; i < kGenericNumBuckets; ++i) { | |
| 301 PartitionBucket* bucket = &root->buckets[i]; | |
| 302 foundLeak |= partitionAllocShutdownBucket(bucket); | |
| 303 } | |
| 304 foundLeak |= partitionAllocBaseShutdown(root); | |
| 305 return !foundLeak; | |
| 306 } | |
| 307 | |
| 308 #if !CPU(64BIT) | |
| 309 static NEVER_INLINE void partitionOutOfMemoryWithLotsOfUncommitedPages() | |
| 310 { | |
| 311 IMMEDIATE_CRASH(); | |
| 312 } | |
| 313 #endif | |
| 314 | |
| 315 static NEVER_INLINE void partitionOutOfMemory(const PartitionRootBase* root) | |
| 316 { | |
| 317 #if !CPU(64BIT) | |
| 318 // Check whether this OOM is due to a lot of super pages that are allocated | |
| 319 // but not committed, probably due to http://crbug.com/421387. | |
| 320 if (root->totalSizeOfSuperPages + root->totalSizeOfDirectMappedPages - root-
>totalSizeOfCommittedPages > kReasonableSizeOfUnusedPages) { | |
| 321 partitionOutOfMemoryWithLotsOfUncommitedPages(); | |
| 322 } | |
| 323 #endif | |
| 324 if (PartitionRootBase::gOomHandlingFunction) | |
| 325 (*PartitionRootBase::gOomHandlingFunction)(); | |
| 326 IMMEDIATE_CRASH(); | |
| 327 } | |
| 328 | |
| 329 static NEVER_INLINE void partitionExcessiveAllocationSize() | |
| 330 { | |
| 331 IMMEDIATE_CRASH(); | |
| 332 } | |
| 333 | |
| 334 static NEVER_INLINE void partitionBucketFull() | |
| 335 { | |
| 336 IMMEDIATE_CRASH(); | |
| 337 } | |
| 338 | |
| 339 // partitionPageStateIs* | |
| 340 // Note that it's only valid to call these functions on pages found on one of | |
| 341 // the page lists. Specifically, you can't call these functions on full pages | |
| 342 // that were detached from the active list. | |
| 343 static bool ALWAYS_INLINE partitionPageStateIsActive(const PartitionPage* page) | |
| 344 { | |
| 345 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 346 ASSERT(!page->pageOffset); | |
| 347 return (page->numAllocatedSlots > 0 && (page->freelistHead || page->numUnpro
visionedSlots)); | |
| 348 } | |
| 349 | |
| 350 static bool ALWAYS_INLINE partitionPageStateIsFull(const PartitionPage* page) | |
| 351 { | |
| 352 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 353 ASSERT(!page->pageOffset); | |
| 354 bool ret = (page->numAllocatedSlots == partitionBucketSlots(page->bucket)); | |
| 355 if (ret) { | |
| 356 ASSERT(!page->freelistHead); | |
| 357 ASSERT(!page->numUnprovisionedSlots); | |
| 358 } | |
| 359 return ret; | |
| 360 } | |
| 361 | |
| 362 static bool ALWAYS_INLINE partitionPageStateIsEmpty(const PartitionPage* page) | |
| 363 { | |
| 364 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 365 ASSERT(!page->pageOffset); | |
| 366 return (!page->numAllocatedSlots && page->freelistHead); | |
| 367 } | |
| 368 | |
| 369 static bool ALWAYS_INLINE partitionPageStateIsDecommitted(const PartitionPage* p
age) | |
| 370 { | |
| 371 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 372 ASSERT(!page->pageOffset); | |
| 373 bool ret = (!page->numAllocatedSlots && !page->freelistHead); | |
| 374 if (ret) { | |
| 375 ASSERT(!page->numUnprovisionedSlots); | |
| 376 ASSERT(page->emptyCacheIndex == -1); | |
| 377 } | |
| 378 return ret; | |
| 379 } | |
| 380 | |
| 381 static void partitionIncreaseCommittedPages(PartitionRootBase* root, size_t len) | |
| 382 { | |
| 383 root->totalSizeOfCommittedPages += len; | |
| 384 ASSERT(root->totalSizeOfCommittedPages <= root->totalSizeOfSuperPages + root
->totalSizeOfDirectMappedPages); | |
| 385 } | |
| 386 | |
| 387 static void partitionDecreaseCommittedPages(PartitionRootBase* root, size_t len) | |
| 388 { | |
| 389 root->totalSizeOfCommittedPages -= len; | |
| 390 ASSERT(root->totalSizeOfCommittedPages <= root->totalSizeOfSuperPages + root
->totalSizeOfDirectMappedPages); | |
| 391 } | |
| 392 | |
| 393 static ALWAYS_INLINE void partitionDecommitSystemPages(PartitionRootBase* root,
void* addr, size_t len) | |
| 394 { | |
| 395 decommitSystemPages(addr, len); | |
| 396 partitionDecreaseCommittedPages(root, len); | |
| 397 } | |
| 398 | |
| 399 static ALWAYS_INLINE void partitionRecommitSystemPages(PartitionRootBase* root,
void* addr, size_t len) | |
| 400 { | |
| 401 recommitSystemPages(addr, len); | |
| 402 partitionIncreaseCommittedPages(root, len); | |
| 403 } | |
| 404 | |
| 405 static ALWAYS_INLINE void* partitionAllocPartitionPages(PartitionRootBase* root,
int flags, uint16_t numPartitionPages) | |
| 406 { | |
| 407 ASSERT(!(reinterpret_cast<uintptr_t>(root->nextPartitionPage) % kPartitionPa
geSize)); | |
| 408 ASSERT(!(reinterpret_cast<uintptr_t>(root->nextPartitionPageEnd) % kPartitio
nPageSize)); | |
| 409 ASSERT(numPartitionPages <= kNumPartitionPagesPerSuperPage); | |
| 410 size_t totalSize = kPartitionPageSize * numPartitionPages; | |
| 411 size_t numPartitionPagesLeft = (root->nextPartitionPageEnd - root->nextParti
tionPage) >> kPartitionPageShift; | |
| 412 if (LIKELY(numPartitionPagesLeft >= numPartitionPages)) { | |
| 413 // In this case, we can still hand out pages from the current super page | |
| 414 // allocation. | |
| 415 char* ret = root->nextPartitionPage; | |
| 416 root->nextPartitionPage += totalSize; | |
| 417 partitionIncreaseCommittedPages(root, totalSize); | |
| 418 return ret; | |
| 419 } | |
| 420 | |
| 421 // Need a new super page. We want to allocate super pages in a continguous | |
| 422 // address region as much as possible. This is important for not causing | |
| 423 // page table bloat and not fragmenting address spaces in 32 bit architectur
es. | |
| 424 char* requestedAddress = root->nextSuperPage; | |
| 425 char* superPage = reinterpret_cast<char*>(allocPages(requestedAddress, kSupe
rPageSize, kSuperPageSize, PageAccessible)); | |
| 426 if (UNLIKELY(!superPage)) | |
| 427 return 0; | |
| 428 | |
| 429 root->totalSizeOfSuperPages += kSuperPageSize; | |
| 430 partitionIncreaseCommittedPages(root, totalSize); | |
| 431 | |
| 432 root->nextSuperPage = superPage + kSuperPageSize; | |
| 433 char* ret = superPage + kPartitionPageSize; | |
| 434 root->nextPartitionPage = ret + totalSize; | |
| 435 root->nextPartitionPageEnd = root->nextSuperPage - kPartitionPageSize; | |
| 436 // Make the first partition page in the super page a guard page, but leave a | |
| 437 // hole in the middle. | |
| 438 // This is where we put page metadata and also a tiny amount of extent | |
| 439 // metadata. | |
| 440 setSystemPagesInaccessible(superPage, kSystemPageSize); | |
| 441 setSystemPagesInaccessible(superPage + (kSystemPageSize * 2), kPartitionPage
Size - (kSystemPageSize * 2)); | |
| 442 // Also make the last partition page a guard page. | |
| 443 setSystemPagesInaccessible(superPage + (kSuperPageSize - kPartitionPageSize)
, kPartitionPageSize); | |
| 444 | |
| 445 // If we were after a specific address, but didn't get it, assume that | |
| 446 // the system chose a lousy address. Here most OS'es have a default | |
| 447 // algorithm that isn't randomized. For example, most Linux | |
| 448 // distributions will allocate the mapping directly before the last | |
| 449 // successful mapping, which is far from random. So we just get fresh | |
| 450 // randomness for the next mapping attempt. | |
| 451 if (requestedAddress && requestedAddress != superPage) | |
| 452 root->nextSuperPage = 0; | |
| 453 | |
| 454 // We allocated a new super page so update super page metadata. | |
| 455 // First check if this is a new extent or not. | |
| 456 PartitionSuperPageExtentEntry* latestExtent = reinterpret_cast<PartitionSupe
rPageExtentEntry*>(partitionSuperPageToMetadataArea(superPage)); | |
| 457 // By storing the root in every extent metadata object, we have a fast way | |
| 458 // to go from a pointer within the partition to the root object. | |
| 459 latestExtent->root = root; | |
| 460 // Most new extents will be part of a larger extent, and these three fields | |
| 461 // are unused, but we initialize them to 0 so that we get a clear signal | |
| 462 // in case they are accidentally used. | |
| 463 latestExtent->superPageBase = 0; | |
| 464 latestExtent->superPagesEnd = 0; | |
| 465 latestExtent->next = 0; | |
| 466 | |
| 467 PartitionSuperPageExtentEntry* currentExtent = root->currentExtent; | |
| 468 bool isNewExtent = (superPage != requestedAddress); | |
| 469 if (UNLIKELY(isNewExtent)) { | |
| 470 if (UNLIKELY(!currentExtent)) { | |
| 471 ASSERT(!root->firstExtent); | |
| 472 root->firstExtent = latestExtent; | |
| 473 } else { | |
| 474 ASSERT(currentExtent->superPageBase); | |
| 475 currentExtent->next = latestExtent; | |
| 476 } | |
| 477 root->currentExtent = latestExtent; | |
| 478 latestExtent->superPageBase = superPage; | |
| 479 latestExtent->superPagesEnd = superPage + kSuperPageSize; | |
| 480 } else { | |
| 481 // We allocated next to an existing extent so just nudge the size up a l
ittle. | |
| 482 ASSERT(currentExtent->superPagesEnd); | |
| 483 currentExtent->superPagesEnd += kSuperPageSize; | |
| 484 ASSERT(ret >= currentExtent->superPageBase && ret < currentExtent->super
PagesEnd); | |
| 485 } | |
| 486 return ret; | |
| 487 } | |
| 488 | |
| 489 static ALWAYS_INLINE uint16_t partitionBucketPartitionPages(const PartitionBucke
t* bucket) | |
| 490 { | |
| 491 return (bucket->numSystemPagesPerSlotSpan + (kNumSystemPagesPerPartitionPage
- 1)) / kNumSystemPagesPerPartitionPage; | |
| 492 } | |
| 493 | |
| 494 static ALWAYS_INLINE void partitionPageReset(PartitionPage* page) | |
| 495 { | |
| 496 ASSERT(partitionPageStateIsDecommitted(page)); | |
| 497 | |
| 498 page->numUnprovisionedSlots = partitionBucketSlots(page->bucket); | |
| 499 ASSERT(page->numUnprovisionedSlots); | |
| 500 | |
| 501 page->nextPage = nullptr; | |
| 502 } | |
| 503 | |
| 504 static ALWAYS_INLINE void partitionPageSetup(PartitionPage* page, PartitionBucke
t* bucket) | |
| 505 { | |
| 506 // The bucket never changes. We set it up once. | |
| 507 page->bucket = bucket; | |
| 508 page->emptyCacheIndex = -1; | |
| 509 | |
| 510 partitionPageReset(page); | |
| 511 | |
| 512 // If this page has just a single slot, do not set up page offsets for any | |
| 513 // page metadata other than the first one. This ensures that attempts to | |
| 514 // touch invalid page metadata fail. | |
| 515 if (page->numUnprovisionedSlots == 1) | |
| 516 return; | |
| 517 | |
| 518 uint16_t numPartitionPages = partitionBucketPartitionPages(bucket); | |
| 519 char* pageCharPtr = reinterpret_cast<char*>(page); | |
| 520 for (uint16_t i = 1; i < numPartitionPages; ++i) { | |
| 521 pageCharPtr += kPageMetadataSize; | |
| 522 PartitionPage* secondaryPage = reinterpret_cast<PartitionPage*>(pageChar
Ptr); | |
| 523 secondaryPage->pageOffset = i; | |
| 524 } | |
| 525 } | |
| 526 | |
| 527 static ALWAYS_INLINE char* partitionPageAllocAndFillFreelist(PartitionPage* page
) | |
| 528 { | |
| 529 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 530 uint16_t numSlots = page->numUnprovisionedSlots; | |
| 531 ASSERT(numSlots); | |
| 532 PartitionBucket* bucket = page->bucket; | |
| 533 // We should only get here when _every_ slot is either used or unprovisioned
. | |
| 534 // (The third state is "on the freelist". If we have a non-empty freelist, w
e should not get here.) | |
| 535 ASSERT(numSlots + page->numAllocatedSlots == partitionBucketSlots(bucket)); | |
| 536 // Similarly, make explicitly sure that the freelist is empty. | |
| 537 ASSERT(!page->freelistHead); | |
| 538 ASSERT(page->numAllocatedSlots >= 0); | |
| 539 | |
| 540 size_t size = bucket->slotSize; | |
| 541 char* base = reinterpret_cast<char*>(partitionPageToPointer(page)); | |
| 542 char* returnObject = base + (size * page->numAllocatedSlots); | |
| 543 char* firstFreelistPointer = returnObject + size; | |
| 544 char* firstFreelistPointerExtent = firstFreelistPointer + sizeof(PartitionFr
eelistEntry*); | |
| 545 // Our goal is to fault as few system pages as possible. We calculate the | |
| 546 // page containing the "end" of the returned slot, and then allow freelist | |
| 547 // pointers to be written up to the end of that page. | |
| 548 char* subPageLimit = reinterpret_cast<char*>(WTF::roundUpToSystemPage(reinte
rpret_cast<size_t>(firstFreelistPointer))); | |
| 549 char* slotsLimit = returnObject + (size * numSlots); | |
| 550 char* freelistLimit = subPageLimit; | |
| 551 if (UNLIKELY(slotsLimit < freelistLimit)) | |
| 552 freelistLimit = slotsLimit; | |
| 553 | |
| 554 uint16_t numNewFreelistEntries = 0; | |
| 555 if (LIKELY(firstFreelistPointerExtent <= freelistLimit)) { | |
| 556 // Only consider used space in the slot span. If we consider wasted | |
| 557 // space, we may get an off-by-one when a freelist pointer fits in the | |
| 558 // wasted space, but a slot does not. | |
| 559 // We know we can fit at least one freelist pointer. | |
| 560 numNewFreelistEntries = 1; | |
| 561 // Any further entries require space for the whole slot span. | |
| 562 numNewFreelistEntries += static_cast<uint16_t>((freelistLimit - firstFre
elistPointerExtent) / size); | |
| 563 } | |
| 564 | |
| 565 // We always return an object slot -- that's the +1 below. | |
| 566 // We do not neccessarily create any new freelist entries, because we cross
sub page boundaries frequently for large bucket sizes. | |
| 567 ASSERT(numNewFreelistEntries + 1 <= numSlots); | |
| 568 numSlots -= (numNewFreelistEntries + 1); | |
| 569 page->numUnprovisionedSlots = numSlots; | |
| 570 page->numAllocatedSlots++; | |
| 571 | |
| 572 if (LIKELY(numNewFreelistEntries)) { | |
| 573 char* freelistPointer = firstFreelistPointer; | |
| 574 PartitionFreelistEntry* entry = reinterpret_cast<PartitionFreelistEntry*
>(freelistPointer); | |
| 575 page->freelistHead = entry; | |
| 576 while (--numNewFreelistEntries) { | |
| 577 freelistPointer += size; | |
| 578 PartitionFreelistEntry* nextEntry = reinterpret_cast<PartitionFreeli
stEntry*>(freelistPointer); | |
| 579 entry->next = partitionFreelistMask(nextEntry); | |
| 580 entry = nextEntry; | |
| 581 } | |
| 582 entry->next = partitionFreelistMask(0); | |
| 583 } else { | |
| 584 page->freelistHead = 0; | |
| 585 } | |
| 586 return returnObject; | |
| 587 } | |
| 588 | |
| 589 // This helper function scans a bucket's active page list for a suitable new | |
| 590 // active page. | |
| 591 // When it finds a suitable new active page (one that has free slots and is not | |
| 592 // empty), it is set as the new active page. If there is no suitable new | |
| 593 // active page, the current active page is set to the seed page. | |
| 594 // As potential pages are scanned, they are tidied up according to their state. | |
| 595 // Empty pages are swept on to the empty page list, decommitted pages on to the | |
| 596 // decommitted page list and full pages are unlinked from any list. | |
| 597 static bool partitionSetNewActivePage(PartitionBucket* bucket) | |
| 598 { | |
| 599 PartitionPage* page = bucket->activePagesHead; | |
| 600 if (page == &PartitionRootBase::gSeedPage) | |
| 601 return false; | |
| 602 | |
| 603 PartitionPage* nextPage; | |
| 604 | |
| 605 for (; page; page = nextPage) { | |
| 606 nextPage = page->nextPage; | |
| 607 ASSERT(page->bucket == bucket); | |
| 608 ASSERT(page != bucket->emptyPagesHead); | |
| 609 ASSERT(page != bucket->decommittedPagesHead); | |
| 610 | |
| 611 // Deal with empty and decommitted pages. | |
| 612 if (LIKELY(partitionPageStateIsActive(page))) { | |
| 613 // This page is usable because it has freelist entries, or has | |
| 614 // unprovisioned slots we can create freelist entries from. | |
| 615 bucket->activePagesHead = page; | |
| 616 return true; | |
| 617 } | |
| 618 if (LIKELY(partitionPageStateIsEmpty(page))) { | |
| 619 page->nextPage = bucket->emptyPagesHead; | |
| 620 bucket->emptyPagesHead = page; | |
| 621 } else if (LIKELY(partitionPageStateIsDecommitted(page))) { | |
| 622 page->nextPage = bucket->decommittedPagesHead; | |
| 623 bucket->decommittedPagesHead = page; | |
| 624 } else { | |
| 625 ASSERT(partitionPageStateIsFull(page)); | |
| 626 // If we get here, we found a full page. Skip over it too, and also | |
| 627 // tag it as full (via a negative value). We need it tagged so that | |
| 628 // free'ing can tell, and move it back into the active page list. | |
| 629 page->numAllocatedSlots = -page->numAllocatedSlots; | |
| 630 ++bucket->numFullPages; | |
| 631 // numFullPages is a uint16_t for efficient packing so guard against | |
| 632 // overflow to be safe. | |
| 633 if (UNLIKELY(!bucket->numFullPages)) | |
| 634 partitionBucketFull(); | |
| 635 // Not necessary but might help stop accidents. | |
| 636 page->nextPage = 0; | |
| 637 } | |
| 638 } | |
| 639 | |
| 640 bucket->activePagesHead = &PartitionRootGeneric::gSeedPage; | |
| 641 return false; | |
| 642 } | |
| 643 | |
| 644 static ALWAYS_INLINE PartitionDirectMapExtent* partitionPageToDirectMapExtent(Pa
rtitionPage* page) | |
| 645 { | |
| 646 ASSERT(partitionBucketIsDirectMapped(page->bucket)); | |
| 647 return reinterpret_cast<PartitionDirectMapExtent*>(reinterpret_cast<char*>(p
age) + 3 * kPageMetadataSize); | |
| 648 } | |
| 649 | |
| 650 static ALWAYS_INLINE void partitionPageSetRawSize(PartitionPage* page, size_t si
ze) | |
| 651 { | |
| 652 size_t* rawSizePtr = partitionPageGetRawSizePtr(page); | |
| 653 if (UNLIKELY(rawSizePtr != nullptr)) | |
| 654 *rawSizePtr = size; | |
| 655 } | |
| 656 | |
| 657 static ALWAYS_INLINE PartitionPage* partitionDirectMap(PartitionRootBase* root,
int flags, size_t rawSize) | |
| 658 { | |
| 659 size_t size = partitionDirectMapSize(rawSize); | |
| 660 | |
| 661 // Because we need to fake looking like a super page, we need to allocate | |
| 662 // a bunch of system pages more than "size": | |
| 663 // - The first few system pages are the partition page in which the super | |
| 664 // page metadata is stored. We fault just one system page out of a partition | |
| 665 // page sized clump. | |
| 666 // - We add a trailing guard page on 32-bit (on 64-bit we rely on the | |
| 667 // massive address space plus randomization instead). | |
| 668 size_t mapSize = size + kPartitionPageSize; | |
| 669 #if !CPU(64BIT) | |
| 670 mapSize += kSystemPageSize; | |
| 671 #endif | |
| 672 // Round up to the allocation granularity. | |
| 673 mapSize += kPageAllocationGranularityOffsetMask; | |
| 674 mapSize &= kPageAllocationGranularityBaseMask; | |
| 675 | |
| 676 // TODO: these pages will be zero-filled. Consider internalizing an | |
| 677 // allocZeroed() API so we can avoid a memset() entirely in this case. | |
| 678 char* ptr = reinterpret_cast<char*>(allocPages(0, mapSize, kSuperPageSize, P
ageAccessible)); | |
| 679 if (UNLIKELY(!ptr)) | |
| 680 return nullptr; | |
| 681 | |
| 682 size_t committedPageSize = size + kSystemPageSize; | |
| 683 root->totalSizeOfDirectMappedPages += committedPageSize; | |
| 684 partitionIncreaseCommittedPages(root, committedPageSize); | |
| 685 | |
| 686 char* slot = ptr + kPartitionPageSize; | |
| 687 setSystemPagesInaccessible(ptr + (kSystemPageSize * 2), kPartitionPageSize -
(kSystemPageSize * 2)); | |
| 688 #if !CPU(64BIT) | |
| 689 setSystemPagesInaccessible(ptr, kSystemPageSize); | |
| 690 setSystemPagesInaccessible(slot + size, kSystemPageSize); | |
| 691 #endif | |
| 692 | |
| 693 PartitionSuperPageExtentEntry* extent = reinterpret_cast<PartitionSuperPageE
xtentEntry*>(partitionSuperPageToMetadataArea(ptr)); | |
| 694 extent->root = root; | |
| 695 // The new structures are all located inside a fresh system page so they | |
| 696 // will all be zeroed out. These ASSERTs are for documentation. | |
| 697 ASSERT(!extent->superPageBase); | |
| 698 ASSERT(!extent->superPagesEnd); | |
| 699 ASSERT(!extent->next); | |
| 700 PartitionPage* page = partitionPointerToPageNoAlignmentCheck(slot); | |
| 701 PartitionBucket* bucket = reinterpret_cast<PartitionBucket*>(reinterpret_cas
t<char*>(page) + (kPageMetadataSize * 2)); | |
| 702 ASSERT(!page->nextPage); | |
| 703 ASSERT(!page->numAllocatedSlots); | |
| 704 ASSERT(!page->numUnprovisionedSlots); | |
| 705 ASSERT(!page->pageOffset); | |
| 706 ASSERT(!page->emptyCacheIndex); | |
| 707 page->bucket = bucket; | |
| 708 page->freelistHead = reinterpret_cast<PartitionFreelistEntry*>(slot); | |
| 709 PartitionFreelistEntry* nextEntry = reinterpret_cast<PartitionFreelistEntry*
>(slot); | |
| 710 nextEntry->next = partitionFreelistMask(0); | |
| 711 | |
| 712 ASSERT(!bucket->activePagesHead); | |
| 713 ASSERT(!bucket->emptyPagesHead); | |
| 714 ASSERT(!bucket->decommittedPagesHead); | |
| 715 ASSERT(!bucket->numSystemPagesPerSlotSpan); | |
| 716 ASSERT(!bucket->numFullPages); | |
| 717 bucket->slotSize = size; | |
| 718 | |
| 719 PartitionDirectMapExtent* mapExtent = partitionPageToDirectMapExtent(page); | |
| 720 mapExtent->mapSize = mapSize - kPartitionPageSize - kSystemPageSize; | |
| 721 mapExtent->bucket = bucket; | |
| 722 | |
| 723 // Maintain the doubly-linked list of all direct mappings. | |
| 724 mapExtent->nextExtent = root->directMapList; | |
| 725 if (mapExtent->nextExtent) | |
| 726 mapExtent->nextExtent->prevExtent = mapExtent; | |
| 727 mapExtent->prevExtent = nullptr; | |
| 728 root->directMapList = mapExtent; | |
| 729 | |
| 730 return page; | |
| 731 } | |
| 732 | |
| 733 static ALWAYS_INLINE void partitionDirectUnmap(PartitionPage* page) | |
| 734 { | |
| 735 PartitionRootBase* root = partitionPageToRoot(page); | |
| 736 const PartitionDirectMapExtent* extent = partitionPageToDirectMapExtent(page
); | |
| 737 size_t unmapSize = extent->mapSize; | |
| 738 | |
| 739 // Maintain the doubly-linked list of all direct mappings. | |
| 740 if (extent->prevExtent) { | |
| 741 ASSERT(extent->prevExtent->nextExtent == extent); | |
| 742 extent->prevExtent->nextExtent = extent->nextExtent; | |
| 743 } else { | |
| 744 root->directMapList = extent->nextExtent; | |
| 745 } | |
| 746 if (extent->nextExtent) { | |
| 747 ASSERT(extent->nextExtent->prevExtent == extent); | |
| 748 extent->nextExtent->prevExtent = extent->prevExtent; | |
| 749 } | |
| 750 | |
| 751 // Add on the size of the trailing guard page and preceeding partition | |
| 752 // page. | |
| 753 unmapSize += kPartitionPageSize + kSystemPageSize; | |
| 754 | |
| 755 size_t uncommittedPageSize = page->bucket->slotSize + kSystemPageSize; | |
| 756 partitionDecreaseCommittedPages(root, uncommittedPageSize); | |
| 757 ASSERT(root->totalSizeOfDirectMappedPages >= uncommittedPageSize); | |
| 758 root->totalSizeOfDirectMappedPages -= uncommittedPageSize; | |
| 759 | |
| 760 ASSERT(!(unmapSize & kPageAllocationGranularityOffsetMask)); | |
| 761 | |
| 762 char* ptr = reinterpret_cast<char*>(partitionPageToPointer(page)); | |
| 763 // Account for the mapping starting a partition page before the actual | |
| 764 // allocation address. | |
| 765 ptr -= kPartitionPageSize; | |
| 766 | |
| 767 freePages(ptr, unmapSize); | |
| 768 } | |
| 769 | |
| 770 void* partitionAllocSlowPath(PartitionRootBase* root, int flags, size_t size, Pa
rtitionBucket* bucket) | |
| 771 { | |
| 772 // The slow path is called when the freelist is empty. | |
| 773 ASSERT(!bucket->activePagesHead->freelistHead); | |
| 774 | |
| 775 PartitionPage* newPage = nullptr; | |
| 776 | |
| 777 // For the partitionAllocGeneric API, we have a bunch of buckets marked | |
| 778 // as special cases. We bounce them through to the slow path so that we | |
| 779 // can still have a blazing fast hot path due to lack of corner-case | |
| 780 // branches. | |
| 781 bool returnNull = flags & PartitionAllocReturnNull; | |
| 782 if (UNLIKELY(partitionBucketIsDirectMapped(bucket))) { | |
| 783 ASSERT(size > kGenericMaxBucketed); | |
| 784 ASSERT(bucket == &PartitionRootBase::gPagedBucket); | |
| 785 ASSERT(bucket->activePagesHead == &PartitionRootGeneric::gSeedPage); | |
| 786 if (size > kGenericMaxDirectMapped) { | |
| 787 if (returnNull) | |
| 788 return nullptr; | |
| 789 partitionExcessiveAllocationSize(); | |
| 790 } | |
| 791 newPage = partitionDirectMap(root, flags, size); | |
| 792 } else if (LIKELY(partitionSetNewActivePage(bucket))) { | |
| 793 // First, did we find an active page in the active pages list? | |
| 794 newPage = bucket->activePagesHead; | |
| 795 ASSERT(partitionPageStateIsActive(newPage)); | |
| 796 } else if (LIKELY(bucket->emptyPagesHead != nullptr) || LIKELY(bucket->decom
mittedPagesHead != nullptr)) { | |
| 797 // Second, look in our lists of empty and decommitted pages. | |
| 798 // Check empty pages first, which are preferred, but beware that an | |
| 799 // empty page might have been decommitted. | |
| 800 while (LIKELY((newPage = bucket->emptyPagesHead) != nullptr)) { | |
| 801 ASSERT(newPage->bucket == bucket); | |
| 802 ASSERT(partitionPageStateIsEmpty(newPage) || partitionPageStateIsDec
ommitted(newPage)); | |
| 803 bucket->emptyPagesHead = newPage->nextPage; | |
| 804 // Accept the empty page unless it got decommitted. | |
| 805 if (newPage->freelistHead) { | |
| 806 newPage->nextPage = nullptr; | |
| 807 break; | |
| 808 } | |
| 809 ASSERT(partitionPageStateIsDecommitted(newPage)); | |
| 810 newPage->nextPage = bucket->decommittedPagesHead; | |
| 811 bucket->decommittedPagesHead = newPage; | |
| 812 } | |
| 813 if (UNLIKELY(!newPage) && LIKELY(bucket->decommittedPagesHead != nullptr
)) { | |
| 814 newPage = bucket->decommittedPagesHead; | |
| 815 ASSERT(newPage->bucket == bucket); | |
| 816 ASSERT(partitionPageStateIsDecommitted(newPage)); | |
| 817 bucket->decommittedPagesHead = newPage->nextPage; | |
| 818 void* addr = partitionPageToPointer(newPage); | |
| 819 partitionRecommitSystemPages(root, addr, partitionBucketBytes(newPag
e->bucket)); | |
| 820 partitionPageReset(newPage); | |
| 821 } | |
| 822 ASSERT(newPage); | |
| 823 } else { | |
| 824 // Third. If we get here, we need a brand new page. | |
| 825 uint16_t numPartitionPages = partitionBucketPartitionPages(bucket); | |
| 826 void* rawPages = partitionAllocPartitionPages(root, flags, numPartitionP
ages); | |
| 827 if (LIKELY(rawPages != nullptr)) { | |
| 828 newPage = partitionPointerToPageNoAlignmentCheck(rawPages); | |
| 829 partitionPageSetup(newPage, bucket); | |
| 830 } | |
| 831 } | |
| 832 | |
| 833 // Bail if we had a memory allocation failure. | |
| 834 if (UNLIKELY(!newPage)) { | |
| 835 ASSERT(bucket->activePagesHead == &PartitionRootGeneric::gSeedPage); | |
| 836 if (returnNull) | |
| 837 return nullptr; | |
| 838 partitionOutOfMemory(root); | |
| 839 } | |
| 840 | |
| 841 bucket = newPage->bucket; | |
| 842 ASSERT(bucket != &PartitionRootBase::gPagedBucket); | |
| 843 bucket->activePagesHead = newPage; | |
| 844 partitionPageSetRawSize(newPage, size); | |
| 845 | |
| 846 // If we found an active page with free slots, or an empty page, we have a | |
| 847 // usable freelist head. | |
| 848 if (LIKELY(newPage->freelistHead != nullptr)) { | |
| 849 PartitionFreelistEntry* entry = newPage->freelistHead; | |
| 850 PartitionFreelistEntry* newHead = partitionFreelistMask(entry->next); | |
| 851 newPage->freelistHead = newHead; | |
| 852 newPage->numAllocatedSlots++; | |
| 853 return entry; | |
| 854 } | |
| 855 // Otherwise, we need to build the freelist. | |
| 856 ASSERT(newPage->numUnprovisionedSlots); | |
| 857 return partitionPageAllocAndFillFreelist(newPage); | |
| 858 } | |
| 859 | |
| 860 static ALWAYS_INLINE void partitionDecommitPage(PartitionRootBase* root, Partiti
onPage* page) | |
| 861 { | |
| 862 ASSERT(partitionPageStateIsEmpty(page)); | |
| 863 ASSERT(!partitionBucketIsDirectMapped(page->bucket)); | |
| 864 void* addr = partitionPageToPointer(page); | |
| 865 partitionDecommitSystemPages(root, addr, partitionBucketBytes(page->bucket))
; | |
| 866 | |
| 867 // We actually leave the decommitted page in the active list. We'll sweep | |
| 868 // it on to the decommitted page list when we next walk the active page | |
| 869 // list. | |
| 870 // Pulling this trick enables us to use a singly-linked page list for all | |
| 871 // cases, which is critical in keeping the page metadata structure down to | |
| 872 // 32 bytes in size. | |
| 873 page->freelistHead = 0; | |
| 874 page->numUnprovisionedSlots = 0; | |
| 875 ASSERT(partitionPageStateIsDecommitted(page)); | |
| 876 } | |
| 877 | |
| 878 static void partitionDecommitPageIfPossible(PartitionRootBase* root, PartitionPa
ge* page) | |
| 879 { | |
| 880 ASSERT(page->emptyCacheIndex >= 0); | |
| 881 ASSERT(static_cast<unsigned>(page->emptyCacheIndex) < kMaxFreeableSpans); | |
| 882 ASSERT(page == root->globalEmptyPageRing[page->emptyCacheIndex]); | |
| 883 page->emptyCacheIndex = -1; | |
| 884 if (partitionPageStateIsEmpty(page)) | |
| 885 partitionDecommitPage(root, page); | |
| 886 } | |
| 887 | |
| 888 static ALWAYS_INLINE void partitionRegisterEmptyPage(PartitionPage* page) | |
| 889 { | |
| 890 ASSERT(partitionPageStateIsEmpty(page)); | |
| 891 PartitionRootBase* root = partitionPageToRoot(page); | |
| 892 | |
| 893 // If the page is already registered as empty, give it another life. | |
| 894 if (page->emptyCacheIndex != -1) { | |
| 895 ASSERT(page->emptyCacheIndex >= 0); | |
| 896 ASSERT(static_cast<unsigned>(page->emptyCacheIndex) < kMaxFreeableSpans)
; | |
| 897 ASSERT(root->globalEmptyPageRing[page->emptyCacheIndex] == page); | |
| 898 root->globalEmptyPageRing[page->emptyCacheIndex] = 0; | |
| 899 } | |
| 900 | |
| 901 int16_t currentIndex = root->globalEmptyPageRingIndex; | |
| 902 PartitionPage* pageToDecommit = root->globalEmptyPageRing[currentIndex]; | |
| 903 // The page might well have been re-activated, filled up, etc. before we get | |
| 904 // around to looking at it here. | |
| 905 if (pageToDecommit) | |
| 906 partitionDecommitPageIfPossible(root, pageToDecommit); | |
| 907 | |
| 908 // We put the empty slot span on our global list of "pages that were once | |
| 909 // empty". thus providing it a bit of breathing room to get re-used before | |
| 910 // we really free it. This improves performance, particularly on Mac OS X | |
| 911 // which has subpar memory management performance. | |
| 912 root->globalEmptyPageRing[currentIndex] = page; | |
| 913 page->emptyCacheIndex = currentIndex; | |
| 914 ++currentIndex; | |
| 915 if (currentIndex == kMaxFreeableSpans) | |
| 916 currentIndex = 0; | |
| 917 root->globalEmptyPageRingIndex = currentIndex; | |
| 918 } | |
| 919 | |
| 920 static void partitionDecommitEmptyPages(PartitionRootBase* root) | |
| 921 { | |
| 922 for (size_t i = 0; i < kMaxFreeableSpans; ++i) { | |
| 923 PartitionPage* page = root->globalEmptyPageRing[i]; | |
| 924 if (page) | |
| 925 partitionDecommitPageIfPossible(root, page); | |
| 926 root->globalEmptyPageRing[i] = nullptr; | |
| 927 } | |
| 928 } | |
| 929 | |
| 930 void partitionFreeSlowPath(PartitionPage* page) | |
| 931 { | |
| 932 PartitionBucket* bucket = page->bucket; | |
| 933 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 934 if (LIKELY(page->numAllocatedSlots == 0)) { | |
| 935 // Page became fully unused. | |
| 936 if (UNLIKELY(partitionBucketIsDirectMapped(bucket))) { | |
| 937 partitionDirectUnmap(page); | |
| 938 return; | |
| 939 } | |
| 940 // If it's the current active page, change it. We bounce the page to | |
| 941 // the empty list as a force towards defragmentation. | |
| 942 if (LIKELY(page == bucket->activePagesHead)) | |
| 943 (void) partitionSetNewActivePage(bucket); | |
| 944 ASSERT(bucket->activePagesHead != page); | |
| 945 | |
| 946 partitionPageSetRawSize(page, 0); | |
| 947 ASSERT(!partitionPageGetRawSize(page)); | |
| 948 | |
| 949 partitionRegisterEmptyPage(page); | |
| 950 } else { | |
| 951 ASSERT(!partitionBucketIsDirectMapped(bucket)); | |
| 952 // Ensure that the page is full. That's the only valid case if we | |
| 953 // arrive here. | |
| 954 ASSERT(page->numAllocatedSlots < 0); | |
| 955 // A transition of numAllocatedSlots from 0 to -1 is not legal, and | |
| 956 // likely indicates a double-free. | |
| 957 SECURITY_CHECK(page->numAllocatedSlots != -1); | |
| 958 page->numAllocatedSlots = -page->numAllocatedSlots - 2; | |
| 959 ASSERT(page->numAllocatedSlots == partitionBucketSlots(bucket) - 1); | |
| 960 // Fully used page became partially used. It must be put back on the | |
| 961 // non-full page list. Also make it the current page to increase the | |
| 962 // chances of it being filled up again. The old current page will be | |
| 963 // the next page. | |
| 964 ASSERT(!page->nextPage); | |
| 965 if (LIKELY(bucket->activePagesHead != &PartitionRootGeneric::gSeedPage)) | |
| 966 page->nextPage = bucket->activePagesHead; | |
| 967 bucket->activePagesHead = page; | |
| 968 --bucket->numFullPages; | |
| 969 // Special case: for a partition page with just a single slot, it may | |
| 970 // now be empty and we want to run it through the empty logic. | |
| 971 if (UNLIKELY(page->numAllocatedSlots == 0)) | |
| 972 partitionFreeSlowPath(page); | |
| 973 } | |
| 974 } | |
| 975 | |
| 976 bool partitionReallocDirectMappedInPlace(PartitionRootGeneric* root, PartitionPa
ge* page, size_t rawSize) | |
| 977 { | |
| 978 ASSERT(partitionBucketIsDirectMapped(page->bucket)); | |
| 979 | |
| 980 rawSize = partitionCookieSizeAdjustAdd(rawSize); | |
| 981 | |
| 982 // Note that the new size might be a bucketed size; this function is called | |
| 983 // whenever we're reallocating a direct mapped allocation. | |
| 984 size_t newSize = partitionDirectMapSize(rawSize); | |
| 985 if (newSize < kGenericMinDirectMappedDownsize) | |
| 986 return false; | |
| 987 | |
| 988 // bucket->slotSize is the current size of the allocation. | |
| 989 size_t currentSize = page->bucket->slotSize; | |
| 990 if (newSize == currentSize) | |
| 991 return true; | |
| 992 | |
| 993 char* charPtr = static_cast<char*>(partitionPageToPointer(page)); | |
| 994 | |
| 995 if (newSize < currentSize) { | |
| 996 size_t mapSize = partitionPageToDirectMapExtent(page)->mapSize; | |
| 997 | |
| 998 // Don't reallocate in-place if new size is less than 80 % of the full | |
| 999 // map size, to avoid holding on to too much unused address space. | |
| 1000 if ((newSize / kSystemPageSize) * 5 < (mapSize / kSystemPageSize) * 4) | |
| 1001 return false; | |
| 1002 | |
| 1003 // Shrink by decommitting unneeded pages and making them inaccessible. | |
| 1004 size_t decommitSize = currentSize - newSize; | |
| 1005 partitionDecommitSystemPages(root, charPtr + newSize, decommitSize); | |
| 1006 setSystemPagesInaccessible(charPtr + newSize, decommitSize); | |
| 1007 } else if (newSize <= partitionPageToDirectMapExtent(page)->mapSize) { | |
| 1008 // Grow within the actually allocated memory. Just need to make the | |
| 1009 // pages accessible again. | |
| 1010 size_t recommitSize = newSize - currentSize; | |
| 1011 bool ret = setSystemPagesAccessible(charPtr + currentSize, recommitSize)
; | |
| 1012 RELEASE_ASSERT(ret); | |
| 1013 partitionRecommitSystemPages(root, charPtr + currentSize, recommitSize); | |
| 1014 | |
| 1015 #if ENABLE(ASSERT) | |
| 1016 memset(charPtr + currentSize, kUninitializedByte, recommitSize); | |
| 1017 #endif | |
| 1018 } else { | |
| 1019 // We can't perform the realloc in-place. | |
| 1020 // TODO: support this too when possible. | |
| 1021 return false; | |
| 1022 } | |
| 1023 | |
| 1024 #if ENABLE(ASSERT) | |
| 1025 // Write a new trailing cookie. | |
| 1026 partitionCookieWriteValue(charPtr + rawSize - kCookieSize); | |
| 1027 #endif | |
| 1028 | |
| 1029 partitionPageSetRawSize(page, rawSize); | |
| 1030 ASSERT(partitionPageGetRawSize(page) == rawSize); | |
| 1031 | |
| 1032 page->bucket->slotSize = newSize; | |
| 1033 return true; | |
| 1034 } | |
| 1035 | |
| 1036 void* partitionReallocGeneric(PartitionRootGeneric* root, void* ptr, size_t newS
ize, const char* typeName) | |
| 1037 { | |
| 1038 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) | |
| 1039 return realloc(ptr, newSize); | |
| 1040 #else | |
| 1041 if (UNLIKELY(!ptr)) | |
| 1042 return partitionAllocGeneric(root, newSize, typeName); | |
| 1043 if (UNLIKELY(!newSize)) { | |
| 1044 partitionFreeGeneric(root, ptr); | |
| 1045 return 0; | |
| 1046 } | |
| 1047 | |
| 1048 if (newSize > kGenericMaxDirectMapped) | |
| 1049 partitionExcessiveAllocationSize(); | |
| 1050 | |
| 1051 ASSERT(partitionPointerIsValid(partitionCookieFreePointerAdjust(ptr))); | |
| 1052 | |
| 1053 PartitionPage* page = partitionPointerToPage(partitionCookieFreePointerAdjus
t(ptr)); | |
| 1054 | |
| 1055 if (UNLIKELY(partitionBucketIsDirectMapped(page->bucket))) { | |
| 1056 // We may be able to perform the realloc in place by changing the | |
| 1057 // accessibility of memory pages and, if reducing the size, decommitting | |
| 1058 // them. | |
| 1059 if (partitionReallocDirectMappedInPlace(root, page, newSize)) { | |
| 1060 PartitionAllocHooks::reallocHookIfEnabled(ptr, ptr, newSize, typeNam
e); | |
| 1061 return ptr; | |
| 1062 } | |
| 1063 } | |
| 1064 | |
| 1065 size_t actualNewSize = partitionAllocActualSize(root, newSize); | |
| 1066 size_t actualOldSize = partitionAllocGetSize(ptr); | |
| 1067 | |
| 1068 // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the | |
| 1069 // new size is a significant percentage smaller. We could do the same if we | |
| 1070 // determine it is a win. | |
| 1071 if (actualNewSize == actualOldSize) { | |
| 1072 // Trying to allocate a block of size newSize would give us a block of | |
| 1073 // the same size as the one we've already got, so no point in doing | |
| 1074 // anything here. | |
| 1075 return ptr; | |
| 1076 } | |
| 1077 | |
| 1078 // This realloc cannot be resized in-place. Sadness. | |
| 1079 void* ret = partitionAllocGeneric(root, newSize, typeName); | |
| 1080 size_t copySize = actualOldSize; | |
| 1081 if (newSize < copySize) | |
| 1082 copySize = newSize; | |
| 1083 | |
| 1084 memcpy(ret, ptr, copySize); | |
| 1085 partitionFreeGeneric(root, ptr); | |
| 1086 return ret; | |
| 1087 #endif | |
| 1088 } | |
| 1089 | |
| 1090 static size_t partitionPurgePage(PartitionPage* page, bool discard) | |
| 1091 { | |
| 1092 const PartitionBucket* bucket = page->bucket; | |
| 1093 size_t slotSize = bucket->slotSize; | |
| 1094 if (slotSize < kSystemPageSize || !page->numAllocatedSlots) | |
| 1095 return 0; | |
| 1096 | |
| 1097 size_t bucketNumSlots = partitionBucketSlots(bucket); | |
| 1098 size_t discardableBytes = 0; | |
| 1099 | |
| 1100 size_t rawSize = partitionPageGetRawSize(const_cast<PartitionPage*>(page)); | |
| 1101 if (rawSize) { | |
| 1102 uint32_t usedBytes = static_cast<uint32_t>(WTF::roundUpToSystemPage(rawS
ize)); | |
| 1103 discardableBytes = bucket->slotSize - usedBytes; | |
| 1104 if (discardableBytes && discard) { | |
| 1105 char* ptr = reinterpret_cast<char*>(partitionPageToPointer(page)); | |
| 1106 ptr += usedBytes; | |
| 1107 discardSystemPages(ptr, discardableBytes); | |
| 1108 } | |
| 1109 return discardableBytes; | |
| 1110 } | |
| 1111 | |
| 1112 const size_t maxSlotCount = (kPartitionPageSize * kMaxPartitionPagesPerSlotS
pan) / kSystemPageSize; | |
| 1113 ASSERT(bucketNumSlots <= maxSlotCount); | |
| 1114 ASSERT(page->numUnprovisionedSlots < bucketNumSlots); | |
| 1115 size_t numSlots = bucketNumSlots - page->numUnprovisionedSlots; | |
| 1116 char slotUsage[maxSlotCount]; | |
| 1117 size_t lastSlot = static_cast<size_t>(-1); | |
| 1118 memset(slotUsage, 1, numSlots); | |
| 1119 char* ptr = reinterpret_cast<char*>(partitionPageToPointer(page)); | |
| 1120 PartitionFreelistEntry* entry = page->freelistHead; | |
| 1121 // First, walk the freelist for this page and make a bitmap of which slots | |
| 1122 // are not in use. | |
| 1123 while (entry) { | |
| 1124 size_t slotIndex = (reinterpret_cast<char*>(entry) - ptr) / slotSize; | |
| 1125 ASSERT(slotIndex < numSlots); | |
| 1126 slotUsage[slotIndex] = 0; | |
| 1127 entry = partitionFreelistMask(entry->next); | |
| 1128 // If we have a slot where the masked freelist entry is 0, we can | |
| 1129 // actually discard that freelist entry because touching a discarded | |
| 1130 // page is guaranteed to return original content or 0. | |
| 1131 // (Note that this optimization won't fire on big endian machines | |
| 1132 // because the masking function is negation.) | |
| 1133 if (!partitionFreelistMask(entry)) | |
| 1134 lastSlot = slotIndex; | |
| 1135 } | |
| 1136 | |
| 1137 // If the slot(s) at the end of the slot span are not in used, we can | |
| 1138 // truncate them entirely and rewrite the freelist. | |
| 1139 size_t truncatedSlots = 0; | |
| 1140 while (!slotUsage[numSlots - 1]) { | |
| 1141 truncatedSlots++; | |
| 1142 numSlots--; | |
| 1143 ASSERT(numSlots); | |
| 1144 } | |
| 1145 // First, do the work of calculating the discardable bytes. Don't actually | |
| 1146 // discard anything unless the discard flag was passed in. | |
| 1147 char* beginPtr = nullptr; | |
| 1148 char* endPtr = nullptr; | |
| 1149 size_t unprovisionedBytes = 0; | |
| 1150 if (truncatedSlots) { | |
| 1151 beginPtr = ptr + (numSlots * slotSize); | |
| 1152 endPtr = beginPtr + (slotSize * truncatedSlots); | |
| 1153 beginPtr = reinterpret_cast<char*>(WTF::roundUpToSystemPage(reinterpret_
cast<size_t>(beginPtr))); | |
| 1154 // We round the end pointer here up and not down because we're at the | |
| 1155 // end of a slot span, so we "own" all the way up the page boundary. | |
| 1156 endPtr = reinterpret_cast<char*>(WTF::roundUpToSystemPage(reinterpret_ca
st<size_t>(endPtr))); | |
| 1157 ASSERT(endPtr <= ptr + partitionBucketBytes(bucket)); | |
| 1158 if (beginPtr < endPtr) { | |
| 1159 unprovisionedBytes = endPtr - beginPtr; | |
| 1160 discardableBytes += unprovisionedBytes; | |
| 1161 } | |
| 1162 } | |
| 1163 if (unprovisionedBytes && discard) { | |
| 1164 ASSERT(truncatedSlots > 0); | |
| 1165 size_t numNewEntries = 0; | |
| 1166 page->numUnprovisionedSlots += static_cast<uint16_t>(truncatedSlots); | |
| 1167 // Rewrite the freelist. | |
| 1168 PartitionFreelistEntry** entryPtr = &page->freelistHead; | |
| 1169 for (size_t slotIndex = 0; slotIndex < numSlots; ++slotIndex) { | |
| 1170 if (slotUsage[slotIndex]) | |
| 1171 continue; | |
| 1172 PartitionFreelistEntry* entry = reinterpret_cast<PartitionFreelistEn
try*>(ptr + (slotSize * slotIndex)); | |
| 1173 *entryPtr = partitionFreelistMask(entry); | |
| 1174 entryPtr = reinterpret_cast<PartitionFreelistEntry**>(entry); | |
| 1175 numNewEntries++; | |
| 1176 } | |
| 1177 // Terminate the freelist chain. | |
| 1178 *entryPtr = nullptr; | |
| 1179 // The freelist head is stored unmasked. | |
| 1180 page->freelistHead = partitionFreelistMask(page->freelistHead); | |
| 1181 ASSERT(numNewEntries == numSlots - page->numAllocatedSlots); | |
| 1182 // Discard the memory. | |
| 1183 discardSystemPages(beginPtr, unprovisionedBytes); | |
| 1184 } | |
| 1185 | |
| 1186 // Next, walk the slots and for any not in use, consider where the system | |
| 1187 // page boundaries occur. We can release any system pages back to the | |
| 1188 // system as long as we don't interfere with a freelist pointer or an | |
| 1189 // adjacent slot. | |
| 1190 for (size_t i = 0; i < numSlots; ++i) { | |
| 1191 if (slotUsage[i]) | |
| 1192 continue; | |
| 1193 // The first address we can safely discard is just after the freelist | |
| 1194 // pointer. There's one quirk: if the freelist pointer is actually a | |
| 1195 // null, we can discard that pointer value too. | |
| 1196 char* beginPtr = ptr + (i * slotSize); | |
| 1197 char* endPtr = beginPtr + slotSize; | |
| 1198 if (i != lastSlot) | |
| 1199 beginPtr += sizeof(PartitionFreelistEntry); | |
| 1200 beginPtr = reinterpret_cast<char*>(WTF::roundUpToSystemPage(reinterpret_
cast<size_t>(beginPtr))); | |
| 1201 endPtr = reinterpret_cast<char*>(WTF::roundDownToSystemPage(reinterpret_
cast<size_t>(endPtr))); | |
| 1202 if (beginPtr < endPtr) { | |
| 1203 size_t partialSlotBytes = endPtr - beginPtr; | |
| 1204 discardableBytes += partialSlotBytes; | |
| 1205 if (discard) | |
| 1206 discardSystemPages(beginPtr, partialSlotBytes); | |
| 1207 } | |
| 1208 } | |
| 1209 return discardableBytes; | |
| 1210 } | |
| 1211 | |
| 1212 static void partitionPurgeBucket(PartitionBucket* bucket) | |
| 1213 { | |
| 1214 if (bucket->activePagesHead != &PartitionRootGeneric::gSeedPage) { | |
| 1215 for (PartitionPage* page = bucket->activePagesHead; page; page = page->n
extPage) { | |
| 1216 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 1217 (void) partitionPurgePage(page, true); | |
| 1218 } | |
| 1219 } | |
| 1220 } | |
| 1221 | |
| 1222 void partitionPurgeMemory(PartitionRoot* root, int flags) | |
| 1223 { | |
| 1224 if (flags & PartitionPurgeDecommitEmptyPages) | |
| 1225 partitionDecommitEmptyPages(root); | |
| 1226 // We don't currently do anything for PartitionPurgeDiscardUnusedSystemPages | |
| 1227 // here because that flag is only useful for allocations >= system page | |
| 1228 // size. We only have allocations that large inside generic partitions | |
| 1229 // at the moment. | |
| 1230 } | |
| 1231 | |
| 1232 void partitionPurgeMemoryGeneric(PartitionRootGeneric* root, int flags) | |
| 1233 { | |
| 1234 SpinLock::Guard guard(root->lock); | |
| 1235 if (flags & PartitionPurgeDecommitEmptyPages) | |
| 1236 partitionDecommitEmptyPages(root); | |
| 1237 if (flags & PartitionPurgeDiscardUnusedSystemPages) { | |
| 1238 for (size_t i = 0; i < kGenericNumBuckets; ++i) { | |
| 1239 PartitionBucket* bucket = &root->buckets[i]; | |
| 1240 if (bucket->slotSize >= kSystemPageSize) | |
| 1241 partitionPurgeBucket(bucket); | |
| 1242 } | |
| 1243 } | |
| 1244 } | |
| 1245 | |
| 1246 static void partitionDumpPageStats(PartitionBucketMemoryStats* statsOut, const P
artitionPage* page) | |
| 1247 { | |
| 1248 uint16_t bucketNumSlots = partitionBucketSlots(page->bucket); | |
| 1249 | |
| 1250 if (partitionPageStateIsDecommitted(page)) { | |
| 1251 ++statsOut->numDecommittedPages; | |
| 1252 return; | |
| 1253 } | |
| 1254 | |
| 1255 statsOut->discardableBytes += partitionPurgePage(const_cast<PartitionPage*>(
page), false); | |
| 1256 | |
| 1257 size_t rawSize = partitionPageGetRawSize(const_cast<PartitionPage*>(page)); | |
| 1258 if (rawSize) | |
| 1259 statsOut->activeBytes += static_cast<uint32_t>(rawSize); | |
| 1260 else | |
| 1261 statsOut->activeBytes += (page->numAllocatedSlots * statsOut->bucketSlot
Size); | |
| 1262 | |
| 1263 size_t pageBytesResident = WTF::roundUpToSystemPage((bucketNumSlots - page->
numUnprovisionedSlots) * statsOut->bucketSlotSize); | |
| 1264 statsOut->residentBytes += pageBytesResident; | |
| 1265 if (partitionPageStateIsEmpty(page)) { | |
| 1266 statsOut->decommittableBytes += pageBytesResident; | |
| 1267 ++statsOut->numEmptyPages; | |
| 1268 } else if (partitionPageStateIsFull(page)) { | |
| 1269 ++statsOut->numFullPages; | |
| 1270 } else { | |
| 1271 ASSERT(partitionPageStateIsActive(page)); | |
| 1272 ++statsOut->numActivePages; | |
| 1273 } | |
| 1274 } | |
| 1275 | |
| 1276 static void partitionDumpBucketStats(PartitionBucketMemoryStats* statsOut, const
PartitionBucket* bucket) | |
| 1277 { | |
| 1278 ASSERT(!partitionBucketIsDirectMapped(bucket)); | |
| 1279 statsOut->isValid = false; | |
| 1280 // If the active page list is empty (== &PartitionRootGeneric::gSeedPage), | |
| 1281 // the bucket might still need to be reported if it has a list of empty, | |
| 1282 // decommitted or full pages. | |
| 1283 if (bucket->activePagesHead == &PartitionRootGeneric::gSeedPage && !bucket->
emptyPagesHead && !bucket->decommittedPagesHead && !bucket->numFullPages) | |
| 1284 return; | |
| 1285 | |
| 1286 memset(statsOut, '\0', sizeof(*statsOut)); | |
| 1287 statsOut->isValid = true; | |
| 1288 statsOut->isDirectMap = false; | |
| 1289 statsOut->numFullPages = static_cast<size_t>(bucket->numFullPages); | |
| 1290 statsOut->bucketSlotSize = bucket->slotSize; | |
| 1291 uint16_t bucketNumSlots = partitionBucketSlots(bucket); | |
| 1292 size_t bucketUsefulStorage = statsOut->bucketSlotSize * bucketNumSlots; | |
| 1293 statsOut->allocatedPageSize = partitionBucketBytes(bucket); | |
| 1294 statsOut->activeBytes = bucket->numFullPages * bucketUsefulStorage; | |
| 1295 statsOut->residentBytes = bucket->numFullPages * statsOut->allocatedPageSize
; | |
| 1296 | |
| 1297 for (const PartitionPage* page = bucket->emptyPagesHead; page; page = page->
nextPage) { | |
| 1298 ASSERT(partitionPageStateIsEmpty(page) || partitionPageStateIsDecommitte
d(page)); | |
| 1299 partitionDumpPageStats(statsOut, page); | |
| 1300 } | |
| 1301 for (const PartitionPage* page = bucket->decommittedPagesHead; page; page =
page->nextPage) { | |
| 1302 ASSERT(partitionPageStateIsDecommitted(page)); | |
| 1303 partitionDumpPageStats(statsOut, page); | |
| 1304 } | |
| 1305 | |
| 1306 if (bucket->activePagesHead != &PartitionRootGeneric::gSeedPage) { | |
| 1307 for (const PartitionPage* page = bucket->activePagesHead; page; page = p
age->nextPage) { | |
| 1308 ASSERT(page != &PartitionRootGeneric::gSeedPage); | |
| 1309 partitionDumpPageStats(statsOut, page); | |
| 1310 } | |
| 1311 } | |
| 1312 } | |
| 1313 | |
| 1314 void partitionDumpStatsGeneric(PartitionRootGeneric* partition, const char* part
itionName, bool isLightDump, PartitionStatsDumper* partitionStatsDumper) | |
| 1315 { | |
| 1316 PartitionBucketMemoryStats bucketStats[kGenericNumBuckets]; | |
| 1317 static const size_t kMaxReportableDirectMaps = 4096; | |
| 1318 uint32_t directMapLengths[kMaxReportableDirectMaps]; | |
| 1319 size_t numDirectMappedAllocations = 0; | |
| 1320 | |
| 1321 { | |
| 1322 SpinLock::Guard guard(partition->lock); | |
| 1323 | |
| 1324 for (size_t i = 0; i < kGenericNumBuckets; ++i) { | |
| 1325 const PartitionBucket* bucket = &partition->buckets[i]; | |
| 1326 // Don't report the pseudo buckets that the generic allocator sets u
p in | |
| 1327 // order to preserve a fast size->bucket map (see | |
| 1328 // partitionAllocGenericInit for details). | |
| 1329 if (!bucket->activePagesHead) | |
| 1330 bucketStats[i].isValid = false; | |
| 1331 else | |
| 1332 partitionDumpBucketStats(&bucketStats[i], bucket); | |
| 1333 } | |
| 1334 | |
| 1335 for (PartitionDirectMapExtent* extent = partition->directMapList; extent
; extent = extent->nextExtent) { | |
| 1336 ASSERT(!extent->nextExtent || extent->nextExtent->prevExtent == exte
nt); | |
| 1337 directMapLengths[numDirectMappedAllocations] = extent->bucket->slotS
ize; | |
| 1338 ++numDirectMappedAllocations; | |
| 1339 if (numDirectMappedAllocations == kMaxReportableDirectMaps) | |
| 1340 break; | |
| 1341 } | |
| 1342 } | |
| 1343 | |
| 1344 // partitionsDumpBucketStats is called after collecting stats because it | |
| 1345 // can try to allocate using PartitionAllocGeneric and it can't obtain the | |
| 1346 // lock. | |
| 1347 PartitionMemoryStats partitionStats = { 0 }; | |
| 1348 partitionStats.totalMmappedBytes = partition->totalSizeOfSuperPages + partit
ion->totalSizeOfDirectMappedPages; | |
| 1349 partitionStats.totalCommittedBytes = partition->totalSizeOfCommittedPages; | |
| 1350 for (size_t i = 0; i < kGenericNumBuckets; ++i) { | |
| 1351 if (bucketStats[i].isValid) { | |
| 1352 partitionStats.totalResidentBytes += bucketStats[i].residentBytes; | |
| 1353 partitionStats.totalActiveBytes += bucketStats[i].activeBytes; | |
| 1354 partitionStats.totalDecommittableBytes += bucketStats[i].decommittab
leBytes; | |
| 1355 partitionStats.totalDiscardableBytes += bucketStats[i].discardableBy
tes; | |
| 1356 if (!isLightDump) | |
| 1357 partitionStatsDumper->partitionsDumpBucketStats(partitionName, &
bucketStats[i]); | |
| 1358 } | |
| 1359 } | |
| 1360 | |
| 1361 size_t directMappedAllocationsTotalSize = 0; | |
| 1362 for (size_t i = 0; i < numDirectMappedAllocations; ++i) { | |
| 1363 PartitionBucketMemoryStats stats; | |
| 1364 memset(&stats, '\0', sizeof(stats)); | |
| 1365 stats.isValid = true; | |
| 1366 stats.isDirectMap = true; | |
| 1367 stats.numFullPages = 1; | |
| 1368 uint32_t size = directMapLengths[i]; | |
| 1369 stats.allocatedPageSize = size; | |
| 1370 stats.bucketSlotSize = size; | |
| 1371 stats.activeBytes = size; | |
| 1372 stats.residentBytes = size; | |
| 1373 directMappedAllocationsTotalSize += size; | |
| 1374 partitionStatsDumper->partitionsDumpBucketStats(partitionName, &stats); | |
| 1375 } | |
| 1376 partitionStats.totalResidentBytes += directMappedAllocationsTotalSize; | |
| 1377 partitionStats.totalActiveBytes += directMappedAllocationsTotalSize; | |
| 1378 partitionStatsDumper->partitionDumpTotals(partitionName, &partitionStats); | |
| 1379 } | |
| 1380 | |
| 1381 void partitionDumpStats(PartitionRoot* partition, const char* partitionName, boo
l isLightDump, PartitionStatsDumper* partitionStatsDumper) | |
| 1382 { | |
| 1383 static const size_t kMaxReportableBuckets = 4096 / sizeof(void*); | |
| 1384 PartitionBucketMemoryStats memoryStats[kMaxReportableBuckets]; | |
| 1385 const size_t partitionNumBuckets = partition->numBuckets; | |
| 1386 ASSERT(partitionNumBuckets <= kMaxReportableBuckets); | |
| 1387 | |
| 1388 for (size_t i = 0; i < partitionNumBuckets; ++i) | |
| 1389 partitionDumpBucketStats(&memoryStats[i], &partition->buckets()[i]); | |
| 1390 | |
| 1391 // partitionsDumpBucketStats is called after collecting stats because it | |
| 1392 // can use PartitionAlloc to allocate and this can affect the statistics. | |
| 1393 PartitionMemoryStats partitionStats = { 0 }; | |
| 1394 partitionStats.totalMmappedBytes = partition->totalSizeOfSuperPages; | |
| 1395 partitionStats.totalCommittedBytes = partition->totalSizeOfCommittedPages; | |
| 1396 ASSERT(!partition->totalSizeOfDirectMappedPages); | |
| 1397 for (size_t i = 0; i < partitionNumBuckets; ++i) { | |
| 1398 if (memoryStats[i].isValid) { | |
| 1399 partitionStats.totalResidentBytes += memoryStats[i].residentBytes; | |
| 1400 partitionStats.totalActiveBytes += memoryStats[i].activeBytes; | |
| 1401 partitionStats.totalDecommittableBytes += memoryStats[i].decommittab
leBytes; | |
| 1402 partitionStats.totalDiscardableBytes += memoryStats[i].discardableBy
tes; | |
| 1403 if (!isLightDump) | |
| 1404 partitionStatsDumper->partitionsDumpBucketStats(partitionName, &
memoryStats[i]); | |
| 1405 } | |
| 1406 } | |
| 1407 partitionStatsDumper->partitionDumpTotals(partitionName, &partitionStats); | |
| 1408 } | |
| 1409 | |
| 1410 } // namespace WTF | |
| 1411 | |
| OLD | NEW |