release.h 26 KB

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