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 "mutex.h"
15
16 namespace scudo {
17
18 class ReleaseRecorder {
19 public:
20 ReleaseRecorder(uptr Base, MapPlatformData *Data = nullptr)
Base(Base)21 : Base(Base), Data(Data) {}
22
getReleasedRangesCount()23 uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
24
getReleasedBytes()25 uptr getReleasedBytes() const { return ReleasedBytes; }
26
getBase()27 uptr getBase() const { return Base; }
28
29 // Releases [From, To) range of pages back to OS.
releasePageRangeToOS(uptr From,uptr To)30 void releasePageRangeToOS(uptr From, uptr To) {
31 const uptr Size = To - From;
32 releasePagesToOS(Base, From, Size, Data);
33 ReleasedRangesCount++;
34 ReleasedBytes += Size;
35 }
36
37 private:
38 uptr ReleasedRangesCount = 0;
39 uptr ReleasedBytes = 0;
40 uptr Base = 0;
41 MapPlatformData *Data = nullptr;
42 };
43
44 // A packed array of Counters. Each counter occupies 2^N bits, enough to store
45 // counter's MaxValue. Ctor will try to use a static buffer first, and if that
46 // fails (the buffer is too small or already locked), will allocate the
47 // required Buffer via map(). The caller is expected to check whether the
48 // initialization was successful by checking isAllocated() result. For
49 // performance sake, none of the accessors check the validity of the arguments,
50 // It is assumed that Index is always in [0, N) range and the value is not
51 // incremented past MaxValue.
52 class PackedCounterArray {
53 public:
PackedCounterArray(uptr NumberOfRegions,uptr CountersPerRegion,uptr MaxValue)54 PackedCounterArray(uptr NumberOfRegions, uptr CountersPerRegion,
55 uptr MaxValue)
56 : Regions(NumberOfRegions), NumCounters(CountersPerRegion) {
57 DCHECK_GT(Regions, 0);
58 DCHECK_GT(NumCounters, 0);
59 DCHECK_GT(MaxValue, 0);
60 constexpr uptr MaxCounterBits = sizeof(*Buffer) * 8UL;
61 // Rounding counter storage size up to the power of two allows for using
62 // bit shifts calculating particular counter's Index and offset.
63 const uptr CounterSizeBits =
64 roundUpToPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1);
65 DCHECK_LE(CounterSizeBits, MaxCounterBits);
66 CounterSizeBitsLog = getLog2(CounterSizeBits);
67 CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits);
68
69 const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog;
70 DCHECK_GT(PackingRatio, 0);
71 PackingRatioLog = getLog2(PackingRatio);
72 BitOffsetMask = PackingRatio - 1;
73
74 SizePerRegion =
75 roundUpTo(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >>
76 PackingRatioLog;
77 BufferSize = SizePerRegion * sizeof(*Buffer) * Regions;
78 if (BufferSize <= (StaticBufferCount * sizeof(Buffer[0])) &&
79 Mutex.tryLock()) {
80 Buffer = &StaticBuffer[0];
81 memset(Buffer, 0, BufferSize);
82 } else {
83 Buffer = reinterpret_cast<uptr *>(
84 map(nullptr, roundUpTo(BufferSize, getPageSizeCached()),
85 "scudo:counters", MAP_ALLOWNOMEM, &MapData));
86 }
87 }
~PackedCounterArray()88 ~PackedCounterArray() {
89 if (!isAllocated())
90 return;
91 if (Buffer == &StaticBuffer[0])
92 Mutex.unlock();
93 else
94 unmap(reinterpret_cast<void *>(Buffer),
95 roundUpTo(BufferSize, getPageSizeCached()), 0, &MapData);
96 }
97
isAllocated()98 bool isAllocated() const { return !!Buffer; }
99
getCount()100 uptr getCount() const { return NumCounters; }
101
get(uptr Region,uptr I)102 uptr get(uptr Region, uptr I) const {
103 DCHECK_LT(Region, Regions);
104 DCHECK_LT(I, NumCounters);
105 const uptr Index = I >> PackingRatioLog;
106 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
107 return (Buffer[Region * SizePerRegion + Index] >> BitOffset) & CounterMask;
108 }
109
inc(uptr Region,uptr I)110 void inc(uptr Region, uptr I) const {
111 DCHECK_LT(get(Region, I), CounterMask);
112 const uptr Index = I >> PackingRatioLog;
113 const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
114 DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
115 Buffer[Region * SizePerRegion + Index] += static_cast<uptr>(1U)
116 << BitOffset;
117 }
118
incRange(uptr Region,uptr From,uptr To)119 void incRange(uptr Region, uptr From, uptr To) const {
120 DCHECK_LE(From, To);
121 const uptr Top = Min(To + 1, NumCounters);
122 for (uptr I = From; I < Top; I++)
123 inc(Region, I);
124 }
125
getBufferSize()126 uptr getBufferSize() const { return BufferSize; }
127
128 static const uptr StaticBufferCount = 2048U;
129
130 private:
131 const uptr Regions;
132 const uptr NumCounters;
133 uptr CounterSizeBitsLog;
134 uptr CounterMask;
135 uptr PackingRatioLog;
136 uptr BitOffsetMask;
137
138 uptr SizePerRegion;
139 uptr BufferSize;
140 uptr *Buffer;
141 [[no_unique_address]] MapPlatformData MapData = {};
142
143 static HybridMutex Mutex;
144 static uptr StaticBuffer[StaticBufferCount];
145 };
146
147 template <class ReleaseRecorderT> class FreePagesRangeTracker {
148 public:
FreePagesRangeTracker(ReleaseRecorderT * Recorder)149 explicit FreePagesRangeTracker(ReleaseRecorderT *Recorder)
150 : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {}
151
processNextPage(bool Freed)152 void processNextPage(bool Freed) {
153 if (Freed) {
154 if (!InRange) {
155 CurrentRangeStatePage = CurrentPage;
156 InRange = true;
157 }
158 } else {
159 closeOpenedRange();
160 }
161 CurrentPage++;
162 }
163
skipPages(uptr N)164 void skipPages(uptr N) {
165 closeOpenedRange();
166 CurrentPage += N;
167 }
168
finish()169 void finish() { closeOpenedRange(); }
170
171 private:
closeOpenedRange()172 void closeOpenedRange() {
173 if (InRange) {
174 Recorder->releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog),
175 (CurrentPage << PageSizeLog));
176 InRange = false;
177 }
178 }
179
180 ReleaseRecorderT *const Recorder;
181 const uptr PageSizeLog;
182 bool InRange = false;
183 uptr CurrentPage = 0;
184 uptr CurrentRangeStatePage = 0;
185 };
186
187 template <class TransferBatchT, class ReleaseRecorderT, typename DecompactPtrT,
188 typename SkipRegionT>
189 NOINLINE void
releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> & FreeList,uptr RegionSize,uptr NumberOfRegions,uptr BlockSize,ReleaseRecorderT * Recorder,DecompactPtrT DecompactPtr,SkipRegionT SkipRegion)190 releaseFreeMemoryToOS(const IntrusiveList<TransferBatchT> &FreeList,
191 uptr RegionSize, uptr NumberOfRegions, uptr BlockSize,
192 ReleaseRecorderT *Recorder, DecompactPtrT DecompactPtr,
193 SkipRegionT SkipRegion) {
194 const uptr PageSize = getPageSizeCached();
195
196 // Figure out the number of chunks per page and whether we can take a fast
197 // path (the number of chunks per page is the same for all pages).
198 uptr FullPagesBlockCountMax;
199 bool SameBlockCountPerPage;
200 if (BlockSize <= PageSize) {
201 if (PageSize % BlockSize == 0) {
202 // Same number of chunks per page, no cross overs.
203 FullPagesBlockCountMax = PageSize / BlockSize;
204 SameBlockCountPerPage = true;
205 } else if (BlockSize % (PageSize % BlockSize) == 0) {
206 // Some chunks are crossing page boundaries, which means that the page
207 // contains one or two partial chunks, but all pages contain the same
208 // number of chunks.
209 FullPagesBlockCountMax = PageSize / BlockSize + 1;
210 SameBlockCountPerPage = true;
211 } else {
212 // Some chunks are crossing page boundaries, which means that the page
213 // contains one or two partial chunks.
214 FullPagesBlockCountMax = PageSize / BlockSize + 2;
215 SameBlockCountPerPage = false;
216 }
217 } else {
218 if (BlockSize % PageSize == 0) {
219 // One chunk covers multiple pages, no cross overs.
220 FullPagesBlockCountMax = 1;
221 SameBlockCountPerPage = true;
222 } else {
223 // One chunk covers multiple pages, Some chunks are crossing page
224 // boundaries. Some pages contain one chunk, some contain two.
225 FullPagesBlockCountMax = 2;
226 SameBlockCountPerPage = false;
227 }
228 }
229
230 const uptr PagesCount = roundUpTo(RegionSize, PageSize) / PageSize;
231 PackedCounterArray Counters(NumberOfRegions, PagesCount,
232 FullPagesBlockCountMax);
233 if (!Counters.isAllocated())
234 return;
235
236 const uptr PageSizeLog = getLog2(PageSize);
237 const uptr RoundedRegionSize = PagesCount << PageSizeLog;
238 const uptr RoundedSize = NumberOfRegions * RoundedRegionSize;
239
240 // Iterate over free chunks and count how many free chunks affect each
241 // allocated page.
242 if (BlockSize <= PageSize && PageSize % BlockSize == 0) {
243 // Each chunk affects one page only.
244 for (const auto &It : FreeList) {
245 for (u32 I = 0; I < It.getCount(); I++) {
246 const uptr P = DecompactPtr(It.get(I)) - Recorder->getBase();
247 if (P >= RoundedSize)
248 continue;
249 const uptr RegionIndex = NumberOfRegions == 1U ? 0 : P / RegionSize;
250 const uptr PInRegion = P - RegionIndex * RegionSize;
251 Counters.inc(RegionIndex, PInRegion >> PageSizeLog);
252 }
253 }
254 } else {
255 // In all other cases chunks might affect more than one page.
256 DCHECK_GE(RegionSize, BlockSize);
257 const uptr LastBlockInRegion = ((RegionSize / BlockSize) - 1U) * BlockSize;
258 for (const auto &It : FreeList) {
259 for (u32 I = 0; I < It.getCount(); I++) {
260 const uptr P = DecompactPtr(It.get(I)) - Recorder->getBase();
261 if (P >= RoundedSize)
262 continue;
263 const uptr RegionIndex = NumberOfRegions == 1U ? 0 : P / RegionSize;
264 uptr PInRegion = P - RegionIndex * RegionSize;
265 Counters.incRange(RegionIndex, PInRegion >> PageSizeLog,
266 (PInRegion + BlockSize - 1) >> PageSizeLog);
267 // The last block in a region might straddle a page, so if it's
268 // free, we mark the following "pretend" memory block(s) as free.
269 if (PInRegion == LastBlockInRegion) {
270 PInRegion += BlockSize;
271 while (PInRegion < RoundedRegionSize) {
272 Counters.incRange(RegionIndex, PInRegion >> PageSizeLog,
273 (PInRegion + BlockSize - 1) >> PageSizeLog);
274 PInRegion += BlockSize;
275 }
276 }
277 }
278 }
279 }
280
281 // Iterate over pages detecting ranges of pages with chunk Counters equal
282 // to the expected number of chunks for the particular page.
283 FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder);
284 if (SameBlockCountPerPage) {
285 // Fast path, every page has the same number of chunks affecting it.
286 for (uptr I = 0; I < NumberOfRegions; I++) {
287 if (SkipRegion(I)) {
288 RangeTracker.skipPages(PagesCount);
289 continue;
290 }
291 for (uptr J = 0; J < PagesCount; J++)
292 RangeTracker.processNextPage(Counters.get(I, J) ==
293 FullPagesBlockCountMax);
294 }
295 } else {
296 // Slow path, go through the pages keeping count how many chunks affect
297 // each page.
298 const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1;
299 const uptr Pnc = Pn * BlockSize;
300 // The idea is to increment the current page pointer by the first chunk
301 // size, middle portion size (the portion of the page covered by chunks
302 // except the first and the last one) and then the last chunk size, adding
303 // up the number of chunks on the current page and checking on every step
304 // whether the page boundary was crossed.
305 for (uptr I = 0; I < NumberOfRegions; I++) {
306 if (SkipRegion(I)) {
307 RangeTracker.skipPages(PagesCount);
308 continue;
309 }
310 uptr PrevPageBoundary = 0;
311 uptr CurrentBoundary = 0;
312 for (uptr J = 0; J < PagesCount; J++) {
313 const uptr PageBoundary = PrevPageBoundary + PageSize;
314 uptr BlocksPerPage = Pn;
315 if (CurrentBoundary < PageBoundary) {
316 if (CurrentBoundary > PrevPageBoundary)
317 BlocksPerPage++;
318 CurrentBoundary += Pnc;
319 if (CurrentBoundary < PageBoundary) {
320 BlocksPerPage++;
321 CurrentBoundary += BlockSize;
322 }
323 }
324 PrevPageBoundary = PageBoundary;
325 RangeTracker.processNextPage(Counters.get(I, J) == BlocksPerPage);
326 }
327 }
328 }
329 RangeTracker.finish();
330 }
331
332 } // namespace scudo
333
334 #endif // SCUDO_RELEASE_H_
335