release.h 12 KB

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