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