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