1 //===-- release.h -----------------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #ifndef SCUDO_RELEASE_H_
10 #define SCUDO_RELEASE_H_
11
12 #include "common.h"
13 #include "list.h"
14 #include "mem_map.h"
15 #include "mutex.h"
16 #include "thread_annotations.h"
17
18 namespace scudo {
19
20 template <typename MemMapT> class RegionReleaseRecorder {
21 public:
22 RegionReleaseRecorder(MemMapT *RegionMemMap, uptr Base, uptr Offset = 0)
RegionMemMap(RegionMemMap)23 : RegionMemMap(RegionMemMap), Base(Base), Offset(Offset) {}
24
getReleasedRangesCount()25 uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
26
getReleasedBytes()27 uptr getReleasedBytes() const { return ReleasedBytes; }
28
getBase()29 uptr getBase() const { return Base; }
30
31 // Releases [From, To) range of pages back to OS. Note that `From` and `To`
32 // are offseted from `Base` + Offset.
releasePageRangeToOS(uptr From,uptr To)33 void releasePageRangeToOS(uptr From, uptr To) {
34 const uptr Size = To - From;
35 RegionMemMap->releasePagesToOS(getBase() + Offset + From, Size);
36 ReleasedRangesCount++;
37 ReleasedBytes += Size;
38 }
39
40 private:
41 uptr ReleasedRangesCount = 0;
42 uptr ReleasedBytes = 0;
43 MemMapT *RegionMemMap = nullptr;
44 uptr Base = 0;
45 // The release offset from Base. This is used when we know a given range after
46 // Base will not be released.
47 uptr Offset = 0;
48 };
49
50 class ReleaseRecorder {
51 public:
52 ReleaseRecorder(uptr Base, uptr Offset = 0, MapPlatformData *Data = nullptr)
Base(Base)53 : Base(Base), Offset(Offset), Data(Data) {}
54
getReleasedRangesCount()55 uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
56
getReleasedBytes()57 uptr getReleasedBytes() const { return ReleasedBytes; }
58
getBase()59 uptr getBase() const { return Base; }
60
61 // Releases [From, To) range of pages back to OS.
releasePageRangeToOS(uptr From,uptr To)62 void releasePageRangeToOS(uptr From, uptr To) {
63 const uptr Size = To - From;
64 releasePagesToOS(Base, From + Offset, Size, Data);
65 ReleasedRangesCount++;
66 ReleasedBytes += Size;
67 }
68
69 private:
70 uptr ReleasedRangesCount = 0;
71 uptr ReleasedBytes = 0;
72 // The starting address to release. Note that we may want to combine (Base +
73 // Offset) as a new Base. However, the Base is retrieved from
74 // `MapPlatformData` on Fuchsia, which means the offset won't be aware.
75 // Therefore, store them separately to make it work on all the platforms.
76 uptr Base = 0;
77 // The release offset from Base. This is used when we know a given range after
78 // Base will not be released.
79 uptr Offset = 0;
80 MapPlatformData *Data = nullptr;
81 };
82
83 class FragmentationRecorder {
84 public:
85 FragmentationRecorder() = default;
86
getReleasedPagesCount()87 uptr getReleasedPagesCount() const { return ReleasedPagesCount; }
88
releasePageRangeToOS(uptr From,uptr To)89 void releasePageRangeToOS(uptr From, uptr To) {
90 DCHECK_EQ((To - From) % getPageSizeCached(), 0U);
91 ReleasedPagesCount += (To - From) >> getPageSizeLogCached();
92 }
93
94 private:
95 uptr ReleasedPagesCount = 0;
96 };
97
98 template <uptr GroupSize, uptr NumGroups>
99 class MemoryGroupFragmentationRecorder {
100 public:
101 const uptr NumPagesInOneGroup = GroupSize / getPageSizeCached();
102
releasePageRangeToOS(uptr From,uptr To)103 void releasePageRangeToOS(uptr From, uptr To) {
104 for (uptr I = From / getPageSizeCached(); I < To / getPageSizeCached(); ++I)
105 ++FreePagesCount[I / NumPagesInOneGroup];
106 }
107
getNumFreePages(uptr GroupId)108 uptr getNumFreePages(uptr GroupId) { return FreePagesCount[GroupId]; }
109
110 private:
111 uptr FreePagesCount[NumGroups] = {};
112 };
113
114 // A buffer pool which holds a fixed number of static buffers of `uptr` elements
115 // for fast buffer allocation. If the request size is greater than
116 // `StaticBufferNumElements` or if all the static buffers are in use, it'll
117 // delegate the allocation to map().
118 template <uptr StaticBufferCount, uptr StaticBufferNumElements>
119 class BufferPool {
120 public:
121 // Preserve 1 bit in the `Mask` so that we don't need to do zero-check while
122 // extracting the least significant bit from the `Mask`.
123 static_assert(StaticBufferCount < SCUDO_WORDSIZE, "");
124 static_assert(isAligned(StaticBufferNumElements * sizeof(uptr),
125 SCUDO_CACHE_LINE_SIZE),
126 "");
127
128 struct Buffer {
129 // Pointer to the buffer's memory, or nullptr if no buffer was allocated.
130 uptr *Data = nullptr;
131
132 // The index of the underlying static buffer, or StaticBufferCount if this
133 // buffer was dynamically allocated. This value is initially set to a poison
134 // value to aid debugging.
135 uptr BufferIndex = ~static_cast<uptr>(0);
136
137 // Only valid if BufferIndex == StaticBufferCount.
138 MemMapT MemMap = {};
139 };
140
141 // Return a zero-initialized buffer which can contain at least the given
142 // number of elements, or nullptr on failure.
getBuffer(const uptr NumElements)143 Buffer getBuffer(const uptr NumElements) {
144 if (UNLIKELY(NumElements > StaticBufferNumElements))
145 return getDynamicBuffer(NumElements);
146
147 uptr index;
148 {
149 // TODO: In general, we expect this operation should be fast so the
150 // waiting thread won't be put into sleep. The HybridMutex does implement
151 // the busy-waiting but we may want to review the performance and see if
152 // we need an explict spin lock here.
153 ScopedLock L(Mutex);
154 index = getLeastSignificantSetBitIndex(Mask);
155 if (index < StaticBufferCount)
156 Mask ^= static_cast<uptr>(1) << index;
157 }
158
159 if (index >= StaticBufferCount)
160 return getDynamicBuffer(NumElements);
161
162 Buffer Buf;
163 Buf.Data = &RawBuffer[index * StaticBufferNumElements];
164 Buf.BufferIndex = index;
165 memset(Buf.Data, 0, StaticBufferNumElements * sizeof(uptr));
166 return Buf;
167 }
168
releaseBuffer(Buffer Buf)169 void releaseBuffer(Buffer Buf) {
170 DCHECK_NE(Buf.Data, nullptr);
171 DCHECK_LE(Buf.BufferIndex, StaticBufferCount);
172 if (Buf.BufferIndex != StaticBufferCount) {
173 ScopedLock L(Mutex);
174 DCHECK_EQ((Mask & (static_cast<uptr>(1) << Buf.BufferIndex)), 0U);
175 Mask |= static_cast<uptr>(1) << Buf.BufferIndex;
176 } else {
177 Buf.MemMap.unmap();
178 }
179 }
180
isStaticBufferTestOnly(const Buffer & Buf)181 bool isStaticBufferTestOnly(const Buffer &Buf) {
182 DCHECK_NE(Buf.Data, nullptr);
183 DCHECK_LE(Buf.BufferIndex, StaticBufferCount);
184 return Buf.BufferIndex != StaticBufferCount;
185 }
186
187 private:
getDynamicBuffer(const uptr NumElements)188 Buffer getDynamicBuffer(const uptr NumElements) {
189 // When using a heap-based buffer, precommit the pages backing the
190 // Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization
191 // where page fault exceptions are skipped as the allocated memory
192 // is accessed. So far, this is only enabled on Fuchsia. It hasn't proven a
193 // performance benefit on other platforms.
194 const uptr MmapFlags = MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0);
195 const uptr MappedSize =
196 roundUp(NumElements * sizeof(uptr), getPageSizeCached());
197 Buffer Buf;
198 if (Buf.MemMap.map(/*Addr=*/0, MappedSize, "scudo:counters", MmapFlags)) {
199 Buf.Data = reinterpret_cast<uptr *>(Buf.MemMap.getBase());
200 Buf.BufferIndex = StaticBufferCount;
201 }
202 return Buf;
203 }
204
205 HybridMutex Mutex;
206 // '1' means that buffer index is not used. '0' means the buffer is in use.
207 uptr Mask GUARDED_BY(Mutex) = ~static_cast<uptr>(0);
208 uptr RawBuffer[StaticBufferCount * StaticBufferNumElements] GUARDED_BY(Mutex);
209 };
210
211 // A Region page map is used to record the usage of pages in the regions. It
212 // implements a packed array of Counters. Each counter occupies 2^N bits, enough
213 // to store counter's MaxValue. Ctor will try to use a static buffer first, and
214 // if that fails (the buffer is too small or already locked), will allocate the
215 // required Buffer via map(). The caller is expected to check whether the
216 // initialization was successful by checking isAllocated() result. For
217 // performance sake, none of the accessors check the validity of the arguments,
218 // It is assumed that Index is always in [0, N) range and the value is not
219 // incremented past MaxValue.
220 class RegionPageMap {
221 public:
RegionPageMap()222 RegionPageMap()
223 : Regions(0), NumCounters(0), CounterSizeBitsLog(0), CounterMask(0),
224 PackingRatioLog(0), BitOffsetMask(0), SizePerRegion(0),
225 BufferNumElements(0) {}
RegionPageMap(uptr NumberOfRegions,uptr CountersPerRegion,uptr MaxValue)226 RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) {
227 reset(NumberOfRegions, CountersPerRegion, MaxValue);
228 }
~RegionPageMap()229 ~RegionPageMap() {
230 if (!isAllocated())
231 return;
232 Buffers.releaseBuffer(Buffer);
233 Buffer = {};
234 }
235
236 // Lock of `StaticBuffer` is acquired conditionally and there's no easy way to
237 // specify the thread-safety attribute properly in current code structure.
238 // Besides, it's the only place we may want to check thread safety. Therefore,
239 // it's fine to bypass the thread-safety analysis now.
reset(uptr NumberOfRegion,uptr CountersPerRegion,uptr MaxValue)240 void reset(uptr NumberOfRegion, uptr CountersPerRegion, uptr MaxValue) {
241 DCHECK_GT(NumberOfRegion, 0);
242 DCHECK_GT(CountersPerRegion, 0);
243 DCHECK_GT(MaxValue, 0);
244
245 Regions = NumberOfRegion;
246 NumCounters = CountersPerRegion;
247
248 constexpr uptr MaxCounterBits = sizeof(*Buffer.Data) * 8UL;
249 // Rounding counter storage size up to the power of two allows for using
250 // bit shifts calculating particular counter's Index and offset.
251 const uptr CounterSizeBits =
252 roundUpPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1);
253 DCHECK_LE(CounterSizeBits, MaxCounterBits);
254 CounterSizeBitsLog = getLog2(CounterSizeBits);
255 CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits);
256
257 const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog;
258 DCHECK_GT(PackingRatio, 0);
259 PackingRatioLog = getLog2(PackingRatio);
260 BitOffsetMask = PackingRatio - 1;
261
262 SizePerRegion =
263 roundUp(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >>
264 PackingRatioLog;
265 BufferNumElements = SizePerRegion * Regions;
266 Buffer = Buffers.getBuffer(BufferNumElements);
267 }
268
isAllocated()269 bool isAllocated() const { return Buffer.Data != nullptr; }
270
getCount()271 uptr getCount() const { return NumCounters; }
272
get(uptr Region,uptr I)273 uptr get(uptr Region, uptr I) const {
274 DCHECK_LT(Region, Regions);
275 DCHECK_LT(I, NumCounters);
276 const uptr Index = I >> PackingRatioLog;
277 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
278 return (Buffer.Data[Region * SizePerRegion + Index] >> BitOffset) &
279 CounterMask;
280 }
281
inc(uptr Region,uptr I)282 void inc(uptr Region, uptr I) const {
283 DCHECK_LT(get(Region, I), CounterMask);
284 const uptr Index = I >> PackingRatioLog;
285 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
286 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
287 DCHECK_EQ(isAllCounted(Region, I), false);
288 Buffer.Data[Region * SizePerRegion + Index] += static_cast<uptr>(1U)
289 << BitOffset;
290 }
291
incN(uptr Region,uptr I,uptr N)292 void incN(uptr Region, uptr I, uptr N) const {
293 DCHECK_GT(N, 0U);
294 DCHECK_LE(N, CounterMask);
295 DCHECK_LE(get(Region, I), CounterMask - N);
296 const uptr Index = I >> PackingRatioLog;
297 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
298 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
299 DCHECK_EQ(isAllCounted(Region, I), false);
300 Buffer.Data[Region * SizePerRegion + Index] += N << BitOffset;
301 }
302
incRange(uptr Region,uptr From,uptr To)303 void incRange(uptr Region, uptr From, uptr To) const {
304 DCHECK_LE(From, To);
305 const uptr Top = Min(To + 1, NumCounters);
306 for (uptr I = From; I < Top; I++)
307 inc(Region, I);
308 }
309
310 // Set the counter to the max value. Note that the max number of blocks in a
311 // page may vary. To provide an easier way to tell if all the blocks are
312 // counted for different pages, set to the same max value to denote the
313 // all-counted status.
setAsAllCounted(uptr Region,uptr I)314 void setAsAllCounted(uptr Region, uptr I) const {
315 DCHECK_LE(get(Region, I), CounterMask);
316 const uptr Index = I >> PackingRatioLog;
317 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
318 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
319 Buffer.Data[Region * SizePerRegion + Index] |= CounterMask << BitOffset;
320 }
setAsAllCountedRange(uptr Region,uptr From,uptr To)321 void setAsAllCountedRange(uptr Region, uptr From, uptr To) const {
322 DCHECK_LE(From, To);
323 const uptr Top = Min(To + 1, NumCounters);
324 for (uptr I = From; I < Top; I++)
325 setAsAllCounted(Region, I);
326 }
327
updateAsAllCountedIf(uptr Region,uptr I,uptr MaxCount)328 bool updateAsAllCountedIf(uptr Region, uptr I, uptr MaxCount) {
329 const uptr Count = get(Region, I);
330 if (Count == CounterMask)
331 return true;
332 if (Count == MaxCount) {
333 setAsAllCounted(Region, I);
334 return true;
335 }
336 return false;
337 }
isAllCounted(uptr Region,uptr I)338 bool isAllCounted(uptr Region, uptr I) const {
339 return get(Region, I) == CounterMask;
340 }
341
getBufferNumElements()342 uptr getBufferNumElements() const { return BufferNumElements; }
343
344 private:
345 // We may consider making this configurable if there are cases which may
346 // benefit from this.
347 static const uptr StaticBufferCount = 2U;
348 static const uptr StaticBufferNumElements = 512U;
349 using BufferPoolT = BufferPool<StaticBufferCount, StaticBufferNumElements>;
350 static BufferPoolT Buffers;
351
352 uptr Regions;
353 uptr NumCounters;
354 uptr CounterSizeBitsLog;
355 uptr CounterMask;
356 uptr PackingRatioLog;
357 uptr BitOffsetMask;
358
359 uptr SizePerRegion;
360 uptr BufferNumElements;
361 BufferPoolT::Buffer Buffer;
362 };
363
364 template <class ReleaseRecorderT> class FreePagesRangeTracker {
365 public:
FreePagesRangeTracker(ReleaseRecorderT & Recorder)366 explicit FreePagesRangeTracker(ReleaseRecorderT &Recorder)
367 : Recorder(Recorder) {}
368
processNextPage(bool Released)369 void processNextPage(bool Released) {
370 if (Released) {
371 if (!InRange) {
372 CurrentRangeStatePage = CurrentPage;
373 InRange = true;
374 }
375 } else {
376 closeOpenedRange();
377 }
378 CurrentPage++;
379 }
380
skipPages(uptr N)381 void skipPages(uptr N) {
382 closeOpenedRange();
383 CurrentPage += N;
384 }
385
finish()386 void finish() { closeOpenedRange(); }
387
388 private:
closeOpenedRange()389 void closeOpenedRange() {
390 if (InRange) {
391 const uptr PageSizeLog = getPageSizeLogCached();
392 Recorder.releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog),
393 (CurrentPage << PageSizeLog));
394 InRange = false;
395 }
396 }
397
398 ReleaseRecorderT &Recorder;
399 bool InRange = false;
400 uptr CurrentPage = 0;
401 uptr CurrentRangeStatePage = 0;
402 };
403
404 struct PageReleaseContext {
405 PageReleaseContext(uptr BlockSize, uptr NumberOfRegions, uptr ReleaseSize,
406 uptr ReleaseOffset = 0)
BlockSizePageReleaseContext407 : BlockSize(BlockSize), NumberOfRegions(NumberOfRegions) {
408 const uptr PageSize = getPageSizeCached();
409 if (BlockSize <= PageSize) {
410 if (PageSize % BlockSize == 0) {
411 // Same number of chunks per page, no cross overs.
412 FullPagesBlockCountMax = PageSize / BlockSize;
413 SameBlockCountPerPage = true;
414 } else if (BlockSize % (PageSize % BlockSize) == 0) {
415 // Some chunks are crossing page boundaries, which means that the page
416 // contains one or two partial chunks, but all pages contain the same
417 // number of chunks.
418 FullPagesBlockCountMax = PageSize / BlockSize + 1;
419 SameBlockCountPerPage = true;
420 } else {
421 // Some chunks are crossing page boundaries, which means that the page
422 // contains one or two partial chunks.
423 FullPagesBlockCountMax = PageSize / BlockSize + 2;
424 SameBlockCountPerPage = false;
425 }
426 } else {
427 if ((BlockSize & (PageSize - 1)) == 0) {
428 // One chunk covers multiple pages, no cross overs.
429 FullPagesBlockCountMax = 1;
430 SameBlockCountPerPage = true;
431 } else {
432 // One chunk covers multiple pages, Some chunks are crossing page
433 // boundaries. Some pages contain one chunk, some contain two.
434 FullPagesBlockCountMax = 2;
435 SameBlockCountPerPage = false;
436 }
437 }
438
439 // TODO: For multiple regions, it's more complicated to support partial
440 // region marking (which includes the complexity of how to handle the last
441 // block in a region). We may consider this after markFreeBlocks() accepts
442 // only free blocks from the same region.
443 if (NumberOfRegions != 1)
444 DCHECK_EQ(ReleaseOffset, 0U);
445
446 const uptr PageSizeLog = getPageSizeLogCached();
447 PagesCount = roundUp(ReleaseSize, PageSize) >> PageSizeLog;
448 ReleasePageOffset = ReleaseOffset >> PageSizeLog;
449 }
450
451 // PageMap is lazily allocated when markFreeBlocks() is invoked.
hasBlockMarkedPageReleaseContext452 bool hasBlockMarked() const {
453 return PageMap.isAllocated();
454 }
455
ensurePageMapAllocatedPageReleaseContext456 bool ensurePageMapAllocated() {
457 if (PageMap.isAllocated())
458 return true;
459 PageMap.reset(NumberOfRegions, PagesCount, FullPagesBlockCountMax);
460 // TODO: Log some message when we fail on PageMap allocation.
461 return PageMap.isAllocated();
462 }
463
464 // Mark all the blocks in the given range [From, to). Instead of visiting all
465 // the blocks, we will just mark the page as all counted. Note the `From` and
466 // `To` has to be page aligned but with one exception, if `To` is equal to the
467 // RegionSize, it's not necessary to be aligned with page size.
markRangeAsAllCountedPageReleaseContext468 bool markRangeAsAllCounted(uptr From, uptr To, uptr Base,
469 const uptr RegionIndex, const uptr RegionSize) {
470 const uptr PageSize = getPageSizeCached();
471 DCHECK_LT(From, To);
472 DCHECK_LE(To, Base + RegionSize);
473 DCHECK_EQ(From % PageSize, 0U);
474 DCHECK_LE(To - From, RegionSize);
475
476 if (!ensurePageMapAllocated())
477 return false;
478
479 uptr FromInRegion = From - Base;
480 uptr ToInRegion = To - Base;
481 uptr FirstBlockInRange = roundUpSlow(FromInRegion, BlockSize);
482
483 // The straddling block sits across entire range.
484 if (FirstBlockInRange >= ToInRegion)
485 return true;
486
487 // First block may not sit at the first pape in the range, move
488 // `FromInRegion` to the first block page.
489 FromInRegion = roundDown(FirstBlockInRange, PageSize);
490
491 // When The first block is not aligned to the range boundary, which means
492 // there is a block sitting acorss `From`, that looks like,
493 //
494 // From To
495 // V V
496 // +-----------------------------------------------+
497 // +-----+-----+-----+-----+
498 // | | | | | ...
499 // +-----+-----+-----+-----+
500 // |- first page -||- second page -||- ...
501 //
502 // Therefore, we can't just mark the first page as all counted. Instead, we
503 // increment the number of blocks in the first page in the page map and
504 // then round up the `From` to the next page.
505 if (FirstBlockInRange != FromInRegion) {
506 DCHECK_GT(FromInRegion + PageSize, FirstBlockInRange);
507 uptr NumBlocksInFirstPage =
508 (FromInRegion + PageSize - FirstBlockInRange + BlockSize - 1) /
509 BlockSize;
510 PageMap.incN(RegionIndex, getPageIndex(FromInRegion),
511 NumBlocksInFirstPage);
512 FromInRegion = roundUp(FromInRegion + 1, PageSize);
513 }
514
515 uptr LastBlockInRange = roundDownSlow(ToInRegion - 1, BlockSize);
516
517 // Note that LastBlockInRange may be smaller than `FromInRegion` at this
518 // point because it may contain only one block in the range.
519
520 // When the last block sits across `To`, we can't just mark the pages
521 // occupied by the last block as all counted. Instead, we increment the
522 // counters of those pages by 1. The exception is that if it's the last
523 // block in the region, it's fine to mark those pages as all counted.
524 if (LastBlockInRange + BlockSize != RegionSize) {
525 DCHECK_EQ(ToInRegion % PageSize, 0U);
526 // The case below is like,
527 //
528 // From To
529 // V V
530 // +----------------------------------------+
531 // +-----+-----+-----+-----+
532 // | | | | | ...
533 // +-----+-----+-----+-----+
534 // ... -||- last page -||- next page -|
535 //
536 // The last block is not aligned to `To`, we need to increment the
537 // counter of `next page` by 1.
538 if (LastBlockInRange + BlockSize != ToInRegion) {
539 PageMap.incRange(RegionIndex, getPageIndex(ToInRegion),
540 getPageIndex(LastBlockInRange + BlockSize - 1));
541 }
542 } else {
543 ToInRegion = RegionSize;
544 }
545
546 // After handling the first page and the last block, it's safe to mark any
547 // page in between the range [From, To).
548 if (FromInRegion < ToInRegion) {
549 PageMap.setAsAllCountedRange(RegionIndex, getPageIndex(FromInRegion),
550 getPageIndex(ToInRegion - 1));
551 }
552
553 return true;
554 }
555
556 template <class TransferBatchT, typename DecompactPtrT>
markFreeBlocksInRegionPageReleaseContext557 bool markFreeBlocksInRegion(const IntrusiveList<TransferBatchT> &FreeList,
558 DecompactPtrT DecompactPtr, const uptr Base,
559 const uptr RegionIndex, const uptr RegionSize,
560 bool MayContainLastBlockInRegion) {
561 if (!ensurePageMapAllocated())
562 return false;
563
564 const uptr PageSize = getPageSizeCached();
565 if (MayContainLastBlockInRegion) {
566 const uptr LastBlockInRegion =
567 ((RegionSize / BlockSize) - 1U) * BlockSize;
568 // The last block in a region may not use the entire page, we mark the
569 // following "pretend" memory block(s) as free in advance.
570 //
571 // Region Boundary
572 // v
573 // -----+-----------------------+
574 // | Last Page | <- Rounded Region Boundary
575 // -----+-----------------------+
576 // |-----||- trailing blocks -|
577 // ^
578 // last block
579 const uptr RoundedRegionSize = roundUp(RegionSize, PageSize);
580 const uptr TrailingBlockBase = LastBlockInRegion + BlockSize;
581 // If the difference between `RoundedRegionSize` and
582 // `TrailingBlockBase` is larger than a page, that implies the reported
583 // `RegionSize` may not be accurate.
584 DCHECK_LT(RoundedRegionSize - TrailingBlockBase, PageSize);
585
586 // Only the last page touched by the last block needs to mark the trailing
587 // blocks. Note that if the last "pretend" block straddles the boundary,
588 // we still have to count it in so that the logic of counting the number
589 // of blocks on a page is consistent.
590 uptr NumTrailingBlocks =
591 (roundUpSlow(RoundedRegionSize - TrailingBlockBase, BlockSize) +
592 BlockSize - 1) /
593 BlockSize;
594 if (NumTrailingBlocks > 0) {
595 PageMap.incN(RegionIndex, getPageIndex(TrailingBlockBase),
596 NumTrailingBlocks);
597 }
598 }
599
600 // Iterate over free chunks and count how many free chunks affect each
601 // allocated page.
602 if (BlockSize <= PageSize && PageSize % BlockSize == 0) {
603 // Each chunk affects one page only.
604 for (const auto &It : FreeList) {
605 for (u16 I = 0; I < It.getCount(); I++) {
606 const uptr PInRegion = DecompactPtr(It.get(I)) - Base;
607 DCHECK_LT(PInRegion, RegionSize);
608 PageMap.inc(RegionIndex, getPageIndex(PInRegion));
609 }
610 }
611 } else {
612 // In all other cases chunks might affect more than one page.
613 DCHECK_GE(RegionSize, BlockSize);
614 for (const auto &It : FreeList) {
615 for (u16 I = 0; I < It.getCount(); I++) {
616 const uptr PInRegion = DecompactPtr(It.get(I)) - Base;
617 PageMap.incRange(RegionIndex, getPageIndex(PInRegion),
618 getPageIndex(PInRegion + BlockSize - 1));
619 }
620 }
621 }
622
623 return true;
624 }
625
getPageIndexPageReleaseContext626 uptr getPageIndex(uptr P) {
627 return (P >> getPageSizeLogCached()) - ReleasePageOffset;
628 }
getReleaseOffsetPageReleaseContext629 uptr getReleaseOffset() {
630 return ReleasePageOffset << getPageSizeLogCached();
631 }
632
633 uptr BlockSize;
634 uptr NumberOfRegions;
635 // For partial region marking, some pages in front are not needed to be
636 // counted.
637 uptr ReleasePageOffset;
638 uptr PagesCount;
639 uptr FullPagesBlockCountMax;
640 bool SameBlockCountPerPage;
641 RegionPageMap PageMap;
642 };
643
644 // Try to release the page which doesn't have any in-used block, i.e., they are
645 // all free blocks. The `PageMap` will record the number of free blocks in each
646 // page.
647 template <class ReleaseRecorderT, typename SkipRegionT>
648 NOINLINE void
releaseFreeMemoryToOS(PageReleaseContext & Context,ReleaseRecorderT & Recorder,SkipRegionT SkipRegion)649 releaseFreeMemoryToOS(PageReleaseContext &Context,
650 ReleaseRecorderT &Recorder, SkipRegionT SkipRegion) {
651 const uptr PageSize = getPageSizeCached();
652 const uptr BlockSize = Context.BlockSize;
653 const uptr PagesCount = Context.PagesCount;
654 const uptr NumberOfRegions = Context.NumberOfRegions;
655 const uptr ReleasePageOffset = Context.ReleasePageOffset;
656 const uptr FullPagesBlockCountMax = Context.FullPagesBlockCountMax;
657 const bool SameBlockCountPerPage = Context.SameBlockCountPerPage;
658 RegionPageMap &PageMap = Context.PageMap;
659
660 // Iterate over pages detecting ranges of pages with chunk Counters equal
661 // to the expected number of chunks for the particular page.
662 FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder);
663 if (SameBlockCountPerPage) {
664 // Fast path, every page has the same number of chunks affecting it.
665 for (uptr I = 0; I < NumberOfRegions; I++) {
666 if (SkipRegion(I)) {
667 RangeTracker.skipPages(PagesCount);
668 continue;
669 }
670 for (uptr J = 0; J < PagesCount; J++) {
671 const bool CanRelease =
672 PageMap.updateAsAllCountedIf(I, J, FullPagesBlockCountMax);
673 RangeTracker.processNextPage(CanRelease);
674 }
675 }
676 } else {
677 // Slow path, go through the pages keeping count how many chunks affect
678 // each page.
679 const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1;
680 const uptr Pnc = Pn * BlockSize;
681 // The idea is to increment the current page pointer by the first chunk
682 // size, middle portion size (the portion of the page covered by chunks
683 // except the first and the last one) and then the last chunk size, adding
684 // up the number of chunks on the current page and checking on every step
685 // whether the page boundary was crossed.
686 for (uptr I = 0; I < NumberOfRegions; I++) {
687 if (SkipRegion(I)) {
688 RangeTracker.skipPages(PagesCount);
689 continue;
690 }
691 uptr PrevPageBoundary = 0;
692 uptr CurrentBoundary = 0;
693 if (ReleasePageOffset > 0) {
694 PrevPageBoundary = ReleasePageOffset << getPageSizeLogCached();
695 CurrentBoundary = roundUpSlow(PrevPageBoundary, BlockSize);
696 }
697 for (uptr J = 0; J < PagesCount; J++) {
698 const uptr PageBoundary = PrevPageBoundary + PageSize;
699 uptr BlocksPerPage = Pn;
700 if (CurrentBoundary < PageBoundary) {
701 if (CurrentBoundary > PrevPageBoundary)
702 BlocksPerPage++;
703 CurrentBoundary += Pnc;
704 if (CurrentBoundary < PageBoundary) {
705 BlocksPerPage++;
706 CurrentBoundary += BlockSize;
707 }
708 }
709 PrevPageBoundary = PageBoundary;
710 const bool CanRelease =
711 PageMap.updateAsAllCountedIf(I, J, BlocksPerPage);
712 RangeTracker.processNextPage(CanRelease);
713 }
714 }
715 }
716 RangeTracker.finish();
717 }
718
719 } // namespace scudo
720
721 #endif // SCUDO_RELEASE_H_
722