1*76559068SAndroid Build Coastguard Worker //===-- primary64.h ---------------------------------------------*- C++ -*-===// 2*76559068SAndroid Build Coastguard Worker // 3*76559068SAndroid Build Coastguard Worker // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*76559068SAndroid Build Coastguard Worker // See https://llvm.org/LICENSE.txt for license information. 5*76559068SAndroid Build Coastguard Worker // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*76559068SAndroid Build Coastguard Worker // 7*76559068SAndroid Build Coastguard Worker //===----------------------------------------------------------------------===// 8*76559068SAndroid Build Coastguard Worker 9*76559068SAndroid Build Coastguard Worker #ifndef SCUDO_PRIMARY64_H_ 10*76559068SAndroid Build Coastguard Worker #define SCUDO_PRIMARY64_H_ 11*76559068SAndroid Build Coastguard Worker 12*76559068SAndroid Build Coastguard Worker #include "allocator_common.h" 13*76559068SAndroid Build Coastguard Worker #include "bytemap.h" 14*76559068SAndroid Build Coastguard Worker #include "common.h" 15*76559068SAndroid Build Coastguard Worker #include "condition_variable.h" 16*76559068SAndroid Build Coastguard Worker #include "list.h" 17*76559068SAndroid Build Coastguard Worker #include "local_cache.h" 18*76559068SAndroid Build Coastguard Worker #include "mem_map.h" 19*76559068SAndroid Build Coastguard Worker #include "memtag.h" 20*76559068SAndroid Build Coastguard Worker #include "options.h" 21*76559068SAndroid Build Coastguard Worker #include "release.h" 22*76559068SAndroid Build Coastguard Worker #include "stats.h" 23*76559068SAndroid Build Coastguard Worker #include "string_utils.h" 24*76559068SAndroid Build Coastguard Worker #include "thread_annotations.h" 25*76559068SAndroid Build Coastguard Worker 26*76559068SAndroid Build Coastguard Worker namespace scudo { 27*76559068SAndroid Build Coastguard Worker 28*76559068SAndroid Build Coastguard Worker // SizeClassAllocator64 is an allocator tuned for 64-bit address space. 29*76559068SAndroid Build Coastguard Worker // 30*76559068SAndroid Build Coastguard Worker // It starts by reserving NumClasses * 2^RegionSizeLog bytes, equally divided in 31*76559068SAndroid Build Coastguard Worker // Regions, specific to each size class. Note that the base of that mapping is 32*76559068SAndroid Build Coastguard Worker // random (based to the platform specific map() capabilities). If 33*76559068SAndroid Build Coastguard Worker // PrimaryEnableRandomOffset is set, each Region actually starts at a random 34*76559068SAndroid Build Coastguard Worker // offset from its base. 35*76559068SAndroid Build Coastguard Worker // 36*76559068SAndroid Build Coastguard Worker // Regions are mapped incrementally on demand to fulfill allocation requests, 37*76559068SAndroid Build Coastguard Worker // those mappings being split into equally sized Blocks based on the size class 38*76559068SAndroid Build Coastguard Worker // they belong to. The Blocks created are shuffled to prevent predictable 39*76559068SAndroid Build Coastguard Worker // address patterns (the predictability increases with the size of the Blocks). 40*76559068SAndroid Build Coastguard Worker // 41*76559068SAndroid Build Coastguard Worker // The 1st Region (for size class 0) holds the TransferBatches. This is a 42*76559068SAndroid Build Coastguard Worker // structure used to transfer arrays of available pointers from the class size 43*76559068SAndroid Build Coastguard Worker // freelist to the thread specific freelist, and back. 44*76559068SAndroid Build Coastguard Worker // 45*76559068SAndroid Build Coastguard Worker // The memory used by this allocator is never unmapped, but can be partially 46*76559068SAndroid Build Coastguard Worker // released if the platform allows for it. 47*76559068SAndroid Build Coastguard Worker 48*76559068SAndroid Build Coastguard Worker template <typename Config> class SizeClassAllocator64 { 49*76559068SAndroid Build Coastguard Worker public: 50*76559068SAndroid Build Coastguard Worker typedef typename Config::CompactPtrT CompactPtrT; 51*76559068SAndroid Build Coastguard Worker typedef typename Config::SizeClassMap SizeClassMap; 52*76559068SAndroid Build Coastguard Worker typedef typename Config::ConditionVariableT ConditionVariableT; 53*76559068SAndroid Build Coastguard Worker static const uptr CompactPtrScale = Config::getCompactPtrScale(); 54*76559068SAndroid Build Coastguard Worker static const uptr RegionSizeLog = Config::getRegionSizeLog(); 55*76559068SAndroid Build Coastguard Worker static const uptr GroupSizeLog = Config::getGroupSizeLog(); 56*76559068SAndroid Build Coastguard Worker static_assert(RegionSizeLog >= GroupSizeLog, 57*76559068SAndroid Build Coastguard Worker "Group size shouldn't be greater than the region size"); 58*76559068SAndroid Build Coastguard Worker static const uptr GroupScale = GroupSizeLog - CompactPtrScale; 59*76559068SAndroid Build Coastguard Worker typedef SizeClassAllocator64<Config> ThisT; 60*76559068SAndroid Build Coastguard Worker typedef SizeClassAllocatorLocalCache<ThisT> CacheT; 61*76559068SAndroid Build Coastguard Worker typedef TransferBatch<ThisT> TransferBatchT; 62*76559068SAndroid Build Coastguard Worker typedef BatchGroup<ThisT> BatchGroupT; 63*76559068SAndroid Build Coastguard Worker 64*76559068SAndroid Build Coastguard Worker // BachClass is used to store internal metadata so it needs to be at least as 65*76559068SAndroid Build Coastguard Worker // large as the largest data structure. getSizeByClassId(uptr ClassId)66*76559068SAndroid Build Coastguard Worker static uptr getSizeByClassId(uptr ClassId) { 67*76559068SAndroid Build Coastguard Worker return (ClassId == SizeClassMap::BatchClassId) 68*76559068SAndroid Build Coastguard Worker ? roundUp(Max(sizeof(TransferBatchT), sizeof(BatchGroupT)), 69*76559068SAndroid Build Coastguard Worker 1U << CompactPtrScale) 70*76559068SAndroid Build Coastguard Worker : SizeClassMap::getSizeByClassId(ClassId); 71*76559068SAndroid Build Coastguard Worker } 72*76559068SAndroid Build Coastguard Worker canAllocate(uptr Size)73*76559068SAndroid Build Coastguard Worker static bool canAllocate(uptr Size) { return Size <= SizeClassMap::MaxSize; } 74*76559068SAndroid Build Coastguard Worker conditionVariableEnabled()75*76559068SAndroid Build Coastguard Worker static bool conditionVariableEnabled() { 76*76559068SAndroid Build Coastguard Worker return Config::hasConditionVariableT(); 77*76559068SAndroid Build Coastguard Worker } 78*76559068SAndroid Build Coastguard Worker init(s32 ReleaseToOsInterval)79*76559068SAndroid Build Coastguard Worker void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS { 80*76559068SAndroid Build Coastguard Worker DCHECK(isAligned(reinterpret_cast<uptr>(this), alignof(ThisT))); 81*76559068SAndroid Build Coastguard Worker 82*76559068SAndroid Build Coastguard Worker const uptr PageSize = getPageSizeCached(); 83*76559068SAndroid Build Coastguard Worker const uptr GroupSize = (1UL << GroupSizeLog); 84*76559068SAndroid Build Coastguard Worker const uptr PagesInGroup = GroupSize / PageSize; 85*76559068SAndroid Build Coastguard Worker const uptr MinSizeClass = getSizeByClassId(1); 86*76559068SAndroid Build Coastguard Worker // When trying to release pages back to memory, visiting smaller size 87*76559068SAndroid Build Coastguard Worker // classes is expensive. Therefore, we only try to release smaller size 88*76559068SAndroid Build Coastguard Worker // classes when the amount of free blocks goes over a certain threshold (See 89*76559068SAndroid Build Coastguard Worker // the comment in releaseToOSMaybe() for more details). For example, for 90*76559068SAndroid Build Coastguard Worker // size class 32, we only do the release when the size of free blocks is 91*76559068SAndroid Build Coastguard Worker // greater than 97% of pages in a group. However, this may introduce another 92*76559068SAndroid Build Coastguard Worker // issue that if the number of free blocks is bouncing between 97% ~ 100%. 93*76559068SAndroid Build Coastguard Worker // Which means we may try many page releases but only release very few of 94*76559068SAndroid Build Coastguard Worker // them (less than 3% in a group). Even though we have 95*76559068SAndroid Build Coastguard Worker // `&ReleaseToOsIntervalMs` which slightly reduce the frequency of these 96*76559068SAndroid Build Coastguard Worker // calls but it will be better to have another guard to mitigate this issue. 97*76559068SAndroid Build Coastguard Worker // 98*76559068SAndroid Build Coastguard Worker // Here we add another constraint on the minimum size requirement. The 99*76559068SAndroid Build Coastguard Worker // constraint is determined by the size of in-use blocks in the minimal size 100*76559068SAndroid Build Coastguard Worker // class. Take size class 32 as an example, 101*76559068SAndroid Build Coastguard Worker // 102*76559068SAndroid Build Coastguard Worker // +- one memory group -+ 103*76559068SAndroid Build Coastguard Worker // +----------------------+------+ 104*76559068SAndroid Build Coastguard Worker // | 97% of free blocks | | 105*76559068SAndroid Build Coastguard Worker // +----------------------+------+ 106*76559068SAndroid Build Coastguard Worker // \ / 107*76559068SAndroid Build Coastguard Worker // 3% in-use blocks 108*76559068SAndroid Build Coastguard Worker // 109*76559068SAndroid Build Coastguard Worker // * The release size threshold is 97%. 110*76559068SAndroid Build Coastguard Worker // 111*76559068SAndroid Build Coastguard Worker // The 3% size in a group is about 7 pages. For two consecutive 112*76559068SAndroid Build Coastguard Worker // releaseToOSMaybe(), we require the difference between `PushedBlocks` 113*76559068SAndroid Build Coastguard Worker // should be greater than 7 pages. This mitigates the page releasing 114*76559068SAndroid Build Coastguard Worker // thrashing which is caused by memory usage bouncing around the threshold. 115*76559068SAndroid Build Coastguard Worker // The smallest size class takes longest time to do the page release so we 116*76559068SAndroid Build Coastguard Worker // use its size of in-use blocks as a heuristic. 117*76559068SAndroid Build Coastguard Worker SmallerBlockReleasePageDelta = 118*76559068SAndroid Build Coastguard Worker PagesInGroup * (1 + MinSizeClass / 16U) / 100; 119*76559068SAndroid Build Coastguard Worker 120*76559068SAndroid Build Coastguard Worker u32 Seed; 121*76559068SAndroid Build Coastguard Worker const u64 Time = getMonotonicTimeFast(); 122*76559068SAndroid Build Coastguard Worker if (!getRandom(reinterpret_cast<void *>(&Seed), sizeof(Seed))) 123*76559068SAndroid Build Coastguard Worker Seed = static_cast<u32>(Time ^ (reinterpret_cast<uptr>(&Seed) >> 12)); 124*76559068SAndroid Build Coastguard Worker 125*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) 126*76559068SAndroid Build Coastguard Worker getRegionInfo(I)->RandState = getRandomU32(&Seed); 127*76559068SAndroid Build Coastguard Worker 128*76559068SAndroid Build Coastguard Worker if (Config::getEnableContiguousRegions()) { 129*76559068SAndroid Build Coastguard Worker ReservedMemoryT ReservedMemory = {}; 130*76559068SAndroid Build Coastguard Worker // Reserve the space required for the Primary. 131*76559068SAndroid Build Coastguard Worker CHECK(ReservedMemory.create(/*Addr=*/0U, RegionSize * NumClasses, 132*76559068SAndroid Build Coastguard Worker "scudo:primary_reserve")); 133*76559068SAndroid Build Coastguard Worker const uptr PrimaryBase = ReservedMemory.getBase(); 134*76559068SAndroid Build Coastguard Worker 135*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 136*76559068SAndroid Build Coastguard Worker MemMapT RegionMemMap = ReservedMemory.dispatch( 137*76559068SAndroid Build Coastguard Worker PrimaryBase + (I << RegionSizeLog), RegionSize); 138*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 139*76559068SAndroid Build Coastguard Worker 140*76559068SAndroid Build Coastguard Worker initRegion(Region, I, RegionMemMap, Config::getEnableRandomOffset()); 141*76559068SAndroid Build Coastguard Worker } 142*76559068SAndroid Build Coastguard Worker shuffle(RegionInfoArray, NumClasses, &Seed); 143*76559068SAndroid Build Coastguard Worker } 144*76559068SAndroid Build Coastguard Worker 145*76559068SAndroid Build Coastguard Worker // The binding should be done after region shuffling so that it won't bind 146*76559068SAndroid Build Coastguard Worker // the FLLock from the wrong region. 147*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) 148*76559068SAndroid Build Coastguard Worker getRegionInfo(I)->FLLockCV.bindTestOnly(getRegionInfo(I)->FLLock); 149*76559068SAndroid Build Coastguard Worker 150*76559068SAndroid Build Coastguard Worker // The default value in the primary config has the higher priority. 151*76559068SAndroid Build Coastguard Worker if (Config::getDefaultReleaseToOsIntervalMs() != INT32_MIN) 152*76559068SAndroid Build Coastguard Worker ReleaseToOsInterval = Config::getDefaultReleaseToOsIntervalMs(); 153*76559068SAndroid Build Coastguard Worker setOption(Option::ReleaseInterval, static_cast<sptr>(ReleaseToOsInterval)); 154*76559068SAndroid Build Coastguard Worker } 155*76559068SAndroid Build Coastguard Worker unmapTestOnly()156*76559068SAndroid Build Coastguard Worker void unmapTestOnly() { 157*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 158*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 159*76559068SAndroid Build Coastguard Worker { 160*76559068SAndroid Build Coastguard Worker ScopedLock ML(Region->MMLock); 161*76559068SAndroid Build Coastguard Worker MemMapT MemMap = Region->MemMapInfo.MemMap; 162*76559068SAndroid Build Coastguard Worker if (MemMap.isAllocated()) 163*76559068SAndroid Build Coastguard Worker MemMap.unmap(); 164*76559068SAndroid Build Coastguard Worker } 165*76559068SAndroid Build Coastguard Worker *Region = {}; 166*76559068SAndroid Build Coastguard Worker } 167*76559068SAndroid Build Coastguard Worker } 168*76559068SAndroid Build Coastguard Worker 169*76559068SAndroid Build Coastguard Worker // When all blocks are freed, it has to be the same size as `AllocatedUser`. verifyAllBlocksAreReleasedTestOnly()170*76559068SAndroid Build Coastguard Worker void verifyAllBlocksAreReleasedTestOnly() { 171*76559068SAndroid Build Coastguard Worker // `BatchGroup` and `TransferBatch` also use the blocks from BatchClass. 172*76559068SAndroid Build Coastguard Worker uptr BatchClassUsedInFreeLists = 0; 173*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 174*76559068SAndroid Build Coastguard Worker // We have to count BatchClassUsedInFreeLists in other regions first. 175*76559068SAndroid Build Coastguard Worker if (I == SizeClassMap::BatchClassId) 176*76559068SAndroid Build Coastguard Worker continue; 177*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 178*76559068SAndroid Build Coastguard Worker ScopedLock ML(Region->MMLock); 179*76559068SAndroid Build Coastguard Worker ScopedLock FL(Region->FLLock); 180*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(I); 181*76559068SAndroid Build Coastguard Worker uptr TotalBlocks = 0; 182*76559068SAndroid Build Coastguard Worker for (BatchGroupT &BG : Region->FreeListInfo.BlockList) { 183*76559068SAndroid Build Coastguard Worker // `BG::Batches` are `TransferBatches`. +1 for `BatchGroup`. 184*76559068SAndroid Build Coastguard Worker BatchClassUsedInFreeLists += BG.Batches.size() + 1; 185*76559068SAndroid Build Coastguard Worker for (const auto &It : BG.Batches) 186*76559068SAndroid Build Coastguard Worker TotalBlocks += It.getCount(); 187*76559068SAndroid Build Coastguard Worker } 188*76559068SAndroid Build Coastguard Worker 189*76559068SAndroid Build Coastguard Worker DCHECK_EQ(TotalBlocks, Region->MemMapInfo.AllocatedUser / BlockSize); 190*76559068SAndroid Build Coastguard Worker DCHECK_EQ(Region->FreeListInfo.PushedBlocks, 191*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PoppedBlocks); 192*76559068SAndroid Build Coastguard Worker } 193*76559068SAndroid Build Coastguard Worker 194*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(SizeClassMap::BatchClassId); 195*76559068SAndroid Build Coastguard Worker ScopedLock ML(Region->MMLock); 196*76559068SAndroid Build Coastguard Worker ScopedLock FL(Region->FLLock); 197*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(SizeClassMap::BatchClassId); 198*76559068SAndroid Build Coastguard Worker uptr TotalBlocks = 0; 199*76559068SAndroid Build Coastguard Worker for (BatchGroupT &BG : Region->FreeListInfo.BlockList) { 200*76559068SAndroid Build Coastguard Worker if (LIKELY(!BG.Batches.empty())) { 201*76559068SAndroid Build Coastguard Worker for (const auto &It : BG.Batches) 202*76559068SAndroid Build Coastguard Worker TotalBlocks += It.getCount(); 203*76559068SAndroid Build Coastguard Worker } else { 204*76559068SAndroid Build Coastguard Worker // `BatchGroup` with empty freelist doesn't have `TransferBatch` record 205*76559068SAndroid Build Coastguard Worker // itself. 206*76559068SAndroid Build Coastguard Worker ++TotalBlocks; 207*76559068SAndroid Build Coastguard Worker } 208*76559068SAndroid Build Coastguard Worker } 209*76559068SAndroid Build Coastguard Worker DCHECK_EQ(TotalBlocks + BatchClassUsedInFreeLists, 210*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser / BlockSize); 211*76559068SAndroid Build Coastguard Worker DCHECK_GE(Region->FreeListInfo.PoppedBlocks, 212*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks); 213*76559068SAndroid Build Coastguard Worker const uptr BlocksInUse = 214*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; 215*76559068SAndroid Build Coastguard Worker DCHECK_EQ(BlocksInUse, BatchClassUsedInFreeLists); 216*76559068SAndroid Build Coastguard Worker } 217*76559068SAndroid Build Coastguard Worker popBlocks(CacheT * C,uptr ClassId,CompactPtrT * ToArray,const u16 MaxBlockCount)218*76559068SAndroid Build Coastguard Worker u16 popBlocks(CacheT *C, uptr ClassId, CompactPtrT *ToArray, 219*76559068SAndroid Build Coastguard Worker const u16 MaxBlockCount) { 220*76559068SAndroid Build Coastguard Worker DCHECK_LT(ClassId, NumClasses); 221*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(ClassId); 222*76559068SAndroid Build Coastguard Worker u16 PopCount = 0; 223*76559068SAndroid Build Coastguard Worker 224*76559068SAndroid Build Coastguard Worker { 225*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 226*76559068SAndroid Build Coastguard Worker PopCount = popBlocksImpl(C, ClassId, Region, ToArray, MaxBlockCount); 227*76559068SAndroid Build Coastguard Worker if (PopCount != 0U) 228*76559068SAndroid Build Coastguard Worker return PopCount; 229*76559068SAndroid Build Coastguard Worker } 230*76559068SAndroid Build Coastguard Worker 231*76559068SAndroid Build Coastguard Worker bool ReportRegionExhausted = false; 232*76559068SAndroid Build Coastguard Worker 233*76559068SAndroid Build Coastguard Worker if (conditionVariableEnabled()) { 234*76559068SAndroid Build Coastguard Worker PopCount = popBlocksWithCV(C, ClassId, Region, ToArray, MaxBlockCount, 235*76559068SAndroid Build Coastguard Worker ReportRegionExhausted); 236*76559068SAndroid Build Coastguard Worker } else { 237*76559068SAndroid Build Coastguard Worker while (true) { 238*76559068SAndroid Build Coastguard Worker // When two threads compete for `Region->MMLock`, we only want one of 239*76559068SAndroid Build Coastguard Worker // them to call populateFreeListAndPopBlocks(). To avoid both of them 240*76559068SAndroid Build Coastguard Worker // doing that, always check the freelist before mapping new pages. 241*76559068SAndroid Build Coastguard Worker ScopedLock ML(Region->MMLock); 242*76559068SAndroid Build Coastguard Worker { 243*76559068SAndroid Build Coastguard Worker ScopedLock FL(Region->FLLock); 244*76559068SAndroid Build Coastguard Worker PopCount = popBlocksImpl(C, ClassId, Region, ToArray, MaxBlockCount); 245*76559068SAndroid Build Coastguard Worker if (PopCount != 0U) 246*76559068SAndroid Build Coastguard Worker return PopCount; 247*76559068SAndroid Build Coastguard Worker } 248*76559068SAndroid Build Coastguard Worker 249*76559068SAndroid Build Coastguard Worker const bool RegionIsExhausted = Region->Exhausted; 250*76559068SAndroid Build Coastguard Worker if (!RegionIsExhausted) { 251*76559068SAndroid Build Coastguard Worker PopCount = populateFreeListAndPopBlocks(C, ClassId, Region, ToArray, 252*76559068SAndroid Build Coastguard Worker MaxBlockCount); 253*76559068SAndroid Build Coastguard Worker } 254*76559068SAndroid Build Coastguard Worker ReportRegionExhausted = !RegionIsExhausted && Region->Exhausted; 255*76559068SAndroid Build Coastguard Worker break; 256*76559068SAndroid Build Coastguard Worker } 257*76559068SAndroid Build Coastguard Worker } 258*76559068SAndroid Build Coastguard Worker 259*76559068SAndroid Build Coastguard Worker if (UNLIKELY(ReportRegionExhausted)) { 260*76559068SAndroid Build Coastguard Worker Printf("Can't populate more pages for size class %zu.\n", 261*76559068SAndroid Build Coastguard Worker getSizeByClassId(ClassId)); 262*76559068SAndroid Build Coastguard Worker 263*76559068SAndroid Build Coastguard Worker // Theoretically, BatchClass shouldn't be used up. Abort immediately when 264*76559068SAndroid Build Coastguard Worker // it happens. 265*76559068SAndroid Build Coastguard Worker if (ClassId == SizeClassMap::BatchClassId) 266*76559068SAndroid Build Coastguard Worker reportOutOfBatchClass(); 267*76559068SAndroid Build Coastguard Worker } 268*76559068SAndroid Build Coastguard Worker 269*76559068SAndroid Build Coastguard Worker return PopCount; 270*76559068SAndroid Build Coastguard Worker } 271*76559068SAndroid Build Coastguard Worker 272*76559068SAndroid Build Coastguard Worker // Push the array of free blocks to the designated batch group. pushBlocks(CacheT * C,uptr ClassId,CompactPtrT * Array,u32 Size)273*76559068SAndroid Build Coastguard Worker void pushBlocks(CacheT *C, uptr ClassId, CompactPtrT *Array, u32 Size) { 274*76559068SAndroid Build Coastguard Worker DCHECK_LT(ClassId, NumClasses); 275*76559068SAndroid Build Coastguard Worker DCHECK_GT(Size, 0); 276*76559068SAndroid Build Coastguard Worker 277*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(ClassId); 278*76559068SAndroid Build Coastguard Worker if (ClassId == SizeClassMap::BatchClassId) { 279*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 280*76559068SAndroid Build Coastguard Worker pushBatchClassBlocks(Region, Array, Size); 281*76559068SAndroid Build Coastguard Worker if (conditionVariableEnabled()) 282*76559068SAndroid Build Coastguard Worker Region->FLLockCV.notifyAll(Region->FLLock); 283*76559068SAndroid Build Coastguard Worker return; 284*76559068SAndroid Build Coastguard Worker } 285*76559068SAndroid Build Coastguard Worker 286*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): Consider not doing grouping if the group size is not 287*76559068SAndroid Build Coastguard Worker // greater than the block size with a certain scale. 288*76559068SAndroid Build Coastguard Worker 289*76559068SAndroid Build Coastguard Worker bool SameGroup = true; 290*76559068SAndroid Build Coastguard Worker if (GroupSizeLog < RegionSizeLog) { 291*76559068SAndroid Build Coastguard Worker // Sort the blocks so that blocks belonging to the same group can be 292*76559068SAndroid Build Coastguard Worker // pushed together. 293*76559068SAndroid Build Coastguard Worker for (u32 I = 1; I < Size; ++I) { 294*76559068SAndroid Build Coastguard Worker if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I])) 295*76559068SAndroid Build Coastguard Worker SameGroup = false; 296*76559068SAndroid Build Coastguard Worker CompactPtrT Cur = Array[I]; 297*76559068SAndroid Build Coastguard Worker u32 J = I; 298*76559068SAndroid Build Coastguard Worker while (J > 0 && compactPtrGroup(Cur) < compactPtrGroup(Array[J - 1])) { 299*76559068SAndroid Build Coastguard Worker Array[J] = Array[J - 1]; 300*76559068SAndroid Build Coastguard Worker --J; 301*76559068SAndroid Build Coastguard Worker } 302*76559068SAndroid Build Coastguard Worker Array[J] = Cur; 303*76559068SAndroid Build Coastguard Worker } 304*76559068SAndroid Build Coastguard Worker } 305*76559068SAndroid Build Coastguard Worker 306*76559068SAndroid Build Coastguard Worker { 307*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 308*76559068SAndroid Build Coastguard Worker pushBlocksImpl(C, ClassId, Region, Array, Size, SameGroup); 309*76559068SAndroid Build Coastguard Worker if (conditionVariableEnabled()) 310*76559068SAndroid Build Coastguard Worker Region->FLLockCV.notifyAll(Region->FLLock); 311*76559068SAndroid Build Coastguard Worker } 312*76559068SAndroid Build Coastguard Worker } 313*76559068SAndroid Build Coastguard Worker disable()314*76559068SAndroid Build Coastguard Worker void disable() NO_THREAD_SAFETY_ANALYSIS { 315*76559068SAndroid Build Coastguard Worker // The BatchClassId must be locked last since other classes can use it. 316*76559068SAndroid Build Coastguard Worker for (sptr I = static_cast<sptr>(NumClasses) - 1; I >= 0; I--) { 317*76559068SAndroid Build Coastguard Worker if (static_cast<uptr>(I) == SizeClassMap::BatchClassId) 318*76559068SAndroid Build Coastguard Worker continue; 319*76559068SAndroid Build Coastguard Worker getRegionInfo(static_cast<uptr>(I))->MMLock.lock(); 320*76559068SAndroid Build Coastguard Worker getRegionInfo(static_cast<uptr>(I))->FLLock.lock(); 321*76559068SAndroid Build Coastguard Worker } 322*76559068SAndroid Build Coastguard Worker getRegionInfo(SizeClassMap::BatchClassId)->MMLock.lock(); 323*76559068SAndroid Build Coastguard Worker getRegionInfo(SizeClassMap::BatchClassId)->FLLock.lock(); 324*76559068SAndroid Build Coastguard Worker } 325*76559068SAndroid Build Coastguard Worker enable()326*76559068SAndroid Build Coastguard Worker void enable() NO_THREAD_SAFETY_ANALYSIS { 327*76559068SAndroid Build Coastguard Worker getRegionInfo(SizeClassMap::BatchClassId)->FLLock.unlock(); 328*76559068SAndroid Build Coastguard Worker getRegionInfo(SizeClassMap::BatchClassId)->MMLock.unlock(); 329*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 330*76559068SAndroid Build Coastguard Worker if (I == SizeClassMap::BatchClassId) 331*76559068SAndroid Build Coastguard Worker continue; 332*76559068SAndroid Build Coastguard Worker getRegionInfo(I)->FLLock.unlock(); 333*76559068SAndroid Build Coastguard Worker getRegionInfo(I)->MMLock.unlock(); 334*76559068SAndroid Build Coastguard Worker } 335*76559068SAndroid Build Coastguard Worker } 336*76559068SAndroid Build Coastguard Worker iterateOverBlocks(F Callback)337*76559068SAndroid Build Coastguard Worker template <typename F> void iterateOverBlocks(F Callback) { 338*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 339*76559068SAndroid Build Coastguard Worker if (I == SizeClassMap::BatchClassId) 340*76559068SAndroid Build Coastguard Worker continue; 341*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 342*76559068SAndroid Build Coastguard Worker // TODO: The call of `iterateOverBlocks` requires disabling 343*76559068SAndroid Build Coastguard Worker // SizeClassAllocator64. We may consider locking each region on demand 344*76559068SAndroid Build Coastguard Worker // only. 345*76559068SAndroid Build Coastguard Worker Region->FLLock.assertHeld(); 346*76559068SAndroid Build Coastguard Worker Region->MMLock.assertHeld(); 347*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(I); 348*76559068SAndroid Build Coastguard Worker const uptr From = Region->RegionBeg; 349*76559068SAndroid Build Coastguard Worker const uptr To = From + Region->MemMapInfo.AllocatedUser; 350*76559068SAndroid Build Coastguard Worker for (uptr Block = From; Block < To; Block += BlockSize) 351*76559068SAndroid Build Coastguard Worker Callback(Block); 352*76559068SAndroid Build Coastguard Worker } 353*76559068SAndroid Build Coastguard Worker } 354*76559068SAndroid Build Coastguard Worker getStats(ScopedString * Str)355*76559068SAndroid Build Coastguard Worker void getStats(ScopedString *Str) { 356*76559068SAndroid Build Coastguard Worker // TODO(kostyak): get the RSS per region. 357*76559068SAndroid Build Coastguard Worker uptr TotalMapped = 0; 358*76559068SAndroid Build Coastguard Worker uptr PoppedBlocks = 0; 359*76559068SAndroid Build Coastguard Worker uptr PushedBlocks = 0; 360*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 361*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 362*76559068SAndroid Build Coastguard Worker { 363*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->MMLock); 364*76559068SAndroid Build Coastguard Worker TotalMapped += Region->MemMapInfo.MappedUser; 365*76559068SAndroid Build Coastguard Worker } 366*76559068SAndroid Build Coastguard Worker { 367*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 368*76559068SAndroid Build Coastguard Worker PoppedBlocks += Region->FreeListInfo.PoppedBlocks; 369*76559068SAndroid Build Coastguard Worker PushedBlocks += Region->FreeListInfo.PushedBlocks; 370*76559068SAndroid Build Coastguard Worker } 371*76559068SAndroid Build Coastguard Worker } 372*76559068SAndroid Build Coastguard Worker const s32 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs); 373*76559068SAndroid Build Coastguard Worker Str->append("Stats: SizeClassAllocator64: %zuM mapped (%uM rss) in %zu " 374*76559068SAndroid Build Coastguard Worker "allocations; remains %zu; ReleaseToOsIntervalMs = %d\n", 375*76559068SAndroid Build Coastguard Worker TotalMapped >> 20, 0U, PoppedBlocks, 376*76559068SAndroid Build Coastguard Worker PoppedBlocks - PushedBlocks, IntervalMs >= 0 ? IntervalMs : -1); 377*76559068SAndroid Build Coastguard Worker 378*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 379*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 380*76559068SAndroid Build Coastguard Worker ScopedLock L1(Region->MMLock); 381*76559068SAndroid Build Coastguard Worker ScopedLock L2(Region->FLLock); 382*76559068SAndroid Build Coastguard Worker getStats(Str, I, Region); 383*76559068SAndroid Build Coastguard Worker } 384*76559068SAndroid Build Coastguard Worker } 385*76559068SAndroid Build Coastguard Worker getFragmentationInfo(ScopedString * Str)386*76559068SAndroid Build Coastguard Worker void getFragmentationInfo(ScopedString *Str) { 387*76559068SAndroid Build Coastguard Worker Str->append( 388*76559068SAndroid Build Coastguard Worker "Fragmentation Stats: SizeClassAllocator64: page size = %zu bytes\n", 389*76559068SAndroid Build Coastguard Worker getPageSizeCached()); 390*76559068SAndroid Build Coastguard Worker 391*76559068SAndroid Build Coastguard Worker for (uptr I = 1; I < NumClasses; I++) { 392*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 393*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->MMLock); 394*76559068SAndroid Build Coastguard Worker getRegionFragmentationInfo(Region, I, Str); 395*76559068SAndroid Build Coastguard Worker } 396*76559068SAndroid Build Coastguard Worker } 397*76559068SAndroid Build Coastguard Worker getMemoryGroupFragmentationInfo(ScopedString * Str)398*76559068SAndroid Build Coastguard Worker void getMemoryGroupFragmentationInfo(ScopedString *Str) { 399*76559068SAndroid Build Coastguard Worker Str->append( 400*76559068SAndroid Build Coastguard Worker "Fragmentation Stats: SizeClassAllocator64: page size = %zu bytes\n", 401*76559068SAndroid Build Coastguard Worker getPageSizeCached()); 402*76559068SAndroid Build Coastguard Worker 403*76559068SAndroid Build Coastguard Worker for (uptr I = 1; I < NumClasses; I++) { 404*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 405*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->MMLock); 406*76559068SAndroid Build Coastguard Worker getMemoryGroupFragmentationInfoInRegion(Region, I, Str); 407*76559068SAndroid Build Coastguard Worker } 408*76559068SAndroid Build Coastguard Worker } 409*76559068SAndroid Build Coastguard Worker setOption(Option O,sptr Value)410*76559068SAndroid Build Coastguard Worker bool setOption(Option O, sptr Value) { 411*76559068SAndroid Build Coastguard Worker if (O == Option::ReleaseInterval) { 412*76559068SAndroid Build Coastguard Worker const s32 Interval = Max( 413*76559068SAndroid Build Coastguard Worker Min(static_cast<s32>(Value), Config::getMaxReleaseToOsIntervalMs()), 414*76559068SAndroid Build Coastguard Worker Config::getMinReleaseToOsIntervalMs()); 415*76559068SAndroid Build Coastguard Worker atomic_store_relaxed(&ReleaseToOsIntervalMs, Interval); 416*76559068SAndroid Build Coastguard Worker return true; 417*76559068SAndroid Build Coastguard Worker } 418*76559068SAndroid Build Coastguard Worker // Not supported by the Primary, but not an error either. 419*76559068SAndroid Build Coastguard Worker return true; 420*76559068SAndroid Build Coastguard Worker } 421*76559068SAndroid Build Coastguard Worker tryReleaseToOS(uptr ClassId,ReleaseToOS ReleaseType)422*76559068SAndroid Build Coastguard Worker uptr tryReleaseToOS(uptr ClassId, ReleaseToOS ReleaseType) { 423*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(ClassId); 424*76559068SAndroid Build Coastguard Worker // Note that the tryLock() may fail spuriously, given that it should rarely 425*76559068SAndroid Build Coastguard Worker // happen and page releasing is fine to skip, we don't take certain 426*76559068SAndroid Build Coastguard Worker // approaches to ensure one page release is done. 427*76559068SAndroid Build Coastguard Worker if (Region->MMLock.tryLock()) { 428*76559068SAndroid Build Coastguard Worker uptr BytesReleased = releaseToOSMaybe(Region, ClassId, ReleaseType); 429*76559068SAndroid Build Coastguard Worker Region->MMLock.unlock(); 430*76559068SAndroid Build Coastguard Worker return BytesReleased; 431*76559068SAndroid Build Coastguard Worker } 432*76559068SAndroid Build Coastguard Worker return 0; 433*76559068SAndroid Build Coastguard Worker } 434*76559068SAndroid Build Coastguard Worker releaseToOS(ReleaseToOS ReleaseType)435*76559068SAndroid Build Coastguard Worker uptr releaseToOS(ReleaseToOS ReleaseType) { 436*76559068SAndroid Build Coastguard Worker uptr TotalReleasedBytes = 0; 437*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < NumClasses; I++) { 438*76559068SAndroid Build Coastguard Worker if (I == SizeClassMap::BatchClassId) 439*76559068SAndroid Build Coastguard Worker continue; 440*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(I); 441*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->MMLock); 442*76559068SAndroid Build Coastguard Worker TotalReleasedBytes += releaseToOSMaybe(Region, I, ReleaseType); 443*76559068SAndroid Build Coastguard Worker } 444*76559068SAndroid Build Coastguard Worker return TotalReleasedBytes; 445*76559068SAndroid Build Coastguard Worker } 446*76559068SAndroid Build Coastguard Worker getRegionInfoArrayAddress()447*76559068SAndroid Build Coastguard Worker const char *getRegionInfoArrayAddress() const { 448*76559068SAndroid Build Coastguard Worker return reinterpret_cast<const char *>(RegionInfoArray); 449*76559068SAndroid Build Coastguard Worker } 450*76559068SAndroid Build Coastguard Worker getRegionInfoArraySize()451*76559068SAndroid Build Coastguard Worker static uptr getRegionInfoArraySize() { return sizeof(RegionInfoArray); } 452*76559068SAndroid Build Coastguard Worker getCompactPtrBaseByClassId(uptr ClassId)453*76559068SAndroid Build Coastguard Worker uptr getCompactPtrBaseByClassId(uptr ClassId) { 454*76559068SAndroid Build Coastguard Worker return getRegionInfo(ClassId)->RegionBeg; 455*76559068SAndroid Build Coastguard Worker } 456*76559068SAndroid Build Coastguard Worker compactPtr(uptr ClassId,uptr Ptr)457*76559068SAndroid Build Coastguard Worker CompactPtrT compactPtr(uptr ClassId, uptr Ptr) { 458*76559068SAndroid Build Coastguard Worker DCHECK_LE(ClassId, SizeClassMap::LargestClassId); 459*76559068SAndroid Build Coastguard Worker return compactPtrInternal(getCompactPtrBaseByClassId(ClassId), Ptr); 460*76559068SAndroid Build Coastguard Worker } 461*76559068SAndroid Build Coastguard Worker decompactPtr(uptr ClassId,CompactPtrT CompactPtr)462*76559068SAndroid Build Coastguard Worker void *decompactPtr(uptr ClassId, CompactPtrT CompactPtr) { 463*76559068SAndroid Build Coastguard Worker DCHECK_LE(ClassId, SizeClassMap::LargestClassId); 464*76559068SAndroid Build Coastguard Worker return reinterpret_cast<void *>( 465*76559068SAndroid Build Coastguard Worker decompactPtrInternal(getCompactPtrBaseByClassId(ClassId), CompactPtr)); 466*76559068SAndroid Build Coastguard Worker } 467*76559068SAndroid Build Coastguard Worker findNearestBlock(const char * RegionInfoData,uptr Ptr)468*76559068SAndroid Build Coastguard Worker static BlockInfo findNearestBlock(const char *RegionInfoData, 469*76559068SAndroid Build Coastguard Worker uptr Ptr) NO_THREAD_SAFETY_ANALYSIS { 470*76559068SAndroid Build Coastguard Worker const RegionInfo *RegionInfoArray = 471*76559068SAndroid Build Coastguard Worker reinterpret_cast<const RegionInfo *>(RegionInfoData); 472*76559068SAndroid Build Coastguard Worker 473*76559068SAndroid Build Coastguard Worker uptr ClassId; 474*76559068SAndroid Build Coastguard Worker uptr MinDistance = -1UL; 475*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I != NumClasses; ++I) { 476*76559068SAndroid Build Coastguard Worker if (I == SizeClassMap::BatchClassId) 477*76559068SAndroid Build Coastguard Worker continue; 478*76559068SAndroid Build Coastguard Worker uptr Begin = RegionInfoArray[I].RegionBeg; 479*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): In fact, We need to lock the RegionInfo::MMLock. 480*76559068SAndroid Build Coastguard Worker // However, the RegionInfoData is passed with const qualifier and lock the 481*76559068SAndroid Build Coastguard Worker // mutex requires modifying RegionInfoData, which means we need to remove 482*76559068SAndroid Build Coastguard Worker // the const qualifier. This may lead to another undefined behavior (The 483*76559068SAndroid Build Coastguard Worker // first one is accessing `AllocatedUser` without locking. It's better to 484*76559068SAndroid Build Coastguard Worker // pass `RegionInfoData` as `void *` then we can lock the mutex properly. 485*76559068SAndroid Build Coastguard Worker uptr End = Begin + RegionInfoArray[I].MemMapInfo.AllocatedUser; 486*76559068SAndroid Build Coastguard Worker if (Begin > End || End - Begin < SizeClassMap::getSizeByClassId(I)) 487*76559068SAndroid Build Coastguard Worker continue; 488*76559068SAndroid Build Coastguard Worker uptr RegionDistance; 489*76559068SAndroid Build Coastguard Worker if (Begin <= Ptr) { 490*76559068SAndroid Build Coastguard Worker if (Ptr < End) 491*76559068SAndroid Build Coastguard Worker RegionDistance = 0; 492*76559068SAndroid Build Coastguard Worker else 493*76559068SAndroid Build Coastguard Worker RegionDistance = Ptr - End; 494*76559068SAndroid Build Coastguard Worker } else { 495*76559068SAndroid Build Coastguard Worker RegionDistance = Begin - Ptr; 496*76559068SAndroid Build Coastguard Worker } 497*76559068SAndroid Build Coastguard Worker 498*76559068SAndroid Build Coastguard Worker if (RegionDistance < MinDistance) { 499*76559068SAndroid Build Coastguard Worker MinDistance = RegionDistance; 500*76559068SAndroid Build Coastguard Worker ClassId = I; 501*76559068SAndroid Build Coastguard Worker } 502*76559068SAndroid Build Coastguard Worker } 503*76559068SAndroid Build Coastguard Worker 504*76559068SAndroid Build Coastguard Worker BlockInfo B = {}; 505*76559068SAndroid Build Coastguard Worker if (MinDistance <= 8192) { 506*76559068SAndroid Build Coastguard Worker B.RegionBegin = RegionInfoArray[ClassId].RegionBeg; 507*76559068SAndroid Build Coastguard Worker B.RegionEnd = 508*76559068SAndroid Build Coastguard Worker B.RegionBegin + RegionInfoArray[ClassId].MemMapInfo.AllocatedUser; 509*76559068SAndroid Build Coastguard Worker B.BlockSize = SizeClassMap::getSizeByClassId(ClassId); 510*76559068SAndroid Build Coastguard Worker B.BlockBegin = 511*76559068SAndroid Build Coastguard Worker B.RegionBegin + uptr(sptr(Ptr - B.RegionBegin) / sptr(B.BlockSize) * 512*76559068SAndroid Build Coastguard Worker sptr(B.BlockSize)); 513*76559068SAndroid Build Coastguard Worker while (B.BlockBegin < B.RegionBegin) 514*76559068SAndroid Build Coastguard Worker B.BlockBegin += B.BlockSize; 515*76559068SAndroid Build Coastguard Worker while (B.RegionEnd < B.BlockBegin + B.BlockSize) 516*76559068SAndroid Build Coastguard Worker B.BlockBegin -= B.BlockSize; 517*76559068SAndroid Build Coastguard Worker } 518*76559068SAndroid Build Coastguard Worker return B; 519*76559068SAndroid Build Coastguard Worker } 520*76559068SAndroid Build Coastguard Worker 521*76559068SAndroid Build Coastguard Worker AtomicOptions Options; 522*76559068SAndroid Build Coastguard Worker 523*76559068SAndroid Build Coastguard Worker private: 524*76559068SAndroid Build Coastguard Worker static const uptr RegionSize = 1UL << RegionSizeLog; 525*76559068SAndroid Build Coastguard Worker static const uptr NumClasses = SizeClassMap::NumClasses; 526*76559068SAndroid Build Coastguard Worker 527*76559068SAndroid Build Coastguard Worker static const uptr MapSizeIncrement = Config::getMapSizeIncrement(); 528*76559068SAndroid Build Coastguard Worker // Fill at most this number of batches from the newly map'd memory. 529*76559068SAndroid Build Coastguard Worker static const u32 MaxNumBatches = SCUDO_ANDROID ? 4U : 8U; 530*76559068SAndroid Build Coastguard Worker 531*76559068SAndroid Build Coastguard Worker struct ReleaseToOsInfo { 532*76559068SAndroid Build Coastguard Worker uptr BytesInFreeListAtLastCheckpoint; 533*76559068SAndroid Build Coastguard Worker uptr RangesReleased; 534*76559068SAndroid Build Coastguard Worker uptr LastReleasedBytes; 535*76559068SAndroid Build Coastguard Worker // The minimum size of pushed blocks to trigger page release. 536*76559068SAndroid Build Coastguard Worker uptr TryReleaseThreshold; 537*76559068SAndroid Build Coastguard Worker // The number of bytes not triggering `releaseToOSMaybe()` because of 538*76559068SAndroid Build Coastguard Worker // the length of release interval. 539*76559068SAndroid Build Coastguard Worker uptr PendingPushedBytesDelta; 540*76559068SAndroid Build Coastguard Worker u64 LastReleaseAtNs; 541*76559068SAndroid Build Coastguard Worker }; 542*76559068SAndroid Build Coastguard Worker 543*76559068SAndroid Build Coastguard Worker struct BlocksInfo { 544*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> BlockList = {}; 545*76559068SAndroid Build Coastguard Worker uptr PoppedBlocks = 0; 546*76559068SAndroid Build Coastguard Worker uptr PushedBlocks = 0; 547*76559068SAndroid Build Coastguard Worker }; 548*76559068SAndroid Build Coastguard Worker 549*76559068SAndroid Build Coastguard Worker struct PagesInfo { 550*76559068SAndroid Build Coastguard Worker MemMapT MemMap = {}; 551*76559068SAndroid Build Coastguard Worker // Bytes mapped for user memory. 552*76559068SAndroid Build Coastguard Worker uptr MappedUser = 0; 553*76559068SAndroid Build Coastguard Worker // Bytes allocated for user memory. 554*76559068SAndroid Build Coastguard Worker uptr AllocatedUser = 0; 555*76559068SAndroid Build Coastguard Worker }; 556*76559068SAndroid Build Coastguard Worker 557*76559068SAndroid Build Coastguard Worker struct UnpaddedRegionInfo { 558*76559068SAndroid Build Coastguard Worker // Mutex for operations on freelist 559*76559068SAndroid Build Coastguard Worker HybridMutex FLLock; 560*76559068SAndroid Build Coastguard Worker ConditionVariableT FLLockCV GUARDED_BY(FLLock); 561*76559068SAndroid Build Coastguard Worker // Mutex for memmap operations 562*76559068SAndroid Build Coastguard Worker HybridMutex MMLock ACQUIRED_BEFORE(FLLock); 563*76559068SAndroid Build Coastguard Worker // `RegionBeg` is initialized before thread creation and won't be changed. 564*76559068SAndroid Build Coastguard Worker uptr RegionBeg = 0; 565*76559068SAndroid Build Coastguard Worker u32 RandState GUARDED_BY(MMLock) = 0; 566*76559068SAndroid Build Coastguard Worker BlocksInfo FreeListInfo GUARDED_BY(FLLock); 567*76559068SAndroid Build Coastguard Worker PagesInfo MemMapInfo GUARDED_BY(MMLock); 568*76559068SAndroid Build Coastguard Worker ReleaseToOsInfo ReleaseInfo GUARDED_BY(MMLock) = {}; 569*76559068SAndroid Build Coastguard Worker bool Exhausted GUARDED_BY(MMLock) = false; 570*76559068SAndroid Build Coastguard Worker bool isPopulatingFreeList GUARDED_BY(FLLock) = false; 571*76559068SAndroid Build Coastguard Worker }; 572*76559068SAndroid Build Coastguard Worker struct RegionInfo : UnpaddedRegionInfo { 573*76559068SAndroid Build Coastguard Worker char Padding[SCUDO_CACHE_LINE_SIZE - 574*76559068SAndroid Build Coastguard Worker (sizeof(UnpaddedRegionInfo) % SCUDO_CACHE_LINE_SIZE)] = {}; 575*76559068SAndroid Build Coastguard Worker }; 576*76559068SAndroid Build Coastguard Worker static_assert(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0, ""); 577*76559068SAndroid Build Coastguard Worker getRegionInfo(uptr ClassId)578*76559068SAndroid Build Coastguard Worker RegionInfo *getRegionInfo(uptr ClassId) { 579*76559068SAndroid Build Coastguard Worker DCHECK_LT(ClassId, NumClasses); 580*76559068SAndroid Build Coastguard Worker return &RegionInfoArray[ClassId]; 581*76559068SAndroid Build Coastguard Worker } 582*76559068SAndroid Build Coastguard Worker getRegionBaseByClassId(uptr ClassId)583*76559068SAndroid Build Coastguard Worker uptr getRegionBaseByClassId(uptr ClassId) { 584*76559068SAndroid Build Coastguard Worker RegionInfo *Region = getRegionInfo(ClassId); 585*76559068SAndroid Build Coastguard Worker Region->MMLock.assertHeld(); 586*76559068SAndroid Build Coastguard Worker 587*76559068SAndroid Build Coastguard Worker if (!Config::getEnableContiguousRegions() && 588*76559068SAndroid Build Coastguard Worker !Region->MemMapInfo.MemMap.isAllocated()) { 589*76559068SAndroid Build Coastguard Worker return 0U; 590*76559068SAndroid Build Coastguard Worker } 591*76559068SAndroid Build Coastguard Worker return Region->MemMapInfo.MemMap.getBase(); 592*76559068SAndroid Build Coastguard Worker } 593*76559068SAndroid Build Coastguard Worker compactPtrInternal(uptr Base,uptr Ptr)594*76559068SAndroid Build Coastguard Worker static CompactPtrT compactPtrInternal(uptr Base, uptr Ptr) { 595*76559068SAndroid Build Coastguard Worker return static_cast<CompactPtrT>((Ptr - Base) >> CompactPtrScale); 596*76559068SAndroid Build Coastguard Worker } 597*76559068SAndroid Build Coastguard Worker decompactPtrInternal(uptr Base,CompactPtrT CompactPtr)598*76559068SAndroid Build Coastguard Worker static uptr decompactPtrInternal(uptr Base, CompactPtrT CompactPtr) { 599*76559068SAndroid Build Coastguard Worker return Base + (static_cast<uptr>(CompactPtr) << CompactPtrScale); 600*76559068SAndroid Build Coastguard Worker } 601*76559068SAndroid Build Coastguard Worker compactPtrGroup(CompactPtrT CompactPtr)602*76559068SAndroid Build Coastguard Worker static uptr compactPtrGroup(CompactPtrT CompactPtr) { 603*76559068SAndroid Build Coastguard Worker const uptr Mask = (static_cast<uptr>(1) << GroupScale) - 1; 604*76559068SAndroid Build Coastguard Worker return static_cast<uptr>(CompactPtr) & ~Mask; 605*76559068SAndroid Build Coastguard Worker } decompactGroupBase(uptr Base,uptr CompactPtrGroupBase)606*76559068SAndroid Build Coastguard Worker static uptr decompactGroupBase(uptr Base, uptr CompactPtrGroupBase) { 607*76559068SAndroid Build Coastguard Worker DCHECK_EQ(CompactPtrGroupBase % (static_cast<uptr>(1) << (GroupScale)), 0U); 608*76559068SAndroid Build Coastguard Worker return Base + (CompactPtrGroupBase << CompactPtrScale); 609*76559068SAndroid Build Coastguard Worker } 610*76559068SAndroid Build Coastguard Worker isSmallBlock(uptr BlockSize)611*76559068SAndroid Build Coastguard Worker ALWAYS_INLINE static bool isSmallBlock(uptr BlockSize) { 612*76559068SAndroid Build Coastguard Worker const uptr PageSize = getPageSizeCached(); 613*76559068SAndroid Build Coastguard Worker return BlockSize < PageSize / 16U; 614*76559068SAndroid Build Coastguard Worker } 615*76559068SAndroid Build Coastguard Worker getMinReleaseAttemptSize(uptr BlockSize)616*76559068SAndroid Build Coastguard Worker ALWAYS_INLINE uptr getMinReleaseAttemptSize(uptr BlockSize) { 617*76559068SAndroid Build Coastguard Worker return roundUp(BlockSize, getPageSizeCached()); 618*76559068SAndroid Build Coastguard Worker } 619*76559068SAndroid Build Coastguard Worker initRegion(RegionInfo * Region,uptr ClassId,MemMapT MemMap,bool EnableRandomOffset)620*76559068SAndroid Build Coastguard Worker ALWAYS_INLINE void initRegion(RegionInfo *Region, uptr ClassId, 621*76559068SAndroid Build Coastguard Worker MemMapT MemMap, bool EnableRandomOffset) 622*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock) { 623*76559068SAndroid Build Coastguard Worker DCHECK(!Region->MemMapInfo.MemMap.isAllocated()); 624*76559068SAndroid Build Coastguard Worker DCHECK(MemMap.isAllocated()); 625*76559068SAndroid Build Coastguard Worker 626*76559068SAndroid Build Coastguard Worker const uptr PageSize = getPageSizeCached(); 627*76559068SAndroid Build Coastguard Worker 628*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.MemMap = MemMap; 629*76559068SAndroid Build Coastguard Worker 630*76559068SAndroid Build Coastguard Worker Region->RegionBeg = MemMap.getBase(); 631*76559068SAndroid Build Coastguard Worker if (EnableRandomOffset) { 632*76559068SAndroid Build Coastguard Worker Region->RegionBeg += 633*76559068SAndroid Build Coastguard Worker (getRandomModN(&Region->RandState, 16) + 1) * PageSize; 634*76559068SAndroid Build Coastguard Worker } 635*76559068SAndroid Build Coastguard Worker 636*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(ClassId); 637*76559068SAndroid Build Coastguard Worker // Releasing small blocks is expensive, set a higher threshold to avoid 638*76559068SAndroid Build Coastguard Worker // frequent page releases. 639*76559068SAndroid Build Coastguard Worker if (isSmallBlock(BlockSize)) { 640*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold = 641*76559068SAndroid Build Coastguard Worker PageSize * SmallerBlockReleasePageDelta; 642*76559068SAndroid Build Coastguard Worker } else { 643*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold = 644*76559068SAndroid Build Coastguard Worker getMinReleaseAttemptSize(BlockSize); 645*76559068SAndroid Build Coastguard Worker } 646*76559068SAndroid Build Coastguard Worker } 647*76559068SAndroid Build Coastguard Worker pushBatchClassBlocks(RegionInfo * Region,CompactPtrT * Array,u32 Size)648*76559068SAndroid Build Coastguard Worker void pushBatchClassBlocks(RegionInfo *Region, CompactPtrT *Array, u32 Size) 649*76559068SAndroid Build Coastguard Worker REQUIRES(Region->FLLock) { 650*76559068SAndroid Build Coastguard Worker DCHECK_EQ(Region, getRegionInfo(SizeClassMap::BatchClassId)); 651*76559068SAndroid Build Coastguard Worker 652*76559068SAndroid Build Coastguard Worker // Free blocks are recorded by TransferBatch in freelist for all 653*76559068SAndroid Build Coastguard Worker // size-classes. In addition, TransferBatch is allocated from BatchClassId. 654*76559068SAndroid Build Coastguard Worker // In order not to use additional block to record the free blocks in 655*76559068SAndroid Build Coastguard Worker // BatchClassId, they are self-contained. I.e., A TransferBatch records the 656*76559068SAndroid Build Coastguard Worker // block address of itself. See the figure below: 657*76559068SAndroid Build Coastguard Worker // 658*76559068SAndroid Build Coastguard Worker // TransferBatch at 0xABCD 659*76559068SAndroid Build Coastguard Worker // +----------------------------+ 660*76559068SAndroid Build Coastguard Worker // | Free blocks' addr | 661*76559068SAndroid Build Coastguard Worker // | +------+------+------+ | 662*76559068SAndroid Build Coastguard Worker // | |0xABCD|... |... | | 663*76559068SAndroid Build Coastguard Worker // | +------+------+------+ | 664*76559068SAndroid Build Coastguard Worker // +----------------------------+ 665*76559068SAndroid Build Coastguard Worker // 666*76559068SAndroid Build Coastguard Worker // When we allocate all the free blocks in the TransferBatch, the block used 667*76559068SAndroid Build Coastguard Worker // by TransferBatch is also free for use. We don't need to recycle the 668*76559068SAndroid Build Coastguard Worker // TransferBatch. Note that the correctness is maintained by the invariant, 669*76559068SAndroid Build Coastguard Worker // 670*76559068SAndroid Build Coastguard Worker // Each popBlocks() request returns the entire TransferBatch. Returning 671*76559068SAndroid Build Coastguard Worker // part of the blocks in a TransferBatch is invalid. 672*76559068SAndroid Build Coastguard Worker // 673*76559068SAndroid Build Coastguard Worker // This ensures that TransferBatch won't leak the address itself while it's 674*76559068SAndroid Build Coastguard Worker // still holding other valid data. 675*76559068SAndroid Build Coastguard Worker // 676*76559068SAndroid Build Coastguard Worker // Besides, BatchGroup is also allocated from BatchClassId and has its 677*76559068SAndroid Build Coastguard Worker // address recorded in the TransferBatch too. To maintain the correctness, 678*76559068SAndroid Build Coastguard Worker // 679*76559068SAndroid Build Coastguard Worker // The address of BatchGroup is always recorded in the last TransferBatch 680*76559068SAndroid Build Coastguard Worker // in the freelist (also imply that the freelist should only be 681*76559068SAndroid Build Coastguard Worker // updated with push_front). Once the last TransferBatch is popped, 682*76559068SAndroid Build Coastguard Worker // the block used by BatchGroup is also free for use. 683*76559068SAndroid Build Coastguard Worker // 684*76559068SAndroid Build Coastguard Worker // With this approach, the blocks used by BatchGroup and TransferBatch are 685*76559068SAndroid Build Coastguard Worker // reusable and don't need additional space for them. 686*76559068SAndroid Build Coastguard Worker 687*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks += Size; 688*76559068SAndroid Build Coastguard Worker BatchGroupT *BG = Region->FreeListInfo.BlockList.front(); 689*76559068SAndroid Build Coastguard Worker 690*76559068SAndroid Build Coastguard Worker if (BG == nullptr) { 691*76559068SAndroid Build Coastguard Worker // Construct `BatchGroup` on the last element. 692*76559068SAndroid Build Coastguard Worker BG = reinterpret_cast<BatchGroupT *>( 693*76559068SAndroid Build Coastguard Worker decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); 694*76559068SAndroid Build Coastguard Worker --Size; 695*76559068SAndroid Build Coastguard Worker BG->Batches.clear(); 696*76559068SAndroid Build Coastguard Worker // BatchClass hasn't enabled memory group. Use `0` to indicate there's no 697*76559068SAndroid Build Coastguard Worker // memory group here. 698*76559068SAndroid Build Coastguard Worker BG->CompactPtrGroupBase = 0; 699*76559068SAndroid Build Coastguard Worker BG->BytesInBGAtLastCheckpoint = 0; 700*76559068SAndroid Build Coastguard Worker BG->MaxCachedPerBatch = 701*76559068SAndroid Build Coastguard Worker CacheT::getMaxCached(getSizeByClassId(SizeClassMap::BatchClassId)); 702*76559068SAndroid Build Coastguard Worker 703*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.push_front(BG); 704*76559068SAndroid Build Coastguard Worker } 705*76559068SAndroid Build Coastguard Worker 706*76559068SAndroid Build Coastguard Worker if (UNLIKELY(Size == 0)) 707*76559068SAndroid Build Coastguard Worker return; 708*76559068SAndroid Build Coastguard Worker 709*76559068SAndroid Build Coastguard Worker // This happens under 2 cases. 710*76559068SAndroid Build Coastguard Worker // 1. just allocated a new `BatchGroup`. 711*76559068SAndroid Build Coastguard Worker // 2. Only 1 block is pushed when the freelist is empty. 712*76559068SAndroid Build Coastguard Worker if (BG->Batches.empty()) { 713*76559068SAndroid Build Coastguard Worker // Construct the `TransferBatch` on the last element. 714*76559068SAndroid Build Coastguard Worker TransferBatchT *TB = reinterpret_cast<TransferBatchT *>( 715*76559068SAndroid Build Coastguard Worker decompactPtr(SizeClassMap::BatchClassId, Array[Size - 1])); 716*76559068SAndroid Build Coastguard Worker TB->clear(); 717*76559068SAndroid Build Coastguard Worker // As mentioned above, addresses of `TransferBatch` and `BatchGroup` are 718*76559068SAndroid Build Coastguard Worker // recorded in the TransferBatch. 719*76559068SAndroid Build Coastguard Worker TB->add(Array[Size - 1]); 720*76559068SAndroid Build Coastguard Worker TB->add( 721*76559068SAndroid Build Coastguard Worker compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(BG))); 722*76559068SAndroid Build Coastguard Worker --Size; 723*76559068SAndroid Build Coastguard Worker BG->Batches.push_front(TB); 724*76559068SAndroid Build Coastguard Worker } 725*76559068SAndroid Build Coastguard Worker 726*76559068SAndroid Build Coastguard Worker TransferBatchT *CurBatch = BG->Batches.front(); 727*76559068SAndroid Build Coastguard Worker DCHECK_NE(CurBatch, nullptr); 728*76559068SAndroid Build Coastguard Worker 729*76559068SAndroid Build Coastguard Worker for (u32 I = 0; I < Size;) { 730*76559068SAndroid Build Coastguard Worker u16 UnusedSlots = 731*76559068SAndroid Build Coastguard Worker static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); 732*76559068SAndroid Build Coastguard Worker if (UnusedSlots == 0) { 733*76559068SAndroid Build Coastguard Worker CurBatch = reinterpret_cast<TransferBatchT *>( 734*76559068SAndroid Build Coastguard Worker decompactPtr(SizeClassMap::BatchClassId, Array[I])); 735*76559068SAndroid Build Coastguard Worker CurBatch->clear(); 736*76559068SAndroid Build Coastguard Worker // Self-contained 737*76559068SAndroid Build Coastguard Worker CurBatch->add(Array[I]); 738*76559068SAndroid Build Coastguard Worker ++I; 739*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): Avoid the use of push_back() in `Batches` of 740*76559068SAndroid Build Coastguard Worker // BatchClassId. 741*76559068SAndroid Build Coastguard Worker BG->Batches.push_front(CurBatch); 742*76559068SAndroid Build Coastguard Worker UnusedSlots = static_cast<u16>(BG->MaxCachedPerBatch - 1); 743*76559068SAndroid Build Coastguard Worker } 744*76559068SAndroid Build Coastguard Worker // `UnusedSlots` is u16 so the result will be also fit in u16. 745*76559068SAndroid Build Coastguard Worker const u16 AppendSize = static_cast<u16>(Min<u32>(UnusedSlots, Size - I)); 746*76559068SAndroid Build Coastguard Worker CurBatch->appendFromArray(&Array[I], AppendSize); 747*76559068SAndroid Build Coastguard Worker I += AppendSize; 748*76559068SAndroid Build Coastguard Worker } 749*76559068SAndroid Build Coastguard Worker } 750*76559068SAndroid Build Coastguard Worker 751*76559068SAndroid Build Coastguard Worker // Push the blocks to their batch group. The layout will be like, 752*76559068SAndroid Build Coastguard Worker // 753*76559068SAndroid Build Coastguard Worker // FreeListInfo.BlockList - > BG -> BG -> BG 754*76559068SAndroid Build Coastguard Worker // | | | 755*76559068SAndroid Build Coastguard Worker // v v v 756*76559068SAndroid Build Coastguard Worker // TB TB TB 757*76559068SAndroid Build Coastguard Worker // | 758*76559068SAndroid Build Coastguard Worker // v 759*76559068SAndroid Build Coastguard Worker // TB 760*76559068SAndroid Build Coastguard Worker // 761*76559068SAndroid Build Coastguard Worker // Each BlockGroup(BG) will associate with unique group id and the free blocks 762*76559068SAndroid Build Coastguard Worker // are managed by a list of TransferBatch(TB). To reduce the time of inserting 763*76559068SAndroid Build Coastguard Worker // blocks, BGs are sorted and the input `Array` are supposed to be sorted so 764*76559068SAndroid Build Coastguard Worker // that we can get better performance of maintaining sorted property. 765*76559068SAndroid Build Coastguard Worker // Use `SameGroup=true` to indicate that all blocks in the array are from the 766*76559068SAndroid Build Coastguard Worker // same group then we will skip checking the group id of each block. 767*76559068SAndroid Build Coastguard Worker void pushBlocksImpl(CacheT *C, uptr ClassId, RegionInfo *Region, 768*76559068SAndroid Build Coastguard Worker CompactPtrT *Array, u32 Size, bool SameGroup = false) 769*76559068SAndroid Build Coastguard Worker REQUIRES(Region->FLLock) { 770*76559068SAndroid Build Coastguard Worker DCHECK_NE(ClassId, SizeClassMap::BatchClassId); 771*76559068SAndroid Build Coastguard Worker DCHECK_GT(Size, 0U); 772*76559068SAndroid Build Coastguard Worker 773*76559068SAndroid Build Coastguard Worker auto CreateGroup = [&](uptr CompactPtrGroupBase) { 774*76559068SAndroid Build Coastguard Worker BatchGroupT *BG = 775*76559068SAndroid Build Coastguard Worker reinterpret_cast<BatchGroupT *>(C->getBatchClassBlock()); 776*76559068SAndroid Build Coastguard Worker BG->Batches.clear(); 777*76559068SAndroid Build Coastguard Worker TransferBatchT *TB = 778*76559068SAndroid Build Coastguard Worker reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock()); 779*76559068SAndroid Build Coastguard Worker TB->clear(); 780*76559068SAndroid Build Coastguard Worker 781*76559068SAndroid Build Coastguard Worker BG->CompactPtrGroupBase = CompactPtrGroupBase; 782*76559068SAndroid Build Coastguard Worker BG->Batches.push_front(TB); 783*76559068SAndroid Build Coastguard Worker BG->BytesInBGAtLastCheckpoint = 0; 784*76559068SAndroid Build Coastguard Worker BG->MaxCachedPerBatch = TransferBatchT::MaxNumCached; 785*76559068SAndroid Build Coastguard Worker 786*76559068SAndroid Build Coastguard Worker return BG; 787*76559068SAndroid Build Coastguard Worker }; 788*76559068SAndroid Build Coastguard Worker 789*76559068SAndroid Build Coastguard Worker auto InsertBlocks = [&](BatchGroupT *BG, CompactPtrT *Array, u32 Size) { 790*76559068SAndroid Build Coastguard Worker SinglyLinkedList<TransferBatchT> &Batches = BG->Batches; 791*76559068SAndroid Build Coastguard Worker TransferBatchT *CurBatch = Batches.front(); 792*76559068SAndroid Build Coastguard Worker DCHECK_NE(CurBatch, nullptr); 793*76559068SAndroid Build Coastguard Worker 794*76559068SAndroid Build Coastguard Worker for (u32 I = 0; I < Size;) { 795*76559068SAndroid Build Coastguard Worker DCHECK_GE(BG->MaxCachedPerBatch, CurBatch->getCount()); 796*76559068SAndroid Build Coastguard Worker u16 UnusedSlots = 797*76559068SAndroid Build Coastguard Worker static_cast<u16>(BG->MaxCachedPerBatch - CurBatch->getCount()); 798*76559068SAndroid Build Coastguard Worker if (UnusedSlots == 0) { 799*76559068SAndroid Build Coastguard Worker CurBatch = 800*76559068SAndroid Build Coastguard Worker reinterpret_cast<TransferBatchT *>(C->getBatchClassBlock()); 801*76559068SAndroid Build Coastguard Worker CurBatch->clear(); 802*76559068SAndroid Build Coastguard Worker Batches.push_front(CurBatch); 803*76559068SAndroid Build Coastguard Worker UnusedSlots = BG->MaxCachedPerBatch; 804*76559068SAndroid Build Coastguard Worker } 805*76559068SAndroid Build Coastguard Worker // `UnusedSlots` is u16 so the result will be also fit in u16. 806*76559068SAndroid Build Coastguard Worker u16 AppendSize = static_cast<u16>(Min<u32>(UnusedSlots, Size - I)); 807*76559068SAndroid Build Coastguard Worker CurBatch->appendFromArray(&Array[I], AppendSize); 808*76559068SAndroid Build Coastguard Worker I += AppendSize; 809*76559068SAndroid Build Coastguard Worker } 810*76559068SAndroid Build Coastguard Worker }; 811*76559068SAndroid Build Coastguard Worker 812*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks += Size; 813*76559068SAndroid Build Coastguard Worker BatchGroupT *Cur = Region->FreeListInfo.BlockList.front(); 814*76559068SAndroid Build Coastguard Worker 815*76559068SAndroid Build Coastguard Worker // In the following, `Cur` always points to the BatchGroup for blocks that 816*76559068SAndroid Build Coastguard Worker // will be pushed next. `Prev` is the element right before `Cur`. 817*76559068SAndroid Build Coastguard Worker BatchGroupT *Prev = nullptr; 818*76559068SAndroid Build Coastguard Worker 819*76559068SAndroid Build Coastguard Worker while (Cur != nullptr && 820*76559068SAndroid Build Coastguard Worker compactPtrGroup(Array[0]) > Cur->CompactPtrGroupBase) { 821*76559068SAndroid Build Coastguard Worker Prev = Cur; 822*76559068SAndroid Build Coastguard Worker Cur = Cur->Next; 823*76559068SAndroid Build Coastguard Worker } 824*76559068SAndroid Build Coastguard Worker 825*76559068SAndroid Build Coastguard Worker if (Cur == nullptr || 826*76559068SAndroid Build Coastguard Worker compactPtrGroup(Array[0]) != Cur->CompactPtrGroupBase) { 827*76559068SAndroid Build Coastguard Worker Cur = CreateGroup(compactPtrGroup(Array[0])); 828*76559068SAndroid Build Coastguard Worker if (Prev == nullptr) 829*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.push_front(Cur); 830*76559068SAndroid Build Coastguard Worker else 831*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.insert(Prev, Cur); 832*76559068SAndroid Build Coastguard Worker } 833*76559068SAndroid Build Coastguard Worker 834*76559068SAndroid Build Coastguard Worker // All the blocks are from the same group, just push without checking group 835*76559068SAndroid Build Coastguard Worker // id. 836*76559068SAndroid Build Coastguard Worker if (SameGroup) { 837*76559068SAndroid Build Coastguard Worker for (u32 I = 0; I < Size; ++I) 838*76559068SAndroid Build Coastguard Worker DCHECK_EQ(compactPtrGroup(Array[I]), Cur->CompactPtrGroupBase); 839*76559068SAndroid Build Coastguard Worker 840*76559068SAndroid Build Coastguard Worker InsertBlocks(Cur, Array, Size); 841*76559068SAndroid Build Coastguard Worker return; 842*76559068SAndroid Build Coastguard Worker } 843*76559068SAndroid Build Coastguard Worker 844*76559068SAndroid Build Coastguard Worker // The blocks are sorted by group id. Determine the segment of group and 845*76559068SAndroid Build Coastguard Worker // push them to their group together. 846*76559068SAndroid Build Coastguard Worker u32 Count = 1; 847*76559068SAndroid Build Coastguard Worker for (u32 I = 1; I < Size; ++I) { 848*76559068SAndroid Build Coastguard Worker if (compactPtrGroup(Array[I - 1]) != compactPtrGroup(Array[I])) { 849*76559068SAndroid Build Coastguard Worker DCHECK_EQ(compactPtrGroup(Array[I - 1]), Cur->CompactPtrGroupBase); 850*76559068SAndroid Build Coastguard Worker InsertBlocks(Cur, Array + I - Count, Count); 851*76559068SAndroid Build Coastguard Worker 852*76559068SAndroid Build Coastguard Worker while (Cur != nullptr && 853*76559068SAndroid Build Coastguard Worker compactPtrGroup(Array[I]) > Cur->CompactPtrGroupBase) { 854*76559068SAndroid Build Coastguard Worker Prev = Cur; 855*76559068SAndroid Build Coastguard Worker Cur = Cur->Next; 856*76559068SAndroid Build Coastguard Worker } 857*76559068SAndroid Build Coastguard Worker 858*76559068SAndroid Build Coastguard Worker if (Cur == nullptr || 859*76559068SAndroid Build Coastguard Worker compactPtrGroup(Array[I]) != Cur->CompactPtrGroupBase) { 860*76559068SAndroid Build Coastguard Worker Cur = CreateGroup(compactPtrGroup(Array[I])); 861*76559068SAndroid Build Coastguard Worker DCHECK_NE(Prev, nullptr); 862*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.insert(Prev, Cur); 863*76559068SAndroid Build Coastguard Worker } 864*76559068SAndroid Build Coastguard Worker 865*76559068SAndroid Build Coastguard Worker Count = 1; 866*76559068SAndroid Build Coastguard Worker } else { 867*76559068SAndroid Build Coastguard Worker ++Count; 868*76559068SAndroid Build Coastguard Worker } 869*76559068SAndroid Build Coastguard Worker } 870*76559068SAndroid Build Coastguard Worker 871*76559068SAndroid Build Coastguard Worker InsertBlocks(Cur, Array + Size - Count, Count); 872*76559068SAndroid Build Coastguard Worker } 873*76559068SAndroid Build Coastguard Worker popBlocksWithCV(CacheT * C,uptr ClassId,RegionInfo * Region,CompactPtrT * ToArray,const u16 MaxBlockCount,bool & ReportRegionExhausted)874*76559068SAndroid Build Coastguard Worker u16 popBlocksWithCV(CacheT *C, uptr ClassId, RegionInfo *Region, 875*76559068SAndroid Build Coastguard Worker CompactPtrT *ToArray, const u16 MaxBlockCount, 876*76559068SAndroid Build Coastguard Worker bool &ReportRegionExhausted) { 877*76559068SAndroid Build Coastguard Worker u16 PopCount = 0; 878*76559068SAndroid Build Coastguard Worker 879*76559068SAndroid Build Coastguard Worker while (true) { 880*76559068SAndroid Build Coastguard Worker // We only expect one thread doing the freelist refillment and other 881*76559068SAndroid Build Coastguard Worker // threads will be waiting for either the completion of the 882*76559068SAndroid Build Coastguard Worker // `populateFreeListAndPopBlocks()` or `pushBlocks()` called by other 883*76559068SAndroid Build Coastguard Worker // threads. 884*76559068SAndroid Build Coastguard Worker bool PopulateFreeList = false; 885*76559068SAndroid Build Coastguard Worker { 886*76559068SAndroid Build Coastguard Worker ScopedLock FL(Region->FLLock); 887*76559068SAndroid Build Coastguard Worker if (!Region->isPopulatingFreeList) { 888*76559068SAndroid Build Coastguard Worker Region->isPopulatingFreeList = true; 889*76559068SAndroid Build Coastguard Worker PopulateFreeList = true; 890*76559068SAndroid Build Coastguard Worker } 891*76559068SAndroid Build Coastguard Worker } 892*76559068SAndroid Build Coastguard Worker 893*76559068SAndroid Build Coastguard Worker if (PopulateFreeList) { 894*76559068SAndroid Build Coastguard Worker ScopedLock ML(Region->MMLock); 895*76559068SAndroid Build Coastguard Worker 896*76559068SAndroid Build Coastguard Worker const bool RegionIsExhausted = Region->Exhausted; 897*76559068SAndroid Build Coastguard Worker if (!RegionIsExhausted) { 898*76559068SAndroid Build Coastguard Worker PopCount = populateFreeListAndPopBlocks(C, ClassId, Region, ToArray, 899*76559068SAndroid Build Coastguard Worker MaxBlockCount); 900*76559068SAndroid Build Coastguard Worker } 901*76559068SAndroid Build Coastguard Worker ReportRegionExhausted = !RegionIsExhausted && Region->Exhausted; 902*76559068SAndroid Build Coastguard Worker 903*76559068SAndroid Build Coastguard Worker { 904*76559068SAndroid Build Coastguard Worker // Before reacquiring the `FLLock`, the freelist may be used up again 905*76559068SAndroid Build Coastguard Worker // and some threads are waiting for the freelist refillment by the 906*76559068SAndroid Build Coastguard Worker // current thread. It's important to set 907*76559068SAndroid Build Coastguard Worker // `Region->isPopulatingFreeList` to false so the threads about to 908*76559068SAndroid Build Coastguard Worker // sleep will notice the status change. 909*76559068SAndroid Build Coastguard Worker ScopedLock FL(Region->FLLock); 910*76559068SAndroid Build Coastguard Worker Region->isPopulatingFreeList = false; 911*76559068SAndroid Build Coastguard Worker Region->FLLockCV.notifyAll(Region->FLLock); 912*76559068SAndroid Build Coastguard Worker } 913*76559068SAndroid Build Coastguard Worker 914*76559068SAndroid Build Coastguard Worker break; 915*76559068SAndroid Build Coastguard Worker } 916*76559068SAndroid Build Coastguard Worker 917*76559068SAndroid Build Coastguard Worker // At here, there are two preconditions to be met before waiting, 918*76559068SAndroid Build Coastguard Worker // 1. The freelist is empty. 919*76559068SAndroid Build Coastguard Worker // 2. Region->isPopulatingFreeList == true, i.e, someone is still doing 920*76559068SAndroid Build Coastguard Worker // `populateFreeListAndPopBlocks()`. 921*76559068SAndroid Build Coastguard Worker // 922*76559068SAndroid Build Coastguard Worker // Note that it has the chance that freelist is empty but 923*76559068SAndroid Build Coastguard Worker // Region->isPopulatingFreeList == false because all the new populated 924*76559068SAndroid Build Coastguard Worker // blocks were used up right after the refillment. Therefore, we have to 925*76559068SAndroid Build Coastguard Worker // check if someone is still populating the freelist. 926*76559068SAndroid Build Coastguard Worker ScopedLock FL(Region->FLLock); 927*76559068SAndroid Build Coastguard Worker PopCount = popBlocksImpl(C, ClassId, Region, ToArray, MaxBlockCount); 928*76559068SAndroid Build Coastguard Worker if (PopCount != 0U) 929*76559068SAndroid Build Coastguard Worker break; 930*76559068SAndroid Build Coastguard Worker 931*76559068SAndroid Build Coastguard Worker if (!Region->isPopulatingFreeList) 932*76559068SAndroid Build Coastguard Worker continue; 933*76559068SAndroid Build Coastguard Worker 934*76559068SAndroid Build Coastguard Worker // Now the freelist is empty and someone's doing the refillment. We will 935*76559068SAndroid Build Coastguard Worker // wait until anyone refills the freelist or someone finishes doing 936*76559068SAndroid Build Coastguard Worker // `populateFreeListAndPopBlocks()`. The refillment can be done by 937*76559068SAndroid Build Coastguard Worker // `populateFreeListAndPopBlocks()`, `pushBlocks()`, 938*76559068SAndroid Build Coastguard Worker // `pushBatchClassBlocks()` and `mergeGroupsToReleaseBack()`. 939*76559068SAndroid Build Coastguard Worker Region->FLLockCV.wait(Region->FLLock); 940*76559068SAndroid Build Coastguard Worker 941*76559068SAndroid Build Coastguard Worker PopCount = popBlocksImpl(C, ClassId, Region, ToArray, MaxBlockCount); 942*76559068SAndroid Build Coastguard Worker if (PopCount != 0U) 943*76559068SAndroid Build Coastguard Worker break; 944*76559068SAndroid Build Coastguard Worker } 945*76559068SAndroid Build Coastguard Worker 946*76559068SAndroid Build Coastguard Worker return PopCount; 947*76559068SAndroid Build Coastguard Worker } 948*76559068SAndroid Build Coastguard Worker popBlocksImpl(CacheT * C,uptr ClassId,RegionInfo * Region,CompactPtrT * ToArray,const u16 MaxBlockCount)949*76559068SAndroid Build Coastguard Worker u16 popBlocksImpl(CacheT *C, uptr ClassId, RegionInfo *Region, 950*76559068SAndroid Build Coastguard Worker CompactPtrT *ToArray, const u16 MaxBlockCount) 951*76559068SAndroid Build Coastguard Worker REQUIRES(Region->FLLock) { 952*76559068SAndroid Build Coastguard Worker if (Region->FreeListInfo.BlockList.empty()) 953*76559068SAndroid Build Coastguard Worker return 0U; 954*76559068SAndroid Build Coastguard Worker 955*76559068SAndroid Build Coastguard Worker SinglyLinkedList<TransferBatchT> &Batches = 956*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.front()->Batches; 957*76559068SAndroid Build Coastguard Worker 958*76559068SAndroid Build Coastguard Worker if (Batches.empty()) { 959*76559068SAndroid Build Coastguard Worker DCHECK_EQ(ClassId, SizeClassMap::BatchClassId); 960*76559068SAndroid Build Coastguard Worker BatchGroupT *BG = Region->FreeListInfo.BlockList.front(); 961*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.pop_front(); 962*76559068SAndroid Build Coastguard Worker 963*76559068SAndroid Build Coastguard Worker // Block used by `BatchGroup` is from BatchClassId. Turn the block into 964*76559068SAndroid Build Coastguard Worker // `TransferBatch` with single block. 965*76559068SAndroid Build Coastguard Worker TransferBatchT *TB = reinterpret_cast<TransferBatchT *>(BG); 966*76559068SAndroid Build Coastguard Worker ToArray[0] = 967*76559068SAndroid Build Coastguard Worker compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(TB)); 968*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PoppedBlocks += 1; 969*76559068SAndroid Build Coastguard Worker return 1U; 970*76559068SAndroid Build Coastguard Worker } 971*76559068SAndroid Build Coastguard Worker 972*76559068SAndroid Build Coastguard Worker // So far, instead of always filling blocks to `MaxBlockCount`, we only 973*76559068SAndroid Build Coastguard Worker // examine single `TransferBatch` to minimize the time spent in the primary 974*76559068SAndroid Build Coastguard Worker // allocator. Besides, the sizes of `TransferBatch` and 975*76559068SAndroid Build Coastguard Worker // `CacheT::getMaxCached()` may also impact the time spent on accessing the 976*76559068SAndroid Build Coastguard Worker // primary allocator. 977*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): Evaluate if we want to always prepare `MaxBlockCount` 978*76559068SAndroid Build Coastguard Worker // blocks and/or adjust the size of `TransferBatch` according to 979*76559068SAndroid Build Coastguard Worker // `CacheT::getMaxCached()`. 980*76559068SAndroid Build Coastguard Worker TransferBatchT *B = Batches.front(); 981*76559068SAndroid Build Coastguard Worker DCHECK_NE(B, nullptr); 982*76559068SAndroid Build Coastguard Worker DCHECK_GT(B->getCount(), 0U); 983*76559068SAndroid Build Coastguard Worker 984*76559068SAndroid Build Coastguard Worker // BachClassId should always take all blocks in the TransferBatch. Read the 985*76559068SAndroid Build Coastguard Worker // comment in `pushBatchClassBlocks()` for more details. 986*76559068SAndroid Build Coastguard Worker const u16 PopCount = ClassId == SizeClassMap::BatchClassId 987*76559068SAndroid Build Coastguard Worker ? B->getCount() 988*76559068SAndroid Build Coastguard Worker : Min(MaxBlockCount, B->getCount()); 989*76559068SAndroid Build Coastguard Worker B->moveNToArray(ToArray, PopCount); 990*76559068SAndroid Build Coastguard Worker 991*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): The deallocation of unused BatchClassId blocks can be 992*76559068SAndroid Build Coastguard Worker // done without holding `FLLock`. 993*76559068SAndroid Build Coastguard Worker if (B->empty()) { 994*76559068SAndroid Build Coastguard Worker Batches.pop_front(); 995*76559068SAndroid Build Coastguard Worker // `TransferBatch` of BatchClassId is self-contained, no need to 996*76559068SAndroid Build Coastguard Worker // deallocate. Read the comment in `pushBatchClassBlocks()` for more 997*76559068SAndroid Build Coastguard Worker // details. 998*76559068SAndroid Build Coastguard Worker if (ClassId != SizeClassMap::BatchClassId) 999*76559068SAndroid Build Coastguard Worker C->deallocate(SizeClassMap::BatchClassId, B); 1000*76559068SAndroid Build Coastguard Worker 1001*76559068SAndroid Build Coastguard Worker if (Batches.empty()) { 1002*76559068SAndroid Build Coastguard Worker BatchGroupT *BG = Region->FreeListInfo.BlockList.front(); 1003*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.pop_front(); 1004*76559068SAndroid Build Coastguard Worker 1005*76559068SAndroid Build Coastguard Worker // We don't keep BatchGroup with zero blocks to avoid empty-checking 1006*76559068SAndroid Build Coastguard Worker // while allocating. Note that block used for constructing BatchGroup is 1007*76559068SAndroid Build Coastguard Worker // recorded as free blocks in the last element of BatchGroup::Batches. 1008*76559068SAndroid Build Coastguard Worker // Which means, once we pop the last TransferBatch, the block is 1009*76559068SAndroid Build Coastguard Worker // implicitly deallocated. 1010*76559068SAndroid Build Coastguard Worker if (ClassId != SizeClassMap::BatchClassId) 1011*76559068SAndroid Build Coastguard Worker C->deallocate(SizeClassMap::BatchClassId, BG); 1012*76559068SAndroid Build Coastguard Worker } 1013*76559068SAndroid Build Coastguard Worker } 1014*76559068SAndroid Build Coastguard Worker 1015*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PoppedBlocks += PopCount; 1016*76559068SAndroid Build Coastguard Worker 1017*76559068SAndroid Build Coastguard Worker return PopCount; 1018*76559068SAndroid Build Coastguard Worker } 1019*76559068SAndroid Build Coastguard Worker populateFreeListAndPopBlocks(CacheT * C,uptr ClassId,RegionInfo * Region,CompactPtrT * ToArray,const u16 MaxBlockCount)1020*76559068SAndroid Build Coastguard Worker NOINLINE u16 populateFreeListAndPopBlocks(CacheT *C, uptr ClassId, 1021*76559068SAndroid Build Coastguard Worker RegionInfo *Region, 1022*76559068SAndroid Build Coastguard Worker CompactPtrT *ToArray, 1023*76559068SAndroid Build Coastguard Worker const u16 MaxBlockCount) 1024*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1025*76559068SAndroid Build Coastguard Worker if (!Config::getEnableContiguousRegions() && 1026*76559068SAndroid Build Coastguard Worker !Region->MemMapInfo.MemMap.isAllocated()) { 1027*76559068SAndroid Build Coastguard Worker ReservedMemoryT ReservedMemory; 1028*76559068SAndroid Build Coastguard Worker if (UNLIKELY(!ReservedMemory.create(/*Addr=*/0U, RegionSize, 1029*76559068SAndroid Build Coastguard Worker "scudo:primary_reserve", 1030*76559068SAndroid Build Coastguard Worker MAP_ALLOWNOMEM))) { 1031*76559068SAndroid Build Coastguard Worker Printf("Can't reserve pages for size class %zu.\n", 1032*76559068SAndroid Build Coastguard Worker getSizeByClassId(ClassId)); 1033*76559068SAndroid Build Coastguard Worker return 0U; 1034*76559068SAndroid Build Coastguard Worker } 1035*76559068SAndroid Build Coastguard Worker initRegion(Region, ClassId, 1036*76559068SAndroid Build Coastguard Worker ReservedMemory.dispatch(ReservedMemory.getBase(), 1037*76559068SAndroid Build Coastguard Worker ReservedMemory.getCapacity()), 1038*76559068SAndroid Build Coastguard Worker /*EnableRandomOffset=*/false); 1039*76559068SAndroid Build Coastguard Worker } 1040*76559068SAndroid Build Coastguard Worker 1041*76559068SAndroid Build Coastguard Worker DCHECK(Region->MemMapInfo.MemMap.isAllocated()); 1042*76559068SAndroid Build Coastguard Worker const uptr Size = getSizeByClassId(ClassId); 1043*76559068SAndroid Build Coastguard Worker const u16 MaxCount = CacheT::getMaxCached(Size); 1044*76559068SAndroid Build Coastguard Worker const uptr RegionBeg = Region->RegionBeg; 1045*76559068SAndroid Build Coastguard Worker const uptr MappedUser = Region->MemMapInfo.MappedUser; 1046*76559068SAndroid Build Coastguard Worker const uptr TotalUserBytes = 1047*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser + MaxCount * Size; 1048*76559068SAndroid Build Coastguard Worker // Map more space for blocks, if necessary. 1049*76559068SAndroid Build Coastguard Worker if (TotalUserBytes > MappedUser) { 1050*76559068SAndroid Build Coastguard Worker // Do the mmap for the user memory. 1051*76559068SAndroid Build Coastguard Worker const uptr MapSize = 1052*76559068SAndroid Build Coastguard Worker roundUp(TotalUserBytes - MappedUser, MapSizeIncrement); 1053*76559068SAndroid Build Coastguard Worker const uptr RegionBase = RegionBeg - getRegionBaseByClassId(ClassId); 1054*76559068SAndroid Build Coastguard Worker if (UNLIKELY(RegionBase + MappedUser + MapSize > RegionSize)) { 1055*76559068SAndroid Build Coastguard Worker Region->Exhausted = true; 1056*76559068SAndroid Build Coastguard Worker return 0U; 1057*76559068SAndroid Build Coastguard Worker } 1058*76559068SAndroid Build Coastguard Worker 1059*76559068SAndroid Build Coastguard Worker if (UNLIKELY(!Region->MemMapInfo.MemMap.remap( 1060*76559068SAndroid Build Coastguard Worker RegionBeg + MappedUser, MapSize, "scudo:primary", 1061*76559068SAndroid Build Coastguard Worker MAP_ALLOWNOMEM | MAP_RESIZABLE | 1062*76559068SAndroid Build Coastguard Worker (useMemoryTagging<Config>(Options.load()) ? MAP_MEMTAG 1063*76559068SAndroid Build Coastguard Worker : 0)))) { 1064*76559068SAndroid Build Coastguard Worker return 0U; 1065*76559068SAndroid Build Coastguard Worker } 1066*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.MappedUser += MapSize; 1067*76559068SAndroid Build Coastguard Worker C->getStats().add(StatMapped, MapSize); 1068*76559068SAndroid Build Coastguard Worker } 1069*76559068SAndroid Build Coastguard Worker 1070*76559068SAndroid Build Coastguard Worker const u32 NumberOfBlocks = 1071*76559068SAndroid Build Coastguard Worker Min(MaxNumBatches * MaxCount, 1072*76559068SAndroid Build Coastguard Worker static_cast<u32>((Region->MemMapInfo.MappedUser - 1073*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser) / 1074*76559068SAndroid Build Coastguard Worker Size)); 1075*76559068SAndroid Build Coastguard Worker DCHECK_GT(NumberOfBlocks, 0); 1076*76559068SAndroid Build Coastguard Worker 1077*76559068SAndroid Build Coastguard Worker constexpr u32 ShuffleArraySize = 1078*76559068SAndroid Build Coastguard Worker MaxNumBatches * TransferBatchT::MaxNumCached; 1079*76559068SAndroid Build Coastguard Worker CompactPtrT ShuffleArray[ShuffleArraySize]; 1080*76559068SAndroid Build Coastguard Worker DCHECK_LE(NumberOfBlocks, ShuffleArraySize); 1081*76559068SAndroid Build Coastguard Worker 1082*76559068SAndroid Build Coastguard Worker const uptr CompactPtrBase = getCompactPtrBaseByClassId(ClassId); 1083*76559068SAndroid Build Coastguard Worker uptr P = RegionBeg + Region->MemMapInfo.AllocatedUser; 1084*76559068SAndroid Build Coastguard Worker for (u32 I = 0; I < NumberOfBlocks; I++, P += Size) 1085*76559068SAndroid Build Coastguard Worker ShuffleArray[I] = compactPtrInternal(CompactPtrBase, P); 1086*76559068SAndroid Build Coastguard Worker 1087*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 1088*76559068SAndroid Build Coastguard Worker 1089*76559068SAndroid Build Coastguard Worker if (ClassId != SizeClassMap::BatchClassId) { 1090*76559068SAndroid Build Coastguard Worker u32 N = 1; 1091*76559068SAndroid Build Coastguard Worker uptr CurGroup = compactPtrGroup(ShuffleArray[0]); 1092*76559068SAndroid Build Coastguard Worker for (u32 I = 1; I < NumberOfBlocks; I++) { 1093*76559068SAndroid Build Coastguard Worker if (UNLIKELY(compactPtrGroup(ShuffleArray[I]) != CurGroup)) { 1094*76559068SAndroid Build Coastguard Worker shuffle(ShuffleArray + I - N, N, &Region->RandState); 1095*76559068SAndroid Build Coastguard Worker pushBlocksImpl(C, ClassId, Region, ShuffleArray + I - N, N, 1096*76559068SAndroid Build Coastguard Worker /*SameGroup=*/true); 1097*76559068SAndroid Build Coastguard Worker N = 1; 1098*76559068SAndroid Build Coastguard Worker CurGroup = compactPtrGroup(ShuffleArray[I]); 1099*76559068SAndroid Build Coastguard Worker } else { 1100*76559068SAndroid Build Coastguard Worker ++N; 1101*76559068SAndroid Build Coastguard Worker } 1102*76559068SAndroid Build Coastguard Worker } 1103*76559068SAndroid Build Coastguard Worker 1104*76559068SAndroid Build Coastguard Worker shuffle(ShuffleArray + NumberOfBlocks - N, N, &Region->RandState); 1105*76559068SAndroid Build Coastguard Worker pushBlocksImpl(C, ClassId, Region, &ShuffleArray[NumberOfBlocks - N], N, 1106*76559068SAndroid Build Coastguard Worker /*SameGroup=*/true); 1107*76559068SAndroid Build Coastguard Worker } else { 1108*76559068SAndroid Build Coastguard Worker pushBatchClassBlocks(Region, ShuffleArray, NumberOfBlocks); 1109*76559068SAndroid Build Coastguard Worker } 1110*76559068SAndroid Build Coastguard Worker 1111*76559068SAndroid Build Coastguard Worker const u16 PopCount = 1112*76559068SAndroid Build Coastguard Worker popBlocksImpl(C, ClassId, Region, ToArray, MaxBlockCount); 1113*76559068SAndroid Build Coastguard Worker DCHECK_NE(PopCount, 0U); 1114*76559068SAndroid Build Coastguard Worker 1115*76559068SAndroid Build Coastguard Worker // Note that `PushedBlocks` and `PoppedBlocks` are supposed to only record 1116*76559068SAndroid Build Coastguard Worker // the requests from `PushBlocks` and `PopBatch` which are external 1117*76559068SAndroid Build Coastguard Worker // interfaces. `populateFreeListAndPopBlocks` is the internal interface so 1118*76559068SAndroid Build Coastguard Worker // we should set the values back to avoid incorrectly setting the stats. 1119*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks -= NumberOfBlocks; 1120*76559068SAndroid Build Coastguard Worker 1121*76559068SAndroid Build Coastguard Worker const uptr AllocatedUser = Size * NumberOfBlocks; 1122*76559068SAndroid Build Coastguard Worker C->getStats().add(StatFree, AllocatedUser); 1123*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser += AllocatedUser; 1124*76559068SAndroid Build Coastguard Worker 1125*76559068SAndroid Build Coastguard Worker return PopCount; 1126*76559068SAndroid Build Coastguard Worker } 1127*76559068SAndroid Build Coastguard Worker getStats(ScopedString * Str,uptr ClassId,RegionInfo * Region)1128*76559068SAndroid Build Coastguard Worker void getStats(ScopedString *Str, uptr ClassId, RegionInfo *Region) 1129*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock, Region->FLLock) { 1130*76559068SAndroid Build Coastguard Worker if (Region->MemMapInfo.MappedUser == 0) 1131*76559068SAndroid Build Coastguard Worker return; 1132*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(ClassId); 1133*76559068SAndroid Build Coastguard Worker const uptr InUseBlocks = 1134*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; 1135*76559068SAndroid Build Coastguard Worker const uptr BytesInFreeList = 1136*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser - InUseBlocks * BlockSize; 1137*76559068SAndroid Build Coastguard Worker uptr RegionPushedBytesDelta = 0; 1138*76559068SAndroid Build Coastguard Worker if (BytesInFreeList >= 1139*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint) { 1140*76559068SAndroid Build Coastguard Worker RegionPushedBytesDelta = 1141*76559068SAndroid Build Coastguard Worker BytesInFreeList - Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 1142*76559068SAndroid Build Coastguard Worker } 1143*76559068SAndroid Build Coastguard Worker const uptr TotalChunks = Region->MemMapInfo.AllocatedUser / BlockSize; 1144*76559068SAndroid Build Coastguard Worker Str->append( 1145*76559068SAndroid Build Coastguard Worker "%s %02zu (%6zu): mapped: %6zuK popped: %7zu pushed: %7zu " 1146*76559068SAndroid Build Coastguard Worker "inuse: %6zu total: %6zu releases: %6zu last " 1147*76559068SAndroid Build Coastguard Worker "released: %6zuK latest pushed bytes: %6zuK region: 0x%zx (0x%zx)\n", 1148*76559068SAndroid Build Coastguard Worker Region->Exhausted ? "E" : " ", ClassId, getSizeByClassId(ClassId), 1149*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.MappedUser >> 10, Region->FreeListInfo.PoppedBlocks, 1150*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks, InUseBlocks, TotalChunks, 1151*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.RangesReleased, 1152*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.LastReleasedBytes >> 10, 1153*76559068SAndroid Build Coastguard Worker RegionPushedBytesDelta >> 10, Region->RegionBeg, 1154*76559068SAndroid Build Coastguard Worker getRegionBaseByClassId(ClassId)); 1155*76559068SAndroid Build Coastguard Worker } 1156*76559068SAndroid Build Coastguard Worker getRegionFragmentationInfo(RegionInfo * Region,uptr ClassId,ScopedString * Str)1157*76559068SAndroid Build Coastguard Worker void getRegionFragmentationInfo(RegionInfo *Region, uptr ClassId, 1158*76559068SAndroid Build Coastguard Worker ScopedString *Str) REQUIRES(Region->MMLock) { 1159*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(ClassId); 1160*76559068SAndroid Build Coastguard Worker const uptr AllocatedUserEnd = 1161*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser + Region->RegionBeg; 1162*76559068SAndroid Build Coastguard Worker 1163*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> GroupsToRelease; 1164*76559068SAndroid Build Coastguard Worker { 1165*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 1166*76559068SAndroid Build Coastguard Worker GroupsToRelease = Region->FreeListInfo.BlockList; 1167*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.clear(); 1168*76559068SAndroid Build Coastguard Worker } 1169*76559068SAndroid Build Coastguard Worker 1170*76559068SAndroid Build Coastguard Worker FragmentationRecorder Recorder; 1171*76559068SAndroid Build Coastguard Worker if (!GroupsToRelease.empty()) { 1172*76559068SAndroid Build Coastguard Worker PageReleaseContext Context = 1173*76559068SAndroid Build Coastguard Worker markFreeBlocks(Region, BlockSize, AllocatedUserEnd, 1174*76559068SAndroid Build Coastguard Worker getCompactPtrBaseByClassId(ClassId), GroupsToRelease); 1175*76559068SAndroid Build Coastguard Worker auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; }; 1176*76559068SAndroid Build Coastguard Worker releaseFreeMemoryToOS(Context, Recorder, SkipRegion); 1177*76559068SAndroid Build Coastguard Worker 1178*76559068SAndroid Build Coastguard Worker mergeGroupsToReleaseBack(Region, GroupsToRelease); 1179*76559068SAndroid Build Coastguard Worker } 1180*76559068SAndroid Build Coastguard Worker 1181*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 1182*76559068SAndroid Build Coastguard Worker const uptr PageSize = getPageSizeCached(); 1183*76559068SAndroid Build Coastguard Worker const uptr TotalBlocks = Region->MemMapInfo.AllocatedUser / BlockSize; 1184*76559068SAndroid Build Coastguard Worker const uptr InUseBlocks = 1185*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PoppedBlocks - Region->FreeListInfo.PushedBlocks; 1186*76559068SAndroid Build Coastguard Worker const uptr AllocatedPagesCount = 1187*76559068SAndroid Build Coastguard Worker roundUp(Region->MemMapInfo.AllocatedUser, PageSize) / PageSize; 1188*76559068SAndroid Build Coastguard Worker DCHECK_GE(AllocatedPagesCount, Recorder.getReleasedPagesCount()); 1189*76559068SAndroid Build Coastguard Worker const uptr InUsePages = 1190*76559068SAndroid Build Coastguard Worker AllocatedPagesCount - Recorder.getReleasedPagesCount(); 1191*76559068SAndroid Build Coastguard Worker const uptr InUseBytes = InUsePages * PageSize; 1192*76559068SAndroid Build Coastguard Worker 1193*76559068SAndroid Build Coastguard Worker uptr Integral; 1194*76559068SAndroid Build Coastguard Worker uptr Fractional; 1195*76559068SAndroid Build Coastguard Worker computePercentage(BlockSize * InUseBlocks, InUseBytes, &Integral, 1196*76559068SAndroid Build Coastguard Worker &Fractional); 1197*76559068SAndroid Build Coastguard Worker Str->append(" %02zu (%6zu): inuse/total blocks: %6zu/%6zu inuse/total " 1198*76559068SAndroid Build Coastguard Worker "pages: %6zu/%6zu inuse bytes: %6zuK util: %3zu.%02zu%%\n", 1199*76559068SAndroid Build Coastguard Worker ClassId, BlockSize, InUseBlocks, TotalBlocks, InUsePages, 1200*76559068SAndroid Build Coastguard Worker AllocatedPagesCount, InUseBytes >> 10, Integral, Fractional); 1201*76559068SAndroid Build Coastguard Worker } 1202*76559068SAndroid Build Coastguard Worker getMemoryGroupFragmentationInfoInRegion(RegionInfo * Region,uptr ClassId,ScopedString * Str)1203*76559068SAndroid Build Coastguard Worker void getMemoryGroupFragmentationInfoInRegion(RegionInfo *Region, uptr ClassId, 1204*76559068SAndroid Build Coastguard Worker ScopedString *Str) 1205*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1206*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(ClassId); 1207*76559068SAndroid Build Coastguard Worker const uptr AllocatedUserEnd = 1208*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser + Region->RegionBeg; 1209*76559068SAndroid Build Coastguard Worker 1210*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> GroupsToRelease; 1211*76559068SAndroid Build Coastguard Worker { 1212*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 1213*76559068SAndroid Build Coastguard Worker GroupsToRelease = Region->FreeListInfo.BlockList; 1214*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.clear(); 1215*76559068SAndroid Build Coastguard Worker } 1216*76559068SAndroid Build Coastguard Worker 1217*76559068SAndroid Build Coastguard Worker constexpr uptr GroupSize = (1UL << GroupSizeLog); 1218*76559068SAndroid Build Coastguard Worker constexpr uptr MaxNumGroups = RegionSize / GroupSize; 1219*76559068SAndroid Build Coastguard Worker 1220*76559068SAndroid Build Coastguard Worker MemoryGroupFragmentationRecorder<GroupSize, MaxNumGroups> Recorder; 1221*76559068SAndroid Build Coastguard Worker if (!GroupsToRelease.empty()) { 1222*76559068SAndroid Build Coastguard Worker PageReleaseContext Context = 1223*76559068SAndroid Build Coastguard Worker markFreeBlocks(Region, BlockSize, AllocatedUserEnd, 1224*76559068SAndroid Build Coastguard Worker getCompactPtrBaseByClassId(ClassId), GroupsToRelease); 1225*76559068SAndroid Build Coastguard Worker auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; }; 1226*76559068SAndroid Build Coastguard Worker releaseFreeMemoryToOS(Context, Recorder, SkipRegion); 1227*76559068SAndroid Build Coastguard Worker 1228*76559068SAndroid Build Coastguard Worker mergeGroupsToReleaseBack(Region, GroupsToRelease); 1229*76559068SAndroid Build Coastguard Worker } 1230*76559068SAndroid Build Coastguard Worker 1231*76559068SAndroid Build Coastguard Worker Str->append("MemoryGroupFragmentationInfo in Region %zu (%zu)\n", ClassId, 1232*76559068SAndroid Build Coastguard Worker BlockSize); 1233*76559068SAndroid Build Coastguard Worker 1234*76559068SAndroid Build Coastguard Worker const uptr MaxNumGroupsInUse = 1235*76559068SAndroid Build Coastguard Worker roundUp(Region->MemMapInfo.AllocatedUser, GroupSize) / GroupSize; 1236*76559068SAndroid Build Coastguard Worker for (uptr I = 0; I < MaxNumGroupsInUse; ++I) { 1237*76559068SAndroid Build Coastguard Worker uptr Integral; 1238*76559068SAndroid Build Coastguard Worker uptr Fractional; 1239*76559068SAndroid Build Coastguard Worker computePercentage(Recorder.NumPagesInOneGroup - 1240*76559068SAndroid Build Coastguard Worker Recorder.getNumFreePages(I), 1241*76559068SAndroid Build Coastguard Worker Recorder.NumPagesInOneGroup, &Integral, &Fractional); 1242*76559068SAndroid Build Coastguard Worker Str->append("MemoryGroup #%zu (0x%zx): util: %3zu.%02zu%%\n", I, 1243*76559068SAndroid Build Coastguard Worker Region->RegionBeg + I * GroupSize, Integral, Fractional); 1244*76559068SAndroid Build Coastguard Worker } 1245*76559068SAndroid Build Coastguard Worker } 1246*76559068SAndroid Build Coastguard Worker 1247*76559068SAndroid Build Coastguard Worker NOINLINE uptr releaseToOSMaybe(RegionInfo *Region, uptr ClassId, 1248*76559068SAndroid Build Coastguard Worker ReleaseToOS ReleaseType = ReleaseToOS::Normal) 1249*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1250*76559068SAndroid Build Coastguard Worker const uptr BlockSize = getSizeByClassId(ClassId); 1251*76559068SAndroid Build Coastguard Worker uptr BytesInFreeList; 1252*76559068SAndroid Build Coastguard Worker const uptr AllocatedUserEnd = 1253*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser + Region->RegionBeg; 1254*76559068SAndroid Build Coastguard Worker uptr RegionPushedBytesDelta = 0; 1255*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> GroupsToRelease; 1256*76559068SAndroid Build Coastguard Worker 1257*76559068SAndroid Build Coastguard Worker { 1258*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 1259*76559068SAndroid Build Coastguard Worker 1260*76559068SAndroid Build Coastguard Worker BytesInFreeList = Region->MemMapInfo.AllocatedUser - 1261*76559068SAndroid Build Coastguard Worker (Region->FreeListInfo.PoppedBlocks - 1262*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks) * 1263*76559068SAndroid Build Coastguard Worker BlockSize; 1264*76559068SAndroid Build Coastguard Worker if (UNLIKELY(BytesInFreeList == 0)) 1265*76559068SAndroid Build Coastguard Worker return false; 1266*76559068SAndroid Build Coastguard Worker 1267*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1268*76559068SAndroid Build Coastguard Worker // 1. Check if we have enough free blocks and if it's worth doing a page 1269*76559068SAndroid Build Coastguard Worker // release. 1270*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1271*76559068SAndroid Build Coastguard Worker if (ReleaseType != ReleaseToOS::ForceAll && 1272*76559068SAndroid Build Coastguard Worker !hasChanceToReleasePages(Region, BlockSize, BytesInFreeList, 1273*76559068SAndroid Build Coastguard Worker ReleaseType)) { 1274*76559068SAndroid Build Coastguard Worker return 0; 1275*76559068SAndroid Build Coastguard Worker } 1276*76559068SAndroid Build Coastguard Worker 1277*76559068SAndroid Build Coastguard Worker // Given that we will unlock the freelist for block operations, cache the 1278*76559068SAndroid Build Coastguard Worker // value here so that when we are adapting the `TryReleaseThreshold` 1279*76559068SAndroid Build Coastguard Worker // later, we are using the right metric. 1280*76559068SAndroid Build Coastguard Worker RegionPushedBytesDelta = 1281*76559068SAndroid Build Coastguard Worker BytesInFreeList - Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 1282*76559068SAndroid Build Coastguard Worker 1283*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1284*76559068SAndroid Build Coastguard Worker // 2. Determine which groups can release the pages. Use a heuristic to 1285*76559068SAndroid Build Coastguard Worker // gather groups that are candidates for doing a release. 1286*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1287*76559068SAndroid Build Coastguard Worker if (ReleaseType == ReleaseToOS::ForceAll) { 1288*76559068SAndroid Build Coastguard Worker GroupsToRelease = Region->FreeListInfo.BlockList; 1289*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.clear(); 1290*76559068SAndroid Build Coastguard Worker } else { 1291*76559068SAndroid Build Coastguard Worker GroupsToRelease = 1292*76559068SAndroid Build Coastguard Worker collectGroupsToRelease(Region, BlockSize, AllocatedUserEnd, 1293*76559068SAndroid Build Coastguard Worker getCompactPtrBaseByClassId(ClassId)); 1294*76559068SAndroid Build Coastguard Worker } 1295*76559068SAndroid Build Coastguard Worker if (GroupsToRelease.empty()) 1296*76559068SAndroid Build Coastguard Worker return 0; 1297*76559068SAndroid Build Coastguard Worker } 1298*76559068SAndroid Build Coastguard Worker 1299*76559068SAndroid Build Coastguard Worker // Note that we have extracted the `GroupsToRelease` from region freelist. 1300*76559068SAndroid Build Coastguard Worker // It's safe to let pushBlocks()/popBlocks() access the remaining region 1301*76559068SAndroid Build Coastguard Worker // freelist. In the steps 3 and 4, we will temporarily release the FLLock 1302*76559068SAndroid Build Coastguard Worker // and lock it again before step 5. 1303*76559068SAndroid Build Coastguard Worker 1304*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1305*76559068SAndroid Build Coastguard Worker // 3. Mark the free blocks in `GroupsToRelease` in the `PageReleaseContext`. 1306*76559068SAndroid Build Coastguard Worker // Then we can tell which pages are in-use by querying 1307*76559068SAndroid Build Coastguard Worker // `PageReleaseContext`. 1308*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1309*76559068SAndroid Build Coastguard Worker PageReleaseContext Context = 1310*76559068SAndroid Build Coastguard Worker markFreeBlocks(Region, BlockSize, AllocatedUserEnd, 1311*76559068SAndroid Build Coastguard Worker getCompactPtrBaseByClassId(ClassId), GroupsToRelease); 1312*76559068SAndroid Build Coastguard Worker if (UNLIKELY(!Context.hasBlockMarked())) { 1313*76559068SAndroid Build Coastguard Worker mergeGroupsToReleaseBack(Region, GroupsToRelease); 1314*76559068SAndroid Build Coastguard Worker return 0; 1315*76559068SAndroid Build Coastguard Worker } 1316*76559068SAndroid Build Coastguard Worker 1317*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1318*76559068SAndroid Build Coastguard Worker // 4. Release the unused physical pages back to the OS. 1319*76559068SAndroid Build Coastguard Worker // ==================================================================== // 1320*76559068SAndroid Build Coastguard Worker RegionReleaseRecorder<MemMapT> Recorder(&Region->MemMapInfo.MemMap, 1321*76559068SAndroid Build Coastguard Worker Region->RegionBeg, 1322*76559068SAndroid Build Coastguard Worker Context.getReleaseOffset()); 1323*76559068SAndroid Build Coastguard Worker auto SkipRegion = [](UNUSED uptr RegionIndex) { return false; }; 1324*76559068SAndroid Build Coastguard Worker releaseFreeMemoryToOS(Context, Recorder, SkipRegion); 1325*76559068SAndroid Build Coastguard Worker if (Recorder.getReleasedRangesCount() > 0) { 1326*76559068SAndroid Build Coastguard Worker // This is the case that we didn't hit the release threshold but it has 1327*76559068SAndroid Build Coastguard Worker // been past a certain period of time. Thus we try to release some pages 1328*76559068SAndroid Build Coastguard Worker // and if it does release some additional pages, it's hint that we are 1329*76559068SAndroid Build Coastguard Worker // able to lower the threshold. Currently, this case happens when the 1330*76559068SAndroid Build Coastguard Worker // `RegionPushedBytesDelta` is over half of the `TryReleaseThreshold`. As 1331*76559068SAndroid Build Coastguard Worker // a result, we shrink the threshold to half accordingly. 1332*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): Apply the same adjustment strategy to small blocks. 1333*76559068SAndroid Build Coastguard Worker if (!isSmallBlock(BlockSize)) { 1334*76559068SAndroid Build Coastguard Worker if (RegionPushedBytesDelta < Region->ReleaseInfo.TryReleaseThreshold && 1335*76559068SAndroid Build Coastguard Worker Recorder.getReleasedBytes() > 1336*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.LastReleasedBytes + 1337*76559068SAndroid Build Coastguard Worker getMinReleaseAttemptSize(BlockSize)) { 1338*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold = 1339*76559068SAndroid Build Coastguard Worker Max(Region->ReleaseInfo.TryReleaseThreshold / 2, 1340*76559068SAndroid Build Coastguard Worker getMinReleaseAttemptSize(BlockSize)); 1341*76559068SAndroid Build Coastguard Worker } 1342*76559068SAndroid Build Coastguard Worker } 1343*76559068SAndroid Build Coastguard Worker 1344*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; 1345*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.RangesReleased += Recorder.getReleasedRangesCount(); 1346*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.LastReleasedBytes = Recorder.getReleasedBytes(); 1347*76559068SAndroid Build Coastguard Worker } 1348*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.LastReleaseAtNs = getMonotonicTimeFast(); 1349*76559068SAndroid Build Coastguard Worker 1350*76559068SAndroid Build Coastguard Worker if (Region->ReleaseInfo.PendingPushedBytesDelta > 0) { 1351*76559068SAndroid Build Coastguard Worker // Instead of increasing the threshold by the amount of 1352*76559068SAndroid Build Coastguard Worker // `PendingPushedBytesDelta`, we only increase half of the amount so that 1353*76559068SAndroid Build Coastguard Worker // it won't be a leap (which may lead to higher memory pressure) because 1354*76559068SAndroid Build Coastguard Worker // of certain memory usage bursts which don't happen frequently. 1355*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold += 1356*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.PendingPushedBytesDelta / 2; 1357*76559068SAndroid Build Coastguard Worker // This is another guard of avoiding the growth of threshold indefinitely. 1358*76559068SAndroid Build Coastguard Worker // Note that we may consider to make this configurable if we have a better 1359*76559068SAndroid Build Coastguard Worker // way to model this. 1360*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold = Min<uptr>( 1361*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold, (1UL << GroupSizeLog) / 2); 1362*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.PendingPushedBytesDelta = 0; 1363*76559068SAndroid Build Coastguard Worker } 1364*76559068SAndroid Build Coastguard Worker 1365*76559068SAndroid Build Coastguard Worker // ====================================================================== // 1366*76559068SAndroid Build Coastguard Worker // 5. Merge the `GroupsToRelease` back to the freelist. 1367*76559068SAndroid Build Coastguard Worker // ====================================================================== // 1368*76559068SAndroid Build Coastguard Worker mergeGroupsToReleaseBack(Region, GroupsToRelease); 1369*76559068SAndroid Build Coastguard Worker 1370*76559068SAndroid Build Coastguard Worker return Recorder.getReleasedBytes(); 1371*76559068SAndroid Build Coastguard Worker } 1372*76559068SAndroid Build Coastguard Worker hasChanceToReleasePages(RegionInfo * Region,uptr BlockSize,uptr BytesInFreeList,ReleaseToOS ReleaseType)1373*76559068SAndroid Build Coastguard Worker bool hasChanceToReleasePages(RegionInfo *Region, uptr BlockSize, 1374*76559068SAndroid Build Coastguard Worker uptr BytesInFreeList, ReleaseToOS ReleaseType) 1375*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock, Region->FLLock) { 1376*76559068SAndroid Build Coastguard Worker DCHECK_GE(Region->FreeListInfo.PoppedBlocks, 1377*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.PushedBlocks); 1378*76559068SAndroid Build Coastguard Worker // Always update `BytesInFreeListAtLastCheckpoint` with the smallest value 1379*76559068SAndroid Build Coastguard Worker // so that we won't underestimate the releasable pages. For example, the 1380*76559068SAndroid Build Coastguard Worker // following is the region usage, 1381*76559068SAndroid Build Coastguard Worker // 1382*76559068SAndroid Build Coastguard Worker // BytesInFreeListAtLastCheckpoint AllocatedUser 1383*76559068SAndroid Build Coastguard Worker // v v 1384*76559068SAndroid Build Coastguard Worker // |---------------------------------------> 1385*76559068SAndroid Build Coastguard Worker // ^ ^ 1386*76559068SAndroid Build Coastguard Worker // BytesInFreeList ReleaseThreshold 1387*76559068SAndroid Build Coastguard Worker // 1388*76559068SAndroid Build Coastguard Worker // In general, if we have collected enough bytes and the amount of free 1389*76559068SAndroid Build Coastguard Worker // bytes meets the ReleaseThreshold, we will try to do page release. If we 1390*76559068SAndroid Build Coastguard Worker // don't update `BytesInFreeListAtLastCheckpoint` when the current 1391*76559068SAndroid Build Coastguard Worker // `BytesInFreeList` is smaller, we may take longer time to wait for enough 1392*76559068SAndroid Build Coastguard Worker // freed blocks because we miss the bytes between 1393*76559068SAndroid Build Coastguard Worker // (BytesInFreeListAtLastCheckpoint - BytesInFreeList). 1394*76559068SAndroid Build Coastguard Worker if (BytesInFreeList <= 1395*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint) { 1396*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint = BytesInFreeList; 1397*76559068SAndroid Build Coastguard Worker } 1398*76559068SAndroid Build Coastguard Worker 1399*76559068SAndroid Build Coastguard Worker const uptr RegionPushedBytesDelta = 1400*76559068SAndroid Build Coastguard Worker BytesInFreeList - Region->ReleaseInfo.BytesInFreeListAtLastCheckpoint; 1401*76559068SAndroid Build Coastguard Worker 1402*76559068SAndroid Build Coastguard Worker if (ReleaseType == ReleaseToOS::Normal) { 1403*76559068SAndroid Build Coastguard Worker if (RegionPushedBytesDelta < Region->ReleaseInfo.TryReleaseThreshold / 2) 1404*76559068SAndroid Build Coastguard Worker return false; 1405*76559068SAndroid Build Coastguard Worker 1406*76559068SAndroid Build Coastguard Worker const s64 IntervalMs = atomic_load_relaxed(&ReleaseToOsIntervalMs); 1407*76559068SAndroid Build Coastguard Worker if (IntervalMs < 0) 1408*76559068SAndroid Build Coastguard Worker return false; 1409*76559068SAndroid Build Coastguard Worker 1410*76559068SAndroid Build Coastguard Worker const u64 IntervalNs = static_cast<u64>(IntervalMs) * 1000000; 1411*76559068SAndroid Build Coastguard Worker const u64 CurTimeNs = getMonotonicTimeFast(); 1412*76559068SAndroid Build Coastguard Worker const u64 DiffSinceLastReleaseNs = 1413*76559068SAndroid Build Coastguard Worker CurTimeNs - Region->ReleaseInfo.LastReleaseAtNs; 1414*76559068SAndroid Build Coastguard Worker 1415*76559068SAndroid Build Coastguard Worker // At here, `RegionPushedBytesDelta` is more than half of 1416*76559068SAndroid Build Coastguard Worker // `TryReleaseThreshold`. If the last release happened 2 release interval 1417*76559068SAndroid Build Coastguard Worker // before, we will still try to see if there's any chance to release some 1418*76559068SAndroid Build Coastguard Worker // memory even it doesn't exceed the threshold. 1419*76559068SAndroid Build Coastguard Worker if (RegionPushedBytesDelta < Region->ReleaseInfo.TryReleaseThreshold) { 1420*76559068SAndroid Build Coastguard Worker // We want the threshold to have a shorter response time to the variant 1421*76559068SAndroid Build Coastguard Worker // memory usage patterns. According to data collected during experiments 1422*76559068SAndroid Build Coastguard Worker // (which were done with 1, 2, 4, 8 intervals), `2` strikes the better 1423*76559068SAndroid Build Coastguard Worker // balance between the memory usage and number of page release attempts. 1424*76559068SAndroid Build Coastguard Worker if (DiffSinceLastReleaseNs < 2 * IntervalNs) 1425*76559068SAndroid Build Coastguard Worker return false; 1426*76559068SAndroid Build Coastguard Worker } else if (DiffSinceLastReleaseNs < IntervalNs) { 1427*76559068SAndroid Build Coastguard Worker // In this case, we are over the threshold but we just did some page 1428*76559068SAndroid Build Coastguard Worker // release in the same release interval. This is a hint that we may want 1429*76559068SAndroid Build Coastguard Worker // a higher threshold so that we can release more memory at once. 1430*76559068SAndroid Build Coastguard Worker // `TryReleaseThreshold` will be adjusted according to how many bytes 1431*76559068SAndroid Build Coastguard Worker // are not released, i.e., the `PendingPushedBytesdelta` here. 1432*76559068SAndroid Build Coastguard Worker // TODO(chiahungduan): Apply the same adjustment strategy to small 1433*76559068SAndroid Build Coastguard Worker // blocks. 1434*76559068SAndroid Build Coastguard Worker if (!isSmallBlock(BlockSize)) 1435*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.PendingPushedBytesDelta = RegionPushedBytesDelta; 1436*76559068SAndroid Build Coastguard Worker 1437*76559068SAndroid Build Coastguard Worker // Memory was returned recently. 1438*76559068SAndroid Build Coastguard Worker return false; 1439*76559068SAndroid Build Coastguard Worker } 1440*76559068SAndroid Build Coastguard Worker } // if (ReleaseType == ReleaseToOS::Normal) 1441*76559068SAndroid Build Coastguard Worker 1442*76559068SAndroid Build Coastguard Worker return true; 1443*76559068SAndroid Build Coastguard Worker } 1444*76559068SAndroid Build Coastguard Worker 1445*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> collectGroupsToRelease(RegionInfo * Region,const uptr BlockSize,const uptr AllocatedUserEnd,const uptr CompactPtrBase)1446*76559068SAndroid Build Coastguard Worker collectGroupsToRelease(RegionInfo *Region, const uptr BlockSize, 1447*76559068SAndroid Build Coastguard Worker const uptr AllocatedUserEnd, const uptr CompactPtrBase) 1448*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock, Region->FLLock) { 1449*76559068SAndroid Build Coastguard Worker const uptr GroupSize = (1UL << GroupSizeLog); 1450*76559068SAndroid Build Coastguard Worker const uptr PageSize = getPageSizeCached(); 1451*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> GroupsToRelease; 1452*76559068SAndroid Build Coastguard Worker 1453*76559068SAndroid Build Coastguard Worker // We are examining each group and will take the minimum distance to the 1454*76559068SAndroid Build Coastguard Worker // release threshold as the next `TryReleaseThreshold`. Note that if the 1455*76559068SAndroid Build Coastguard Worker // size of free blocks has reached the release threshold, the distance to 1456*76559068SAndroid Build Coastguard Worker // the next release will be PageSize * SmallerBlockReleasePageDelta. See the 1457*76559068SAndroid Build Coastguard Worker // comment on `SmallerBlockReleasePageDelta` for more details. 1458*76559068SAndroid Build Coastguard Worker uptr MinDistToThreshold = GroupSize; 1459*76559068SAndroid Build Coastguard Worker 1460*76559068SAndroid Build Coastguard Worker for (BatchGroupT *BG = Region->FreeListInfo.BlockList.front(), 1461*76559068SAndroid Build Coastguard Worker *Prev = nullptr; 1462*76559068SAndroid Build Coastguard Worker BG != nullptr;) { 1463*76559068SAndroid Build Coastguard Worker // Group boundary is always GroupSize-aligned from CompactPtr base. The 1464*76559068SAndroid Build Coastguard Worker // layout of memory groups is like, 1465*76559068SAndroid Build Coastguard Worker // 1466*76559068SAndroid Build Coastguard Worker // (CompactPtrBase) 1467*76559068SAndroid Build Coastguard Worker // #1 CompactPtrGroupBase #2 CompactPtrGroupBase ... 1468*76559068SAndroid Build Coastguard Worker // | | | 1469*76559068SAndroid Build Coastguard Worker // v v v 1470*76559068SAndroid Build Coastguard Worker // +-----------------------+-----------------------+ 1471*76559068SAndroid Build Coastguard Worker // \ / \ / 1472*76559068SAndroid Build Coastguard Worker // --- GroupSize --- --- GroupSize --- 1473*76559068SAndroid Build Coastguard Worker // 1474*76559068SAndroid Build Coastguard Worker // After decompacting the CompactPtrGroupBase, we expect the alignment 1475*76559068SAndroid Build Coastguard Worker // property is held as well. 1476*76559068SAndroid Build Coastguard Worker const uptr BatchGroupBase = 1477*76559068SAndroid Build Coastguard Worker decompactGroupBase(CompactPtrBase, BG->CompactPtrGroupBase); 1478*76559068SAndroid Build Coastguard Worker DCHECK_LE(Region->RegionBeg, BatchGroupBase); 1479*76559068SAndroid Build Coastguard Worker DCHECK_GE(AllocatedUserEnd, BatchGroupBase); 1480*76559068SAndroid Build Coastguard Worker DCHECK_EQ((Region->RegionBeg - BatchGroupBase) % GroupSize, 0U); 1481*76559068SAndroid Build Coastguard Worker // TransferBatches are pushed in front of BG.Batches. The first one may 1482*76559068SAndroid Build Coastguard Worker // not have all caches used. 1483*76559068SAndroid Build Coastguard Worker const uptr NumBlocks = (BG->Batches.size() - 1) * BG->MaxCachedPerBatch + 1484*76559068SAndroid Build Coastguard Worker BG->Batches.front()->getCount(); 1485*76559068SAndroid Build Coastguard Worker const uptr BytesInBG = NumBlocks * BlockSize; 1486*76559068SAndroid Build Coastguard Worker 1487*76559068SAndroid Build Coastguard Worker if (BytesInBG <= BG->BytesInBGAtLastCheckpoint) { 1488*76559068SAndroid Build Coastguard Worker BG->BytesInBGAtLastCheckpoint = BytesInBG; 1489*76559068SAndroid Build Coastguard Worker Prev = BG; 1490*76559068SAndroid Build Coastguard Worker BG = BG->Next; 1491*76559068SAndroid Build Coastguard Worker continue; 1492*76559068SAndroid Build Coastguard Worker } 1493*76559068SAndroid Build Coastguard Worker 1494*76559068SAndroid Build Coastguard Worker const uptr PushedBytesDelta = BytesInBG - BG->BytesInBGAtLastCheckpoint; 1495*76559068SAndroid Build Coastguard Worker if (PushedBytesDelta < getMinReleaseAttemptSize(BlockSize)) { 1496*76559068SAndroid Build Coastguard Worker Prev = BG; 1497*76559068SAndroid Build Coastguard Worker BG = BG->Next; 1498*76559068SAndroid Build Coastguard Worker continue; 1499*76559068SAndroid Build Coastguard Worker } 1500*76559068SAndroid Build Coastguard Worker 1501*76559068SAndroid Build Coastguard Worker // Given the randomness property, we try to release the pages only if the 1502*76559068SAndroid Build Coastguard Worker // bytes used by free blocks exceed certain proportion of group size. Note 1503*76559068SAndroid Build Coastguard Worker // that this heuristic only applies when all the spaces in a BatchGroup 1504*76559068SAndroid Build Coastguard Worker // are allocated. 1505*76559068SAndroid Build Coastguard Worker if (isSmallBlock(BlockSize)) { 1506*76559068SAndroid Build Coastguard Worker const uptr BatchGroupEnd = BatchGroupBase + GroupSize; 1507*76559068SAndroid Build Coastguard Worker const uptr AllocatedGroupSize = AllocatedUserEnd >= BatchGroupEnd 1508*76559068SAndroid Build Coastguard Worker ? GroupSize 1509*76559068SAndroid Build Coastguard Worker : AllocatedUserEnd - BatchGroupBase; 1510*76559068SAndroid Build Coastguard Worker const uptr ReleaseThreshold = 1511*76559068SAndroid Build Coastguard Worker (AllocatedGroupSize * (100 - 1U - BlockSize / 16U)) / 100U; 1512*76559068SAndroid Build Coastguard Worker const bool HighDensity = BytesInBG >= ReleaseThreshold; 1513*76559068SAndroid Build Coastguard Worker const bool MayHaveReleasedAll = NumBlocks >= (GroupSize / BlockSize); 1514*76559068SAndroid Build Coastguard Worker // If all blocks in the group are released, we will do range marking 1515*76559068SAndroid Build Coastguard Worker // which is fast. Otherwise, we will wait until we have accumulated 1516*76559068SAndroid Build Coastguard Worker // a certain amount of free memory. 1517*76559068SAndroid Build Coastguard Worker const bool ReachReleaseDelta = 1518*76559068SAndroid Build Coastguard Worker MayHaveReleasedAll 1519*76559068SAndroid Build Coastguard Worker ? true 1520*76559068SAndroid Build Coastguard Worker : PushedBytesDelta >= PageSize * SmallerBlockReleasePageDelta; 1521*76559068SAndroid Build Coastguard Worker 1522*76559068SAndroid Build Coastguard Worker if (!HighDensity) { 1523*76559068SAndroid Build Coastguard Worker DCHECK_LE(BytesInBG, ReleaseThreshold); 1524*76559068SAndroid Build Coastguard Worker // The following is the usage of a memroy group, 1525*76559068SAndroid Build Coastguard Worker // 1526*76559068SAndroid Build Coastguard Worker // BytesInBG ReleaseThreshold 1527*76559068SAndroid Build Coastguard Worker // / \ v 1528*76559068SAndroid Build Coastguard Worker // +---+---------------------------+-----+ 1529*76559068SAndroid Build Coastguard Worker // | | | | | 1530*76559068SAndroid Build Coastguard Worker // +---+---------------------------+-----+ 1531*76559068SAndroid Build Coastguard Worker // \ / ^ 1532*76559068SAndroid Build Coastguard Worker // PushedBytesDelta GroupEnd 1533*76559068SAndroid Build Coastguard Worker MinDistToThreshold = 1534*76559068SAndroid Build Coastguard Worker Min(MinDistToThreshold, 1535*76559068SAndroid Build Coastguard Worker ReleaseThreshold - BytesInBG + PushedBytesDelta); 1536*76559068SAndroid Build Coastguard Worker } else { 1537*76559068SAndroid Build Coastguard Worker // If it reaches high density at this round, the next time we will try 1538*76559068SAndroid Build Coastguard Worker // to release is based on SmallerBlockReleasePageDelta 1539*76559068SAndroid Build Coastguard Worker MinDistToThreshold = 1540*76559068SAndroid Build Coastguard Worker Min(MinDistToThreshold, PageSize * SmallerBlockReleasePageDelta); 1541*76559068SAndroid Build Coastguard Worker } 1542*76559068SAndroid Build Coastguard Worker 1543*76559068SAndroid Build Coastguard Worker if (!HighDensity || !ReachReleaseDelta) { 1544*76559068SAndroid Build Coastguard Worker Prev = BG; 1545*76559068SAndroid Build Coastguard Worker BG = BG->Next; 1546*76559068SAndroid Build Coastguard Worker continue; 1547*76559068SAndroid Build Coastguard Worker } 1548*76559068SAndroid Build Coastguard Worker } 1549*76559068SAndroid Build Coastguard Worker 1550*76559068SAndroid Build Coastguard Worker // If `BG` is the first BatchGroupT in the list, we only need to advance 1551*76559068SAndroid Build Coastguard Worker // `BG` and call FreeListInfo.BlockList::pop_front(). No update is needed 1552*76559068SAndroid Build Coastguard Worker // for `Prev`. 1553*76559068SAndroid Build Coastguard Worker // 1554*76559068SAndroid Build Coastguard Worker // (BG) (BG->Next) 1555*76559068SAndroid Build Coastguard Worker // Prev Cur BG 1556*76559068SAndroid Build Coastguard Worker // | | | 1557*76559068SAndroid Build Coastguard Worker // v v v 1558*76559068SAndroid Build Coastguard Worker // nil +--+ +--+ 1559*76559068SAndroid Build Coastguard Worker // |X | -> | | -> ... 1560*76559068SAndroid Build Coastguard Worker // +--+ +--+ 1561*76559068SAndroid Build Coastguard Worker // 1562*76559068SAndroid Build Coastguard Worker // Otherwise, `Prev` will be used to extract the `Cur` from the 1563*76559068SAndroid Build Coastguard Worker // `FreeListInfo.BlockList`. 1564*76559068SAndroid Build Coastguard Worker // 1565*76559068SAndroid Build Coastguard Worker // (BG) (BG->Next) 1566*76559068SAndroid Build Coastguard Worker // Prev Cur BG 1567*76559068SAndroid Build Coastguard Worker // | | | 1568*76559068SAndroid Build Coastguard Worker // v v v 1569*76559068SAndroid Build Coastguard Worker // +--+ +--+ +--+ 1570*76559068SAndroid Build Coastguard Worker // | | -> |X | -> | | -> ... 1571*76559068SAndroid Build Coastguard Worker // +--+ +--+ +--+ 1572*76559068SAndroid Build Coastguard Worker // 1573*76559068SAndroid Build Coastguard Worker // After FreeListInfo.BlockList::extract(), 1574*76559068SAndroid Build Coastguard Worker // 1575*76559068SAndroid Build Coastguard Worker // Prev Cur BG 1576*76559068SAndroid Build Coastguard Worker // | | | 1577*76559068SAndroid Build Coastguard Worker // v v v 1578*76559068SAndroid Build Coastguard Worker // +--+ +--+ +--+ 1579*76559068SAndroid Build Coastguard Worker // | |-+ |X | +->| | -> ... 1580*76559068SAndroid Build Coastguard Worker // +--+ | +--+ | +--+ 1581*76559068SAndroid Build Coastguard Worker // +--------+ 1582*76559068SAndroid Build Coastguard Worker // 1583*76559068SAndroid Build Coastguard Worker // Note that we need to advance before pushing this BatchGroup to 1584*76559068SAndroid Build Coastguard Worker // GroupsToRelease because it's a destructive operation. 1585*76559068SAndroid Build Coastguard Worker 1586*76559068SAndroid Build Coastguard Worker BatchGroupT *Cur = BG; 1587*76559068SAndroid Build Coastguard Worker BG = BG->Next; 1588*76559068SAndroid Build Coastguard Worker 1589*76559068SAndroid Build Coastguard Worker // Ideally, we may want to update this only after successful release. 1590*76559068SAndroid Build Coastguard Worker // However, for smaller blocks, each block marking is a costly operation. 1591*76559068SAndroid Build Coastguard Worker // Therefore, we update it earlier. 1592*76559068SAndroid Build Coastguard Worker // TODO: Consider updating this after releasing pages if `ReleaseRecorder` 1593*76559068SAndroid Build Coastguard Worker // can tell the released bytes in each group. 1594*76559068SAndroid Build Coastguard Worker Cur->BytesInBGAtLastCheckpoint = BytesInBG; 1595*76559068SAndroid Build Coastguard Worker 1596*76559068SAndroid Build Coastguard Worker if (Prev != nullptr) 1597*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.extract(Prev, Cur); 1598*76559068SAndroid Build Coastguard Worker else 1599*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.pop_front(); 1600*76559068SAndroid Build Coastguard Worker GroupsToRelease.push_back(Cur); 1601*76559068SAndroid Build Coastguard Worker } 1602*76559068SAndroid Build Coastguard Worker 1603*76559068SAndroid Build Coastguard Worker // Only small blocks have the adaptive `TryReleaseThreshold`. 1604*76559068SAndroid Build Coastguard Worker if (isSmallBlock(BlockSize)) { 1605*76559068SAndroid Build Coastguard Worker // If the MinDistToThreshold is not updated, that means each memory group 1606*76559068SAndroid Build Coastguard Worker // may have only pushed less than a page size. In that case, just set it 1607*76559068SAndroid Build Coastguard Worker // back to normal. 1608*76559068SAndroid Build Coastguard Worker if (MinDistToThreshold == GroupSize) 1609*76559068SAndroid Build Coastguard Worker MinDistToThreshold = PageSize * SmallerBlockReleasePageDelta; 1610*76559068SAndroid Build Coastguard Worker Region->ReleaseInfo.TryReleaseThreshold = MinDistToThreshold; 1611*76559068SAndroid Build Coastguard Worker } 1612*76559068SAndroid Build Coastguard Worker 1613*76559068SAndroid Build Coastguard Worker return GroupsToRelease; 1614*76559068SAndroid Build Coastguard Worker } 1615*76559068SAndroid Build Coastguard Worker 1616*76559068SAndroid Build Coastguard Worker PageReleaseContext markFreeBlocks(RegionInfo * Region,const uptr BlockSize,const uptr AllocatedUserEnd,const uptr CompactPtrBase,SinglyLinkedList<BatchGroupT> & GroupsToRelease)1617*76559068SAndroid Build Coastguard Worker markFreeBlocks(RegionInfo *Region, const uptr BlockSize, 1618*76559068SAndroid Build Coastguard Worker const uptr AllocatedUserEnd, const uptr CompactPtrBase, 1619*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> &GroupsToRelease) 1620*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1621*76559068SAndroid Build Coastguard Worker const uptr GroupSize = (1UL << GroupSizeLog); 1622*76559068SAndroid Build Coastguard Worker auto DecompactPtr = [CompactPtrBase](CompactPtrT CompactPtr) { 1623*76559068SAndroid Build Coastguard Worker return decompactPtrInternal(CompactPtrBase, CompactPtr); 1624*76559068SAndroid Build Coastguard Worker }; 1625*76559068SAndroid Build Coastguard Worker 1626*76559068SAndroid Build Coastguard Worker const uptr ReleaseBase = decompactGroupBase( 1627*76559068SAndroid Build Coastguard Worker CompactPtrBase, GroupsToRelease.front()->CompactPtrGroupBase); 1628*76559068SAndroid Build Coastguard Worker const uptr LastGroupEnd = 1629*76559068SAndroid Build Coastguard Worker Min(decompactGroupBase(CompactPtrBase, 1630*76559068SAndroid Build Coastguard Worker GroupsToRelease.back()->CompactPtrGroupBase) + 1631*76559068SAndroid Build Coastguard Worker GroupSize, 1632*76559068SAndroid Build Coastguard Worker AllocatedUserEnd); 1633*76559068SAndroid Build Coastguard Worker // The last block may straddle the group boundary. Rounding up to BlockSize 1634*76559068SAndroid Build Coastguard Worker // to get the exact range. 1635*76559068SAndroid Build Coastguard Worker const uptr ReleaseEnd = 1636*76559068SAndroid Build Coastguard Worker roundUpSlow(LastGroupEnd - Region->RegionBeg, BlockSize) + 1637*76559068SAndroid Build Coastguard Worker Region->RegionBeg; 1638*76559068SAndroid Build Coastguard Worker const uptr ReleaseRangeSize = ReleaseEnd - ReleaseBase; 1639*76559068SAndroid Build Coastguard Worker const uptr ReleaseOffset = ReleaseBase - Region->RegionBeg; 1640*76559068SAndroid Build Coastguard Worker 1641*76559068SAndroid Build Coastguard Worker PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U, 1642*76559068SAndroid Build Coastguard Worker ReleaseRangeSize, ReleaseOffset); 1643*76559068SAndroid Build Coastguard Worker // We may not be able to do the page release in a rare case that we may 1644*76559068SAndroid Build Coastguard Worker // fail on PageMap allocation. 1645*76559068SAndroid Build Coastguard Worker if (UNLIKELY(!Context.ensurePageMapAllocated())) 1646*76559068SAndroid Build Coastguard Worker return Context; 1647*76559068SAndroid Build Coastguard Worker 1648*76559068SAndroid Build Coastguard Worker for (BatchGroupT &BG : GroupsToRelease) { 1649*76559068SAndroid Build Coastguard Worker const uptr BatchGroupBase = 1650*76559068SAndroid Build Coastguard Worker decompactGroupBase(CompactPtrBase, BG.CompactPtrGroupBase); 1651*76559068SAndroid Build Coastguard Worker const uptr BatchGroupEnd = BatchGroupBase + GroupSize; 1652*76559068SAndroid Build Coastguard Worker const uptr AllocatedGroupSize = AllocatedUserEnd >= BatchGroupEnd 1653*76559068SAndroid Build Coastguard Worker ? GroupSize 1654*76559068SAndroid Build Coastguard Worker : AllocatedUserEnd - BatchGroupBase; 1655*76559068SAndroid Build Coastguard Worker const uptr BatchGroupUsedEnd = BatchGroupBase + AllocatedGroupSize; 1656*76559068SAndroid Build Coastguard Worker const bool MayContainLastBlockInRegion = 1657*76559068SAndroid Build Coastguard Worker BatchGroupUsedEnd == AllocatedUserEnd; 1658*76559068SAndroid Build Coastguard Worker const bool BlockAlignedWithUsedEnd = 1659*76559068SAndroid Build Coastguard Worker (BatchGroupUsedEnd - Region->RegionBeg) % BlockSize == 0; 1660*76559068SAndroid Build Coastguard Worker 1661*76559068SAndroid Build Coastguard Worker uptr MaxContainedBlocks = AllocatedGroupSize / BlockSize; 1662*76559068SAndroid Build Coastguard Worker if (!BlockAlignedWithUsedEnd) 1663*76559068SAndroid Build Coastguard Worker ++MaxContainedBlocks; 1664*76559068SAndroid Build Coastguard Worker 1665*76559068SAndroid Build Coastguard Worker const uptr NumBlocks = (BG.Batches.size() - 1) * BG.MaxCachedPerBatch + 1666*76559068SAndroid Build Coastguard Worker BG.Batches.front()->getCount(); 1667*76559068SAndroid Build Coastguard Worker 1668*76559068SAndroid Build Coastguard Worker if (NumBlocks == MaxContainedBlocks) { 1669*76559068SAndroid Build Coastguard Worker for (const auto &It : BG.Batches) { 1670*76559068SAndroid Build Coastguard Worker if (&It != BG.Batches.front()) 1671*76559068SAndroid Build Coastguard Worker DCHECK_EQ(It.getCount(), BG.MaxCachedPerBatch); 1672*76559068SAndroid Build Coastguard Worker for (u16 I = 0; I < It.getCount(); ++I) 1673*76559068SAndroid Build Coastguard Worker DCHECK_EQ(compactPtrGroup(It.get(I)), BG.CompactPtrGroupBase); 1674*76559068SAndroid Build Coastguard Worker } 1675*76559068SAndroid Build Coastguard Worker 1676*76559068SAndroid Build Coastguard Worker Context.markRangeAsAllCounted(BatchGroupBase, BatchGroupUsedEnd, 1677*76559068SAndroid Build Coastguard Worker Region->RegionBeg, /*RegionIndex=*/0, 1678*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser); 1679*76559068SAndroid Build Coastguard Worker } else { 1680*76559068SAndroid Build Coastguard Worker DCHECK_LT(NumBlocks, MaxContainedBlocks); 1681*76559068SAndroid Build Coastguard Worker // Note that we don't always visit blocks in each BatchGroup so that we 1682*76559068SAndroid Build Coastguard Worker // may miss the chance of releasing certain pages that cross 1683*76559068SAndroid Build Coastguard Worker // BatchGroups. 1684*76559068SAndroid Build Coastguard Worker Context.markFreeBlocksInRegion( 1685*76559068SAndroid Build Coastguard Worker BG.Batches, DecompactPtr, Region->RegionBeg, /*RegionIndex=*/0, 1686*76559068SAndroid Build Coastguard Worker Region->MemMapInfo.AllocatedUser, MayContainLastBlockInRegion); 1687*76559068SAndroid Build Coastguard Worker } 1688*76559068SAndroid Build Coastguard Worker } 1689*76559068SAndroid Build Coastguard Worker 1690*76559068SAndroid Build Coastguard Worker DCHECK(Context.hasBlockMarked()); 1691*76559068SAndroid Build Coastguard Worker 1692*76559068SAndroid Build Coastguard Worker return Context; 1693*76559068SAndroid Build Coastguard Worker } 1694*76559068SAndroid Build Coastguard Worker mergeGroupsToReleaseBack(RegionInfo * Region,SinglyLinkedList<BatchGroupT> & GroupsToRelease)1695*76559068SAndroid Build Coastguard Worker void mergeGroupsToReleaseBack(RegionInfo *Region, 1696*76559068SAndroid Build Coastguard Worker SinglyLinkedList<BatchGroupT> &GroupsToRelease) 1697*76559068SAndroid Build Coastguard Worker REQUIRES(Region->MMLock) EXCLUDES(Region->FLLock) { 1698*76559068SAndroid Build Coastguard Worker ScopedLock L(Region->FLLock); 1699*76559068SAndroid Build Coastguard Worker 1700*76559068SAndroid Build Coastguard Worker // After merging two freelists, we may have redundant `BatchGroup`s that 1701*76559068SAndroid Build Coastguard Worker // need to be recycled. The number of unused `BatchGroup`s is expected to be 1702*76559068SAndroid Build Coastguard Worker // small. Pick a constant which is inferred from real programs. 1703*76559068SAndroid Build Coastguard Worker constexpr uptr MaxUnusedSize = 8; 1704*76559068SAndroid Build Coastguard Worker CompactPtrT Blocks[MaxUnusedSize]; 1705*76559068SAndroid Build Coastguard Worker u32 Idx = 0; 1706*76559068SAndroid Build Coastguard Worker RegionInfo *BatchClassRegion = getRegionInfo(SizeClassMap::BatchClassId); 1707*76559068SAndroid Build Coastguard Worker // We can't call pushBatchClassBlocks() to recycle the unused `BatchGroup`s 1708*76559068SAndroid Build Coastguard Worker // when we are manipulating the freelist of `BatchClassRegion`. Instead, we 1709*76559068SAndroid Build Coastguard Worker // should just push it back to the freelist when we merge two `BatchGroup`s. 1710*76559068SAndroid Build Coastguard Worker // This logic hasn't been implemented because we haven't supported releasing 1711*76559068SAndroid Build Coastguard Worker // pages in `BatchClassRegion`. 1712*76559068SAndroid Build Coastguard Worker DCHECK_NE(BatchClassRegion, Region); 1713*76559068SAndroid Build Coastguard Worker 1714*76559068SAndroid Build Coastguard Worker // Merge GroupsToRelease back to the Region::FreeListInfo.BlockList. Note 1715*76559068SAndroid Build Coastguard Worker // that both `Region->FreeListInfo.BlockList` and `GroupsToRelease` are 1716*76559068SAndroid Build Coastguard Worker // sorted. 1717*76559068SAndroid Build Coastguard Worker for (BatchGroupT *BG = Region->FreeListInfo.BlockList.front(), 1718*76559068SAndroid Build Coastguard Worker *Prev = nullptr; 1719*76559068SAndroid Build Coastguard Worker ;) { 1720*76559068SAndroid Build Coastguard Worker if (BG == nullptr || GroupsToRelease.empty()) { 1721*76559068SAndroid Build Coastguard Worker if (!GroupsToRelease.empty()) 1722*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.append_back(&GroupsToRelease); 1723*76559068SAndroid Build Coastguard Worker break; 1724*76559068SAndroid Build Coastguard Worker } 1725*76559068SAndroid Build Coastguard Worker 1726*76559068SAndroid Build Coastguard Worker DCHECK(!BG->Batches.empty()); 1727*76559068SAndroid Build Coastguard Worker 1728*76559068SAndroid Build Coastguard Worker if (BG->CompactPtrGroupBase < 1729*76559068SAndroid Build Coastguard Worker GroupsToRelease.front()->CompactPtrGroupBase) { 1730*76559068SAndroid Build Coastguard Worker Prev = BG; 1731*76559068SAndroid Build Coastguard Worker BG = BG->Next; 1732*76559068SAndroid Build Coastguard Worker continue; 1733*76559068SAndroid Build Coastguard Worker } 1734*76559068SAndroid Build Coastguard Worker 1735*76559068SAndroid Build Coastguard Worker BatchGroupT *Cur = GroupsToRelease.front(); 1736*76559068SAndroid Build Coastguard Worker TransferBatchT *UnusedTransferBatch = nullptr; 1737*76559068SAndroid Build Coastguard Worker GroupsToRelease.pop_front(); 1738*76559068SAndroid Build Coastguard Worker 1739*76559068SAndroid Build Coastguard Worker if (BG->CompactPtrGroupBase == Cur->CompactPtrGroupBase) { 1740*76559068SAndroid Build Coastguard Worker // We have updated `BatchGroup::BytesInBGAtLastCheckpoint` while 1741*76559068SAndroid Build Coastguard Worker // collecting the `GroupsToRelease`. 1742*76559068SAndroid Build Coastguard Worker BG->BytesInBGAtLastCheckpoint = Cur->BytesInBGAtLastCheckpoint; 1743*76559068SAndroid Build Coastguard Worker const uptr MaxCachedPerBatch = BG->MaxCachedPerBatch; 1744*76559068SAndroid Build Coastguard Worker 1745*76559068SAndroid Build Coastguard Worker // Note that the first TransferBatches in both `Batches` may not be 1746*76559068SAndroid Build Coastguard Worker // full and only the first TransferBatch can have non-full blocks. Thus 1747*76559068SAndroid Build Coastguard Worker // we have to merge them before appending one to another. 1748*76559068SAndroid Build Coastguard Worker if (Cur->Batches.front()->getCount() == MaxCachedPerBatch) { 1749*76559068SAndroid Build Coastguard Worker BG->Batches.append_back(&Cur->Batches); 1750*76559068SAndroid Build Coastguard Worker } else { 1751*76559068SAndroid Build Coastguard Worker TransferBatchT *NonFullBatch = Cur->Batches.front(); 1752*76559068SAndroid Build Coastguard Worker Cur->Batches.pop_front(); 1753*76559068SAndroid Build Coastguard Worker const u16 NonFullBatchCount = NonFullBatch->getCount(); 1754*76559068SAndroid Build Coastguard Worker // The remaining Batches in `Cur` are full. 1755*76559068SAndroid Build Coastguard Worker BG->Batches.append_back(&Cur->Batches); 1756*76559068SAndroid Build Coastguard Worker 1757*76559068SAndroid Build Coastguard Worker if (BG->Batches.front()->getCount() == MaxCachedPerBatch) { 1758*76559068SAndroid Build Coastguard Worker // Only 1 non-full TransferBatch, push it to the front. 1759*76559068SAndroid Build Coastguard Worker BG->Batches.push_front(NonFullBatch); 1760*76559068SAndroid Build Coastguard Worker } else { 1761*76559068SAndroid Build Coastguard Worker const u16 NumBlocksToMove = static_cast<u16>( 1762*76559068SAndroid Build Coastguard Worker Min(static_cast<u16>(MaxCachedPerBatch - 1763*76559068SAndroid Build Coastguard Worker BG->Batches.front()->getCount()), 1764*76559068SAndroid Build Coastguard Worker NonFullBatchCount)); 1765*76559068SAndroid Build Coastguard Worker BG->Batches.front()->appendFromTransferBatch(NonFullBatch, 1766*76559068SAndroid Build Coastguard Worker NumBlocksToMove); 1767*76559068SAndroid Build Coastguard Worker if (NonFullBatch->isEmpty()) 1768*76559068SAndroid Build Coastguard Worker UnusedTransferBatch = NonFullBatch; 1769*76559068SAndroid Build Coastguard Worker else 1770*76559068SAndroid Build Coastguard Worker BG->Batches.push_front(NonFullBatch); 1771*76559068SAndroid Build Coastguard Worker } 1772*76559068SAndroid Build Coastguard Worker } 1773*76559068SAndroid Build Coastguard Worker 1774*76559068SAndroid Build Coastguard Worker const u32 NeededSlots = UnusedTransferBatch == nullptr ? 1U : 2U; 1775*76559068SAndroid Build Coastguard Worker if (UNLIKELY(Idx + NeededSlots > MaxUnusedSize)) { 1776*76559068SAndroid Build Coastguard Worker ScopedLock L(BatchClassRegion->FLLock); 1777*76559068SAndroid Build Coastguard Worker pushBatchClassBlocks(BatchClassRegion, Blocks, Idx); 1778*76559068SAndroid Build Coastguard Worker if (conditionVariableEnabled()) 1779*76559068SAndroid Build Coastguard Worker BatchClassRegion->FLLockCV.notifyAll(BatchClassRegion->FLLock); 1780*76559068SAndroid Build Coastguard Worker Idx = 0; 1781*76559068SAndroid Build Coastguard Worker } 1782*76559068SAndroid Build Coastguard Worker Blocks[Idx++] = 1783*76559068SAndroid Build Coastguard Worker compactPtr(SizeClassMap::BatchClassId, reinterpret_cast<uptr>(Cur)); 1784*76559068SAndroid Build Coastguard Worker if (UnusedTransferBatch) { 1785*76559068SAndroid Build Coastguard Worker Blocks[Idx++] = 1786*76559068SAndroid Build Coastguard Worker compactPtr(SizeClassMap::BatchClassId, 1787*76559068SAndroid Build Coastguard Worker reinterpret_cast<uptr>(UnusedTransferBatch)); 1788*76559068SAndroid Build Coastguard Worker } 1789*76559068SAndroid Build Coastguard Worker Prev = BG; 1790*76559068SAndroid Build Coastguard Worker BG = BG->Next; 1791*76559068SAndroid Build Coastguard Worker continue; 1792*76559068SAndroid Build Coastguard Worker } 1793*76559068SAndroid Build Coastguard Worker 1794*76559068SAndroid Build Coastguard Worker // At here, the `BG` is the first BatchGroup with CompactPtrGroupBase 1795*76559068SAndroid Build Coastguard Worker // larger than the first element in `GroupsToRelease`. We need to insert 1796*76559068SAndroid Build Coastguard Worker // `GroupsToRelease::front()` (which is `Cur` below) before `BG`. 1797*76559068SAndroid Build Coastguard Worker // 1798*76559068SAndroid Build Coastguard Worker // 1. If `Prev` is nullptr, we simply push `Cur` to the front of 1799*76559068SAndroid Build Coastguard Worker // FreeListInfo.BlockList. 1800*76559068SAndroid Build Coastguard Worker // 2. Otherwise, use `insert()` which inserts an element next to `Prev`. 1801*76559068SAndroid Build Coastguard Worker // 1802*76559068SAndroid Build Coastguard Worker // Afterwards, we don't need to advance `BG` because the order between 1803*76559068SAndroid Build Coastguard Worker // `BG` and the new `GroupsToRelease::front()` hasn't been checked. 1804*76559068SAndroid Build Coastguard Worker if (Prev == nullptr) 1805*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.push_front(Cur); 1806*76559068SAndroid Build Coastguard Worker else 1807*76559068SAndroid Build Coastguard Worker Region->FreeListInfo.BlockList.insert(Prev, Cur); 1808*76559068SAndroid Build Coastguard Worker DCHECK_EQ(Cur->Next, BG); 1809*76559068SAndroid Build Coastguard Worker Prev = Cur; 1810*76559068SAndroid Build Coastguard Worker } 1811*76559068SAndroid Build Coastguard Worker 1812*76559068SAndroid Build Coastguard Worker if (Idx != 0) { 1813*76559068SAndroid Build Coastguard Worker ScopedLock L(BatchClassRegion->FLLock); 1814*76559068SAndroid Build Coastguard Worker pushBatchClassBlocks(BatchClassRegion, Blocks, Idx); 1815*76559068SAndroid Build Coastguard Worker if (conditionVariableEnabled()) 1816*76559068SAndroid Build Coastguard Worker BatchClassRegion->FLLockCV.notifyAll(BatchClassRegion->FLLock); 1817*76559068SAndroid Build Coastguard Worker } 1818*76559068SAndroid Build Coastguard Worker 1819*76559068SAndroid Build Coastguard Worker if (SCUDO_DEBUG) { 1820*76559068SAndroid Build Coastguard Worker BatchGroupT *Prev = Region->FreeListInfo.BlockList.front(); 1821*76559068SAndroid Build Coastguard Worker for (BatchGroupT *Cur = Prev->Next; Cur != nullptr; 1822*76559068SAndroid Build Coastguard Worker Prev = Cur, Cur = Cur->Next) { 1823*76559068SAndroid Build Coastguard Worker CHECK_LT(Prev->CompactPtrGroupBase, Cur->CompactPtrGroupBase); 1824*76559068SAndroid Build Coastguard Worker } 1825*76559068SAndroid Build Coastguard Worker } 1826*76559068SAndroid Build Coastguard Worker 1827*76559068SAndroid Build Coastguard Worker if (conditionVariableEnabled()) 1828*76559068SAndroid Build Coastguard Worker Region->FLLockCV.notifyAll(Region->FLLock); 1829*76559068SAndroid Build Coastguard Worker } 1830*76559068SAndroid Build Coastguard Worker 1831*76559068SAndroid Build Coastguard Worker // The minimum size of pushed blocks that we will try to release the pages in 1832*76559068SAndroid Build Coastguard Worker // that size class. 1833*76559068SAndroid Build Coastguard Worker uptr SmallerBlockReleasePageDelta = 0; 1834*76559068SAndroid Build Coastguard Worker atomic_s32 ReleaseToOsIntervalMs = {}; 1835*76559068SAndroid Build Coastguard Worker alignas(SCUDO_CACHE_LINE_SIZE) RegionInfo RegionInfoArray[NumClasses]; 1836*76559068SAndroid Build Coastguard Worker }; 1837*76559068SAndroid Build Coastguard Worker 1838*76559068SAndroid Build Coastguard Worker } // namespace scudo 1839*76559068SAndroid Build Coastguard Worker 1840*76559068SAndroid Build Coastguard Worker #endif // SCUDO_PRIMARY64_H_ 1841