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