xref: /freebsd-src/contrib/llvm-project/compiler-rt/lib/scudo/standalone/release.h (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
10b57cec5SDimitry Andric //===-- release.h -----------------------------------------------*- C++ -*-===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric 
90b57cec5SDimitry Andric #ifndef SCUDO_RELEASE_H_
100b57cec5SDimitry Andric #define SCUDO_RELEASE_H_
110b57cec5SDimitry Andric 
120b57cec5SDimitry Andric #include "common.h"
130b57cec5SDimitry Andric #include "list.h"
1406c3fb27SDimitry Andric #include "mem_map.h"
155ffd83dbSDimitry Andric #include "mutex.h"
1606c3fb27SDimitry Andric #include "thread_annotations.h"
170b57cec5SDimitry Andric 
180b57cec5SDimitry Andric namespace scudo {
190b57cec5SDimitry Andric 
2006c3fb27SDimitry Andric template <typename MemMapT> class RegionReleaseRecorder {
2106c3fb27SDimitry Andric public:
2206c3fb27SDimitry Andric   RegionReleaseRecorder(MemMapT *RegionMemMap, uptr Base, uptr Offset = 0)
RegionMemMap(RegionMemMap)2306c3fb27SDimitry Andric       : RegionMemMap(RegionMemMap), Base(Base), Offset(Offset) {}
2406c3fb27SDimitry Andric 
getReleasedRangesCount()2506c3fb27SDimitry Andric   uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
2606c3fb27SDimitry Andric 
getReleasedBytes()2706c3fb27SDimitry Andric   uptr getReleasedBytes() const { return ReleasedBytes; }
2806c3fb27SDimitry Andric 
getBase()2906c3fb27SDimitry Andric   uptr getBase() const { return Base; }
3006c3fb27SDimitry Andric 
3106c3fb27SDimitry Andric   // Releases [From, To) range of pages back to OS. Note that `From` and `To`
3206c3fb27SDimitry Andric   // are offseted from `Base` + Offset.
releasePageRangeToOS(uptr From,uptr To)3306c3fb27SDimitry Andric   void releasePageRangeToOS(uptr From, uptr To) {
3406c3fb27SDimitry Andric     const uptr Size = To - From;
3506c3fb27SDimitry Andric     RegionMemMap->releasePagesToOS(getBase() + Offset + From, Size);
3606c3fb27SDimitry Andric     ReleasedRangesCount++;
3706c3fb27SDimitry Andric     ReleasedBytes += Size;
3806c3fb27SDimitry Andric   }
3906c3fb27SDimitry Andric 
4006c3fb27SDimitry Andric private:
4106c3fb27SDimitry Andric   uptr ReleasedRangesCount = 0;
4206c3fb27SDimitry Andric   uptr ReleasedBytes = 0;
4306c3fb27SDimitry Andric   MemMapT *RegionMemMap = nullptr;
4406c3fb27SDimitry Andric   uptr Base = 0;
4506c3fb27SDimitry Andric   // The release offset from Base. This is used when we know a given range after
4606c3fb27SDimitry Andric   // Base will not be released.
4706c3fb27SDimitry Andric   uptr Offset = 0;
4806c3fb27SDimitry Andric };
4906c3fb27SDimitry Andric 
500b57cec5SDimitry Andric class ReleaseRecorder {
510b57cec5SDimitry Andric public:
5206c3fb27SDimitry Andric   ReleaseRecorder(uptr Base, uptr Offset = 0, MapPlatformData *Data = nullptr)
Base(Base)5306c3fb27SDimitry Andric       : Base(Base), Offset(Offset), Data(Data) {}
540b57cec5SDimitry Andric 
getReleasedRangesCount()550b57cec5SDimitry Andric   uptr getReleasedRangesCount() const { return ReleasedRangesCount; }
560b57cec5SDimitry Andric 
getReleasedBytes()570b57cec5SDimitry Andric   uptr getReleasedBytes() const { return ReleasedBytes; }
580b57cec5SDimitry Andric 
getBase()59fe6060f1SDimitry Andric   uptr getBase() const { return Base; }
60fe6060f1SDimitry Andric 
610b57cec5SDimitry Andric   // Releases [From, To) range of pages back to OS.
releasePageRangeToOS(uptr From,uptr To)620b57cec5SDimitry Andric   void releasePageRangeToOS(uptr From, uptr To) {
630b57cec5SDimitry Andric     const uptr Size = To - From;
6406c3fb27SDimitry Andric     releasePagesToOS(Base, From + Offset, Size, Data);
650b57cec5SDimitry Andric     ReleasedRangesCount++;
660b57cec5SDimitry Andric     ReleasedBytes += Size;
670b57cec5SDimitry Andric   }
680b57cec5SDimitry Andric 
690b57cec5SDimitry Andric private:
700b57cec5SDimitry Andric   uptr ReleasedRangesCount = 0;
710b57cec5SDimitry Andric   uptr ReleasedBytes = 0;
7206c3fb27SDimitry Andric   // The starting address to release. Note that we may want to combine (Base +
7306c3fb27SDimitry Andric   // Offset) as a new Base. However, the Base is retrieved from
7406c3fb27SDimitry Andric   // `MapPlatformData` on Fuchsia, which means the offset won't be aware.
7506c3fb27SDimitry Andric   // Therefore, store them separately to make it work on all the platforms.
76fe6060f1SDimitry Andric   uptr Base = 0;
7706c3fb27SDimitry Andric   // The release offset from Base. This is used when we know a given range after
7806c3fb27SDimitry Andric   // Base will not be released.
7906c3fb27SDimitry Andric   uptr Offset = 0;
800b57cec5SDimitry Andric   MapPlatformData *Data = nullptr;
810b57cec5SDimitry Andric };
820b57cec5SDimitry Andric 
83*5f757f3fSDimitry Andric class FragmentationRecorder {
84*5f757f3fSDimitry Andric public:
85*5f757f3fSDimitry Andric   FragmentationRecorder() = default;
86*5f757f3fSDimitry Andric 
getReleasedPagesCount()87*5f757f3fSDimitry Andric   uptr getReleasedPagesCount() const { return ReleasedPagesCount; }
88*5f757f3fSDimitry Andric 
releasePageRangeToOS(uptr From,uptr To)89*5f757f3fSDimitry Andric   void releasePageRangeToOS(uptr From, uptr To) {
90*5f757f3fSDimitry Andric     DCHECK_EQ((To - From) % getPageSizeCached(), 0U);
91*5f757f3fSDimitry Andric     ReleasedPagesCount += (To - From) / getPageSizeCached();
92*5f757f3fSDimitry Andric   }
93*5f757f3fSDimitry Andric 
94*5f757f3fSDimitry Andric private:
95*5f757f3fSDimitry Andric   uptr ReleasedPagesCount = 0;
96*5f757f3fSDimitry Andric };
97*5f757f3fSDimitry Andric 
98*5f757f3fSDimitry Andric // A buffer pool which holds a fixed number of static buffers of `uptr` elements
99*5f757f3fSDimitry Andric // for fast buffer allocation. If the request size is greater than
100*5f757f3fSDimitry Andric // `StaticBufferNumElements` or if all the static buffers are in use, it'll
10106c3fb27SDimitry Andric // delegate the allocation to map().
102*5f757f3fSDimitry Andric template <uptr StaticBufferCount, uptr StaticBufferNumElements>
103*5f757f3fSDimitry Andric class BufferPool {
10406c3fb27SDimitry Andric public:
10506c3fb27SDimitry Andric   // Preserve 1 bit in the `Mask` so that we don't need to do zero-check while
10606c3fb27SDimitry Andric   // extracting the least significant bit from the `Mask`.
10706c3fb27SDimitry Andric   static_assert(StaticBufferCount < SCUDO_WORDSIZE, "");
108*5f757f3fSDimitry Andric   static_assert(isAligned(StaticBufferNumElements * sizeof(uptr),
109*5f757f3fSDimitry Andric                           SCUDO_CACHE_LINE_SIZE),
110*5f757f3fSDimitry Andric                 "");
11106c3fb27SDimitry Andric 
112*5f757f3fSDimitry Andric   struct Buffer {
113*5f757f3fSDimitry Andric     // Pointer to the buffer's memory, or nullptr if no buffer was allocated.
114*5f757f3fSDimitry Andric     uptr *Data = nullptr;
115*5f757f3fSDimitry Andric 
116*5f757f3fSDimitry Andric     // The index of the underlying static buffer, or StaticBufferCount if this
117*5f757f3fSDimitry Andric     // buffer was dynamically allocated. This value is initially set to a poison
118*5f757f3fSDimitry Andric     // value to aid debugging.
119*5f757f3fSDimitry Andric     uptr BufferIndex = ~static_cast<uptr>(0);
120*5f757f3fSDimitry Andric 
121*5f757f3fSDimitry Andric     // Only valid if BufferIndex == StaticBufferCount.
122*5f757f3fSDimitry Andric     MemMapT MemMap = {};
123*5f757f3fSDimitry Andric   };
124*5f757f3fSDimitry Andric 
125*5f757f3fSDimitry Andric   // Return a zero-initialized buffer which can contain at least the given
126*5f757f3fSDimitry Andric   // number of elements, or nullptr on failure.
getBuffer(const uptr NumElements)127*5f757f3fSDimitry Andric   Buffer getBuffer(const uptr NumElements) {
128*5f757f3fSDimitry Andric     if (UNLIKELY(NumElements > StaticBufferNumElements))
129*5f757f3fSDimitry Andric       return getDynamicBuffer(NumElements);
13006c3fb27SDimitry Andric 
13106c3fb27SDimitry Andric     uptr index;
13206c3fb27SDimitry Andric     {
13306c3fb27SDimitry Andric       // TODO: In general, we expect this operation should be fast so the
13406c3fb27SDimitry Andric       // waiting thread won't be put into sleep. The HybridMutex does implement
13506c3fb27SDimitry Andric       // the busy-waiting but we may want to review the performance and see if
13606c3fb27SDimitry Andric       // we need an explict spin lock here.
13706c3fb27SDimitry Andric       ScopedLock L(Mutex);
13806c3fb27SDimitry Andric       index = getLeastSignificantSetBitIndex(Mask);
13906c3fb27SDimitry Andric       if (index < StaticBufferCount)
14006c3fb27SDimitry Andric         Mask ^= static_cast<uptr>(1) << index;
14106c3fb27SDimitry Andric     }
14206c3fb27SDimitry Andric 
14306c3fb27SDimitry Andric     if (index >= StaticBufferCount)
144*5f757f3fSDimitry Andric       return getDynamicBuffer(NumElements);
14506c3fb27SDimitry Andric 
146*5f757f3fSDimitry Andric     Buffer Buf;
147*5f757f3fSDimitry Andric     Buf.Data = &RawBuffer[index * StaticBufferNumElements];
148*5f757f3fSDimitry Andric     Buf.BufferIndex = index;
149*5f757f3fSDimitry Andric     memset(Buf.Data, 0, StaticBufferNumElements * sizeof(uptr));
150*5f757f3fSDimitry Andric     return Buf;
15106c3fb27SDimitry Andric   }
15206c3fb27SDimitry Andric 
releaseBuffer(Buffer Buf)153*5f757f3fSDimitry Andric   void releaseBuffer(Buffer Buf) {
154*5f757f3fSDimitry Andric     DCHECK_NE(Buf.Data, nullptr);
155*5f757f3fSDimitry Andric     DCHECK_LE(Buf.BufferIndex, StaticBufferCount);
156*5f757f3fSDimitry Andric     if (Buf.BufferIndex != StaticBufferCount) {
15706c3fb27SDimitry Andric       ScopedLock L(Mutex);
158*5f757f3fSDimitry Andric       DCHECK_EQ((Mask & (static_cast<uptr>(1) << Buf.BufferIndex)), 0U);
159*5f757f3fSDimitry Andric       Mask |= static_cast<uptr>(1) << Buf.BufferIndex;
16006c3fb27SDimitry Andric     } else {
161*5f757f3fSDimitry Andric       Buf.MemMap.unmap(Buf.MemMap.getBase(), Buf.MemMap.getCapacity());
16206c3fb27SDimitry Andric     }
16306c3fb27SDimitry Andric   }
16406c3fb27SDimitry Andric 
isStaticBufferTestOnly(const Buffer & Buf)165*5f757f3fSDimitry Andric   bool isStaticBufferTestOnly(const Buffer &Buf) {
166*5f757f3fSDimitry Andric     DCHECK_NE(Buf.Data, nullptr);
167*5f757f3fSDimitry Andric     DCHECK_LE(Buf.BufferIndex, StaticBufferCount);
168*5f757f3fSDimitry Andric     return Buf.BufferIndex != StaticBufferCount;
16906c3fb27SDimitry Andric   }
17006c3fb27SDimitry Andric 
17106c3fb27SDimitry Andric private:
getDynamicBuffer(const uptr NumElements)172*5f757f3fSDimitry Andric   Buffer getDynamicBuffer(const uptr NumElements) {
17306c3fb27SDimitry Andric     // When using a heap-based buffer, precommit the pages backing the
17406c3fb27SDimitry Andric     // Vmar by passing |MAP_PRECOMMIT| flag. This allows an optimization
17506c3fb27SDimitry Andric     // where page fault exceptions are skipped as the allocated memory
17606c3fb27SDimitry Andric     // is accessed. So far, this is only enabled on Fuchsia. It hasn't proven a
17706c3fb27SDimitry Andric     // performance benefit on other platforms.
17806c3fb27SDimitry Andric     const uptr MmapFlags = MAP_ALLOWNOMEM | (SCUDO_FUCHSIA ? MAP_PRECOMMIT : 0);
179*5f757f3fSDimitry Andric     const uptr MappedSize =
180*5f757f3fSDimitry Andric         roundUp(NumElements * sizeof(uptr), getPageSizeCached());
181*5f757f3fSDimitry Andric     Buffer Buf;
182*5f757f3fSDimitry Andric     if (Buf.MemMap.map(/*Addr=*/0, MappedSize, "scudo:counters", MmapFlags)) {
183*5f757f3fSDimitry Andric       Buf.Data = reinterpret_cast<uptr *>(Buf.MemMap.getBase());
184*5f757f3fSDimitry Andric       Buf.BufferIndex = StaticBufferCount;
185*5f757f3fSDimitry Andric     }
186*5f757f3fSDimitry Andric     return Buf;
18706c3fb27SDimitry Andric   }
18806c3fb27SDimitry Andric 
18906c3fb27SDimitry Andric   HybridMutex Mutex;
19006c3fb27SDimitry Andric   // '1' means that buffer index is not used. '0' means the buffer is in use.
19106c3fb27SDimitry Andric   uptr Mask GUARDED_BY(Mutex) = ~static_cast<uptr>(0);
192*5f757f3fSDimitry Andric   uptr RawBuffer[StaticBufferCount * StaticBufferNumElements] GUARDED_BY(Mutex);
19306c3fb27SDimitry Andric };
19406c3fb27SDimitry Andric 
195bdd1243dSDimitry Andric // A Region page map is used to record the usage of pages in the regions. It
196bdd1243dSDimitry Andric // implements a packed array of Counters. Each counter occupies 2^N bits, enough
197bdd1243dSDimitry Andric // to store counter's MaxValue. Ctor will try to use a static buffer first, and
198bdd1243dSDimitry Andric // if that fails (the buffer is too small or already locked), will allocate the
1995ffd83dbSDimitry Andric // required Buffer via map(). The caller is expected to check whether the
2005ffd83dbSDimitry Andric // initialization was successful by checking isAllocated() result. For
2015ffd83dbSDimitry Andric // performance sake, none of the accessors check the validity of the arguments,
2025ffd83dbSDimitry Andric // It is assumed that Index is always in [0, N) range and the value is not
2035ffd83dbSDimitry Andric // incremented past MaxValue.
204bdd1243dSDimitry Andric class RegionPageMap {
2050b57cec5SDimitry Andric public:
RegionPageMap()206bdd1243dSDimitry Andric   RegionPageMap()
207*5f757f3fSDimitry Andric       : Regions(0), NumCounters(0), CounterSizeBitsLog(0), CounterMask(0),
208*5f757f3fSDimitry Andric         PackingRatioLog(0), BitOffsetMask(0), SizePerRegion(0),
209*5f757f3fSDimitry Andric         BufferNumElements(0) {}
RegionPageMap(uptr NumberOfRegions,uptr CountersPerRegion,uptr MaxValue)210bdd1243dSDimitry Andric   RegionPageMap(uptr NumberOfRegions, uptr CountersPerRegion, uptr MaxValue) {
211bdd1243dSDimitry Andric     reset(NumberOfRegions, CountersPerRegion, MaxValue);
212bdd1243dSDimitry Andric   }
~RegionPageMap()213bdd1243dSDimitry Andric   ~RegionPageMap() {
214bdd1243dSDimitry Andric     if (!isAllocated())
215bdd1243dSDimitry Andric       return;
216*5f757f3fSDimitry Andric     Buffers.releaseBuffer(Buffer);
217*5f757f3fSDimitry Andric     Buffer = {};
218bdd1243dSDimitry Andric   }
219bdd1243dSDimitry Andric 
22006c3fb27SDimitry Andric   // Lock of `StaticBuffer` is acquired conditionally and there's no easy way to
22106c3fb27SDimitry Andric   // specify the thread-safety attribute properly in current code structure.
22206c3fb27SDimitry Andric   // Besides, it's the only place we may want to check thread safety. Therefore,
22306c3fb27SDimitry Andric   // it's fine to bypass the thread-safety analysis now.
reset(uptr NumberOfRegion,uptr CountersPerRegion,uptr MaxValue)224bdd1243dSDimitry Andric   void reset(uptr NumberOfRegion, uptr CountersPerRegion, uptr MaxValue) {
225bdd1243dSDimitry Andric     DCHECK_GT(NumberOfRegion, 0);
226bdd1243dSDimitry Andric     DCHECK_GT(CountersPerRegion, 0);
227e8d8bef9SDimitry Andric     DCHECK_GT(MaxValue, 0);
228bdd1243dSDimitry Andric 
229bdd1243dSDimitry Andric     Regions = NumberOfRegion;
230bdd1243dSDimitry Andric     NumCounters = CountersPerRegion;
231bdd1243dSDimitry Andric 
232*5f757f3fSDimitry Andric     constexpr uptr MaxCounterBits = sizeof(*Buffer.Data) * 8UL;
2330b57cec5SDimitry Andric     // Rounding counter storage size up to the power of two allows for using
2340b57cec5SDimitry Andric     // bit shifts calculating particular counter's Index and offset.
2350b57cec5SDimitry Andric     const uptr CounterSizeBits =
23606c3fb27SDimitry Andric         roundUpPowerOfTwo(getMostSignificantSetBitIndex(MaxValue) + 1);
237e8d8bef9SDimitry Andric     DCHECK_LE(CounterSizeBits, MaxCounterBits);
2380b57cec5SDimitry Andric     CounterSizeBitsLog = getLog2(CounterSizeBits);
2390b57cec5SDimitry Andric     CounterMask = ~(static_cast<uptr>(0)) >> (MaxCounterBits - CounterSizeBits);
2400b57cec5SDimitry Andric 
2410b57cec5SDimitry Andric     const uptr PackingRatio = MaxCounterBits >> CounterSizeBitsLog;
242e8d8bef9SDimitry Andric     DCHECK_GT(PackingRatio, 0);
2430b57cec5SDimitry Andric     PackingRatioLog = getLog2(PackingRatio);
2440b57cec5SDimitry Andric     BitOffsetMask = PackingRatio - 1;
2450b57cec5SDimitry Andric 
246e8d8bef9SDimitry Andric     SizePerRegion =
24706c3fb27SDimitry Andric         roundUp(NumCounters, static_cast<uptr>(1U) << PackingRatioLog) >>
248e8d8bef9SDimitry Andric         PackingRatioLog;
249*5f757f3fSDimitry Andric     BufferNumElements = SizePerRegion * Regions;
250*5f757f3fSDimitry Andric     Buffer = Buffers.getBuffer(BufferNumElements);
2515ffd83dbSDimitry Andric   }
2520b57cec5SDimitry Andric 
isAllocated()253*5f757f3fSDimitry Andric   bool isAllocated() const { return Buffer.Data != nullptr; }
2540b57cec5SDimitry Andric 
getCount()255e8d8bef9SDimitry Andric   uptr getCount() const { return NumCounters; }
2560b57cec5SDimitry Andric 
get(uptr Region,uptr I)257e8d8bef9SDimitry Andric   uptr get(uptr Region, uptr I) const {
258e8d8bef9SDimitry Andric     DCHECK_LT(Region, Regions);
259e8d8bef9SDimitry Andric     DCHECK_LT(I, NumCounters);
2600b57cec5SDimitry Andric     const uptr Index = I >> PackingRatioLog;
2610b57cec5SDimitry Andric     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
262*5f757f3fSDimitry Andric     return (Buffer.Data[Region * SizePerRegion + Index] >> BitOffset) &
263*5f757f3fSDimitry Andric            CounterMask;
2640b57cec5SDimitry Andric   }
2650b57cec5SDimitry Andric 
inc(uptr Region,uptr I)266e8d8bef9SDimitry Andric   void inc(uptr Region, uptr I) const {
267e8d8bef9SDimitry Andric     DCHECK_LT(get(Region, I), CounterMask);
2680b57cec5SDimitry Andric     const uptr Index = I >> PackingRatioLog;
2690b57cec5SDimitry Andric     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
2700b57cec5SDimitry Andric     DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
271bdd1243dSDimitry Andric     DCHECK_EQ(isAllCounted(Region, I), false);
272*5f757f3fSDimitry Andric     Buffer.Data[Region * SizePerRegion + Index] += static_cast<uptr>(1U)
273e8d8bef9SDimitry Andric                                                    << BitOffset;
2740b57cec5SDimitry Andric   }
2750b57cec5SDimitry Andric 
incN(uptr Region,uptr I,uptr N)27606c3fb27SDimitry Andric   void incN(uptr Region, uptr I, uptr N) const {
27706c3fb27SDimitry Andric     DCHECK_GT(N, 0U);
27806c3fb27SDimitry Andric     DCHECK_LE(N, CounterMask);
27906c3fb27SDimitry Andric     DCHECK_LE(get(Region, I), CounterMask - N);
28006c3fb27SDimitry Andric     const uptr Index = I >> PackingRatioLog;
28106c3fb27SDimitry Andric     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
28206c3fb27SDimitry Andric     DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
28306c3fb27SDimitry Andric     DCHECK_EQ(isAllCounted(Region, I), false);
284*5f757f3fSDimitry Andric     Buffer.Data[Region * SizePerRegion + Index] += N << BitOffset;
28506c3fb27SDimitry Andric   }
28606c3fb27SDimitry Andric 
incRange(uptr Region,uptr From,uptr To)287e8d8bef9SDimitry Andric   void incRange(uptr Region, uptr From, uptr To) const {
2880b57cec5SDimitry Andric     DCHECK_LE(From, To);
289e8d8bef9SDimitry Andric     const uptr Top = Min(To + 1, NumCounters);
2905ffd83dbSDimitry Andric     for (uptr I = From; I < Top; I++)
291e8d8bef9SDimitry Andric       inc(Region, I);
2920b57cec5SDimitry Andric   }
2930b57cec5SDimitry Andric 
294bdd1243dSDimitry Andric   // Set the counter to the max value. Note that the max number of blocks in a
295bdd1243dSDimitry Andric   // page may vary. To provide an easier way to tell if all the blocks are
296bdd1243dSDimitry Andric   // counted for different pages, set to the same max value to denote the
297bdd1243dSDimitry Andric   // all-counted status.
setAsAllCounted(uptr Region,uptr I)298bdd1243dSDimitry Andric   void setAsAllCounted(uptr Region, uptr I) const {
299bdd1243dSDimitry Andric     DCHECK_LE(get(Region, I), CounterMask);
300bdd1243dSDimitry Andric     const uptr Index = I >> PackingRatioLog;
301bdd1243dSDimitry Andric     const uptr BitOffset = (I & BitOffsetMask) << CounterSizeBitsLog;
302bdd1243dSDimitry Andric     DCHECK_LT(BitOffset, SCUDO_WORDSIZE);
303*5f757f3fSDimitry Andric     Buffer.Data[Region * SizePerRegion + Index] |= CounterMask << BitOffset;
304bdd1243dSDimitry Andric   }
setAsAllCountedRange(uptr Region,uptr From,uptr To)30506c3fb27SDimitry Andric   void setAsAllCountedRange(uptr Region, uptr From, uptr To) const {
30606c3fb27SDimitry Andric     DCHECK_LE(From, To);
30706c3fb27SDimitry Andric     const uptr Top = Min(To + 1, NumCounters);
30806c3fb27SDimitry Andric     for (uptr I = From; I < Top; I++)
30906c3fb27SDimitry Andric       setAsAllCounted(Region, I);
31006c3fb27SDimitry Andric   }
31106c3fb27SDimitry Andric 
updateAsAllCountedIf(uptr Region,uptr I,uptr MaxCount)31206c3fb27SDimitry Andric   bool updateAsAllCountedIf(uptr Region, uptr I, uptr MaxCount) {
31306c3fb27SDimitry Andric     const uptr Count = get(Region, I);
31406c3fb27SDimitry Andric     if (Count == CounterMask)
31506c3fb27SDimitry Andric       return true;
31606c3fb27SDimitry Andric     if (Count == MaxCount) {
31706c3fb27SDimitry Andric       setAsAllCounted(Region, I);
31806c3fb27SDimitry Andric       return true;
31906c3fb27SDimitry Andric     }
32006c3fb27SDimitry Andric     return false;
32106c3fb27SDimitry Andric   }
isAllCounted(uptr Region,uptr I)322bdd1243dSDimitry Andric   bool isAllCounted(uptr Region, uptr I) const {
323bdd1243dSDimitry Andric     return get(Region, I) == CounterMask;
324bdd1243dSDimitry Andric   }
325bdd1243dSDimitry Andric 
getBufferNumElements()326*5f757f3fSDimitry Andric   uptr getBufferNumElements() const { return BufferNumElements; }
3270b57cec5SDimitry Andric 
3280b57cec5SDimitry Andric private:
329*5f757f3fSDimitry Andric   // We may consider making this configurable if there are cases which may
330*5f757f3fSDimitry Andric   // benefit from this.
331*5f757f3fSDimitry Andric   static const uptr StaticBufferCount = 2U;
332*5f757f3fSDimitry Andric   static const uptr StaticBufferNumElements = 512U;
333*5f757f3fSDimitry Andric   using BufferPoolT = BufferPool<StaticBufferCount, StaticBufferNumElements>;
334*5f757f3fSDimitry Andric   static BufferPoolT Buffers;
335*5f757f3fSDimitry Andric 
336bdd1243dSDimitry Andric   uptr Regions;
337bdd1243dSDimitry Andric   uptr NumCounters;
3380b57cec5SDimitry Andric   uptr CounterSizeBitsLog;
3390b57cec5SDimitry Andric   uptr CounterMask;
3400b57cec5SDimitry Andric   uptr PackingRatioLog;
3410b57cec5SDimitry Andric   uptr BitOffsetMask;
3420b57cec5SDimitry Andric 
343e8d8bef9SDimitry Andric   uptr SizePerRegion;
344*5f757f3fSDimitry Andric   uptr BufferNumElements;
345*5f757f3fSDimitry Andric   BufferPoolT::Buffer Buffer;
3460b57cec5SDimitry Andric };
3470b57cec5SDimitry Andric 
3480b57cec5SDimitry Andric template <class ReleaseRecorderT> class FreePagesRangeTracker {
3490b57cec5SDimitry Andric public:
FreePagesRangeTracker(ReleaseRecorderT & Recorder)350bdd1243dSDimitry Andric   explicit FreePagesRangeTracker(ReleaseRecorderT &Recorder)
3510b57cec5SDimitry Andric       : Recorder(Recorder), PageSizeLog(getLog2(getPageSizeCached())) {}
3520b57cec5SDimitry Andric 
processNextPage(bool Released)353bdd1243dSDimitry Andric   void processNextPage(bool Released) {
354bdd1243dSDimitry Andric     if (Released) {
3550b57cec5SDimitry Andric       if (!InRange) {
3560b57cec5SDimitry Andric         CurrentRangeStatePage = CurrentPage;
3570b57cec5SDimitry Andric         InRange = true;
3580b57cec5SDimitry Andric       }
3590b57cec5SDimitry Andric     } else {
3600b57cec5SDimitry Andric       closeOpenedRange();
3610b57cec5SDimitry Andric     }
3620b57cec5SDimitry Andric     CurrentPage++;
3630b57cec5SDimitry Andric   }
3640b57cec5SDimitry Andric 
skipPages(uptr N)365e8d8bef9SDimitry Andric   void skipPages(uptr N) {
366e8d8bef9SDimitry Andric     closeOpenedRange();
367e8d8bef9SDimitry Andric     CurrentPage += N;
368e8d8bef9SDimitry Andric   }
369e8d8bef9SDimitry Andric 
finish()3700b57cec5SDimitry Andric   void finish() { closeOpenedRange(); }
3710b57cec5SDimitry Andric 
3720b57cec5SDimitry Andric private:
closeOpenedRange()3730b57cec5SDimitry Andric   void closeOpenedRange() {
3740b57cec5SDimitry Andric     if (InRange) {
375bdd1243dSDimitry Andric       Recorder.releasePageRangeToOS((CurrentRangeStatePage << PageSizeLog),
3760b57cec5SDimitry Andric                                     (CurrentPage << PageSizeLog));
3770b57cec5SDimitry Andric       InRange = false;
3780b57cec5SDimitry Andric     }
3790b57cec5SDimitry Andric   }
3800b57cec5SDimitry Andric 
381bdd1243dSDimitry Andric   ReleaseRecorderT &Recorder;
3820b57cec5SDimitry Andric   const uptr PageSizeLog;
3830b57cec5SDimitry Andric   bool InRange = false;
3840b57cec5SDimitry Andric   uptr CurrentPage = 0;
3850b57cec5SDimitry Andric   uptr CurrentRangeStatePage = 0;
3860b57cec5SDimitry Andric };
3870b57cec5SDimitry Andric 
388bdd1243dSDimitry Andric struct PageReleaseContext {
38906c3fb27SDimitry Andric   PageReleaseContext(uptr BlockSize, uptr NumberOfRegions, uptr ReleaseSize,
39006c3fb27SDimitry Andric                      uptr ReleaseOffset = 0)
BlockSizePageReleaseContext39106c3fb27SDimitry Andric       : BlockSize(BlockSize), NumberOfRegions(NumberOfRegions) {
392bdd1243dSDimitry Andric     PageSize = getPageSizeCached();
3930b57cec5SDimitry Andric     if (BlockSize <= PageSize) {
3940b57cec5SDimitry Andric       if (PageSize % BlockSize == 0) {
3950b57cec5SDimitry Andric         // Same number of chunks per page, no cross overs.
3960b57cec5SDimitry Andric         FullPagesBlockCountMax = PageSize / BlockSize;
3970b57cec5SDimitry Andric         SameBlockCountPerPage = true;
3980b57cec5SDimitry Andric       } else if (BlockSize % (PageSize % BlockSize) == 0) {
3990b57cec5SDimitry Andric         // Some chunks are crossing page boundaries, which means that the page
4000b57cec5SDimitry Andric         // contains one or two partial chunks, but all pages contain the same
4010b57cec5SDimitry Andric         // number of chunks.
4020b57cec5SDimitry Andric         FullPagesBlockCountMax = PageSize / BlockSize + 1;
4030b57cec5SDimitry Andric         SameBlockCountPerPage = true;
4040b57cec5SDimitry Andric       } else {
4050b57cec5SDimitry Andric         // Some chunks are crossing page boundaries, which means that the page
4060b57cec5SDimitry Andric         // contains one or two partial chunks.
4070b57cec5SDimitry Andric         FullPagesBlockCountMax = PageSize / BlockSize + 2;
4080b57cec5SDimitry Andric         SameBlockCountPerPage = false;
4090b57cec5SDimitry Andric       }
4100b57cec5SDimitry Andric     } else {
4110b57cec5SDimitry Andric       if (BlockSize % PageSize == 0) {
4120b57cec5SDimitry Andric         // One chunk covers multiple pages, no cross overs.
4130b57cec5SDimitry Andric         FullPagesBlockCountMax = 1;
4140b57cec5SDimitry Andric         SameBlockCountPerPage = true;
4150b57cec5SDimitry Andric       } else {
4160b57cec5SDimitry Andric         // One chunk covers multiple pages, Some chunks are crossing page
4170b57cec5SDimitry Andric         // boundaries. Some pages contain one chunk, some contain two.
4180b57cec5SDimitry Andric         FullPagesBlockCountMax = 2;
4190b57cec5SDimitry Andric         SameBlockCountPerPage = false;
4200b57cec5SDimitry Andric       }
4210b57cec5SDimitry Andric     }
4220b57cec5SDimitry Andric 
42306c3fb27SDimitry Andric     // TODO: For multiple regions, it's more complicated to support partial
42406c3fb27SDimitry Andric     // region marking (which includes the complexity of how to handle the last
42506c3fb27SDimitry Andric     // block in a region). We may consider this after markFreeBlocks() accepts
42606c3fb27SDimitry Andric     // only free blocks from the same region.
42706c3fb27SDimitry Andric     if (NumberOfRegions != 1)
42806c3fb27SDimitry Andric       DCHECK_EQ(ReleaseOffset, 0U);
42906c3fb27SDimitry Andric 
43006c3fb27SDimitry Andric     PagesCount = roundUp(ReleaseSize, PageSize) / PageSize;
431bdd1243dSDimitry Andric     PageSizeLog = getLog2(PageSize);
43206c3fb27SDimitry Andric     ReleasePageOffset = ReleaseOffset >> PageSizeLog;
433bdd1243dSDimitry Andric   }
4340b57cec5SDimitry Andric 
435bdd1243dSDimitry Andric   // PageMap is lazily allocated when markFreeBlocks() is invoked.
hasBlockMarkedPageReleaseContext436bdd1243dSDimitry Andric   bool hasBlockMarked() const {
437bdd1243dSDimitry Andric     return PageMap.isAllocated();
438bdd1243dSDimitry Andric   }
439bdd1243dSDimitry Andric 
ensurePageMapAllocatedPageReleaseContext44006c3fb27SDimitry Andric   bool ensurePageMapAllocated() {
441bdd1243dSDimitry Andric     if (PageMap.isAllocated())
44206c3fb27SDimitry Andric       return true;
443bdd1243dSDimitry Andric     PageMap.reset(NumberOfRegions, PagesCount, FullPagesBlockCountMax);
44406c3fb27SDimitry Andric     // TODO: Log some message when we fail on PageMap allocation.
44506c3fb27SDimitry Andric     return PageMap.isAllocated();
44606c3fb27SDimitry Andric   }
44706c3fb27SDimitry Andric 
44806c3fb27SDimitry Andric   // Mark all the blocks in the given range [From, to). Instead of visiting all
44906c3fb27SDimitry Andric   // the blocks, we will just mark the page as all counted. Note the `From` and
45006c3fb27SDimitry Andric   // `To` has to be page aligned but with one exception, if `To` is equal to the
45106c3fb27SDimitry Andric   // RegionSize, it's not necessary to be aligned with page size.
markRangeAsAllCountedPageReleaseContext45206c3fb27SDimitry Andric   bool markRangeAsAllCounted(uptr From, uptr To, uptr Base,
45306c3fb27SDimitry Andric                              const uptr RegionIndex, const uptr RegionSize) {
45406c3fb27SDimitry Andric     DCHECK_LT(From, To);
45506c3fb27SDimitry Andric     DCHECK_LE(To, Base + RegionSize);
45606c3fb27SDimitry Andric     DCHECK_EQ(From % PageSize, 0U);
45706c3fb27SDimitry Andric     DCHECK_LE(To - From, RegionSize);
45806c3fb27SDimitry Andric 
45906c3fb27SDimitry Andric     if (!ensurePageMapAllocated())
46006c3fb27SDimitry Andric       return false;
46106c3fb27SDimitry Andric 
46206c3fb27SDimitry Andric     uptr FromInRegion = From - Base;
46306c3fb27SDimitry Andric     uptr ToInRegion = To - Base;
46406c3fb27SDimitry Andric     uptr FirstBlockInRange = roundUpSlow(FromInRegion, BlockSize);
46506c3fb27SDimitry Andric 
46606c3fb27SDimitry Andric     // The straddling block sits across entire range.
46706c3fb27SDimitry Andric     if (FirstBlockInRange >= ToInRegion)
46806c3fb27SDimitry Andric       return true;
46906c3fb27SDimitry Andric 
47006c3fb27SDimitry Andric     // First block may not sit at the first pape in the range, move
47106c3fb27SDimitry Andric     // `FromInRegion` to the first block page.
47206c3fb27SDimitry Andric     FromInRegion = roundDown(FirstBlockInRange, PageSize);
47306c3fb27SDimitry Andric 
47406c3fb27SDimitry Andric     // When The first block is not aligned to the range boundary, which means
47506c3fb27SDimitry Andric     // there is a block sitting acorss `From`, that looks like,
47606c3fb27SDimitry Andric     //
47706c3fb27SDimitry Andric     //   From                                             To
47806c3fb27SDimitry Andric     //     V                                               V
47906c3fb27SDimitry Andric     //     +-----------------------------------------------+
48006c3fb27SDimitry Andric     //  +-----+-----+-----+-----+
48106c3fb27SDimitry Andric     //  |     |     |     |     | ...
48206c3fb27SDimitry Andric     //  +-----+-----+-----+-----+
48306c3fb27SDimitry Andric     //     |-    first page     -||-    second page    -||- ...
48406c3fb27SDimitry Andric     //
48506c3fb27SDimitry Andric     // Therefore, we can't just mark the first page as all counted. Instead, we
48606c3fb27SDimitry Andric     // increment the number of blocks in the first page in the page map and
48706c3fb27SDimitry Andric     // then round up the `From` to the next page.
48806c3fb27SDimitry Andric     if (FirstBlockInRange != FromInRegion) {
48906c3fb27SDimitry Andric       DCHECK_GT(FromInRegion + PageSize, FirstBlockInRange);
49006c3fb27SDimitry Andric       uptr NumBlocksInFirstPage =
49106c3fb27SDimitry Andric           (FromInRegion + PageSize - FirstBlockInRange + BlockSize - 1) /
49206c3fb27SDimitry Andric           BlockSize;
49306c3fb27SDimitry Andric       PageMap.incN(RegionIndex, getPageIndex(FromInRegion),
49406c3fb27SDimitry Andric                    NumBlocksInFirstPage);
49506c3fb27SDimitry Andric       FromInRegion = roundUp(FromInRegion + 1, PageSize);
49606c3fb27SDimitry Andric     }
49706c3fb27SDimitry Andric 
49806c3fb27SDimitry Andric     uptr LastBlockInRange = roundDownSlow(ToInRegion - 1, BlockSize);
49906c3fb27SDimitry Andric 
50006c3fb27SDimitry Andric     // Note that LastBlockInRange may be smaller than `FromInRegion` at this
50106c3fb27SDimitry Andric     // point because it may contain only one block in the range.
50206c3fb27SDimitry Andric 
50306c3fb27SDimitry Andric     // When the last block sits across `To`, we can't just mark the pages
50406c3fb27SDimitry Andric     // occupied by the last block as all counted. Instead, we increment the
50506c3fb27SDimitry Andric     // counters of those pages by 1. The exception is that if it's the last
50606c3fb27SDimitry Andric     // block in the region, it's fine to mark those pages as all counted.
50706c3fb27SDimitry Andric     if (LastBlockInRange + BlockSize != RegionSize) {
50806c3fb27SDimitry Andric       DCHECK_EQ(ToInRegion % PageSize, 0U);
50906c3fb27SDimitry Andric       // The case below is like,
51006c3fb27SDimitry Andric       //
51106c3fb27SDimitry Andric       //   From                                      To
51206c3fb27SDimitry Andric       //     V                                        V
51306c3fb27SDimitry Andric       //     +----------------------------------------+
51406c3fb27SDimitry Andric       //                          +-----+-----+-----+-----+
51506c3fb27SDimitry Andric       //                          |     |     |     |     | ...
51606c3fb27SDimitry Andric       //                          +-----+-----+-----+-----+
51706c3fb27SDimitry Andric       //                    ... -||-    last page    -||-    next page    -|
51806c3fb27SDimitry Andric       //
51906c3fb27SDimitry Andric       // The last block is not aligned to `To`, we need to increment the
52006c3fb27SDimitry Andric       // counter of `next page` by 1.
52106c3fb27SDimitry Andric       if (LastBlockInRange + BlockSize != ToInRegion) {
52206c3fb27SDimitry Andric         PageMap.incRange(RegionIndex, getPageIndex(ToInRegion),
52306c3fb27SDimitry Andric                          getPageIndex(LastBlockInRange + BlockSize - 1));
52406c3fb27SDimitry Andric       }
52506c3fb27SDimitry Andric     } else {
52606c3fb27SDimitry Andric       ToInRegion = RegionSize;
52706c3fb27SDimitry Andric     }
52806c3fb27SDimitry Andric 
52906c3fb27SDimitry Andric     // After handling the first page and the last block, it's safe to mark any
53006c3fb27SDimitry Andric     // page in between the range [From, To).
53106c3fb27SDimitry Andric     if (FromInRegion < ToInRegion) {
53206c3fb27SDimitry Andric       PageMap.setAsAllCountedRange(RegionIndex, getPageIndex(FromInRegion),
53306c3fb27SDimitry Andric                                    getPageIndex(ToInRegion - 1));
53406c3fb27SDimitry Andric     }
53506c3fb27SDimitry Andric 
53606c3fb27SDimitry Andric     return true;
537bdd1243dSDimitry Andric   }
538bdd1243dSDimitry Andric 
539bdd1243dSDimitry Andric   template <class TransferBatchT, typename DecompactPtrT>
markFreeBlocksInRegionPageReleaseContext54006c3fb27SDimitry Andric   bool markFreeBlocksInRegion(const IntrusiveList<TransferBatchT> &FreeList,
54106c3fb27SDimitry Andric                               DecompactPtrT DecompactPtr, const uptr Base,
54206c3fb27SDimitry Andric                               const uptr RegionIndex, const uptr RegionSize,
54306c3fb27SDimitry Andric                               bool MayContainLastBlockInRegion) {
54406c3fb27SDimitry Andric     if (!ensurePageMapAllocated())
54506c3fb27SDimitry Andric       return false;
54606c3fb27SDimitry Andric 
54706c3fb27SDimitry Andric     if (MayContainLastBlockInRegion) {
54806c3fb27SDimitry Andric       const uptr LastBlockInRegion =
54906c3fb27SDimitry Andric           ((RegionSize / BlockSize) - 1U) * BlockSize;
55006c3fb27SDimitry Andric       // The last block in a region may not use the entire page, we mark the
55106c3fb27SDimitry Andric       // following "pretend" memory block(s) as free in advance.
55206c3fb27SDimitry Andric       //
55306c3fb27SDimitry Andric       //     Region Boundary
55406c3fb27SDimitry Andric       //         v
55506c3fb27SDimitry Andric       //  -----+-----------------------+
55606c3fb27SDimitry Andric       //       |      Last Page        | <- Rounded Region Boundary
55706c3fb27SDimitry Andric       //  -----+-----------------------+
55806c3fb27SDimitry Andric       //   |-----||- trailing blocks  -|
55906c3fb27SDimitry Andric       //      ^
56006c3fb27SDimitry Andric       //   last block
56106c3fb27SDimitry Andric       const uptr RoundedRegionSize = roundUp(RegionSize, PageSize);
56206c3fb27SDimitry Andric       const uptr TrailingBlockBase = LastBlockInRegion + BlockSize;
56306c3fb27SDimitry Andric       // If the difference between `RoundedRegionSize` and
56406c3fb27SDimitry Andric       // `TrailingBlockBase` is larger than a page, that implies the reported
56506c3fb27SDimitry Andric       // `RegionSize` may not be accurate.
56606c3fb27SDimitry Andric       DCHECK_LT(RoundedRegionSize - TrailingBlockBase, PageSize);
56706c3fb27SDimitry Andric 
56806c3fb27SDimitry Andric       // Only the last page touched by the last block needs to mark the trailing
56906c3fb27SDimitry Andric       // blocks. Note that if the last "pretend" block straddles the boundary,
57006c3fb27SDimitry Andric       // we still have to count it in so that the logic of counting the number
57106c3fb27SDimitry Andric       // of blocks on a page is consistent.
57206c3fb27SDimitry Andric       uptr NumTrailingBlocks =
57306c3fb27SDimitry Andric           (roundUpSlow(RoundedRegionSize - TrailingBlockBase, BlockSize) +
57406c3fb27SDimitry Andric            BlockSize - 1) /
57506c3fb27SDimitry Andric           BlockSize;
57606c3fb27SDimitry Andric       if (NumTrailingBlocks > 0) {
57706c3fb27SDimitry Andric         PageMap.incN(RegionIndex, getPageIndex(TrailingBlockBase),
57806c3fb27SDimitry Andric                      NumTrailingBlocks);
57906c3fb27SDimitry Andric       }
58006c3fb27SDimitry Andric     }
5810b57cec5SDimitry Andric 
5820b57cec5SDimitry Andric     // Iterate over free chunks and count how many free chunks affect each
5830b57cec5SDimitry Andric     // allocated page.
5840b57cec5SDimitry Andric     if (BlockSize <= PageSize && PageSize % BlockSize == 0) {
5850b57cec5SDimitry Andric       // Each chunk affects one page only.
586480093f4SDimitry Andric       for (const auto &It : FreeList) {
587bdd1243dSDimitry Andric         for (u16 I = 0; I < It.getCount(); I++) {
58806c3fb27SDimitry Andric           const uptr PInRegion = DecompactPtr(It.get(I)) - Base;
58906c3fb27SDimitry Andric           DCHECK_LT(PInRegion, RegionSize);
59006c3fb27SDimitry Andric           PageMap.inc(RegionIndex, getPageIndex(PInRegion));
5910b57cec5SDimitry Andric         }
5920b57cec5SDimitry Andric       }
5930b57cec5SDimitry Andric     } else {
5940b57cec5SDimitry Andric       // In all other cases chunks might affect more than one page.
595e8d8bef9SDimitry Andric       DCHECK_GE(RegionSize, BlockSize);
596480093f4SDimitry Andric       for (const auto &It : FreeList) {
597bdd1243dSDimitry Andric         for (u16 I = 0; I < It.getCount(); I++) {
59806c3fb27SDimitry Andric           const uptr PInRegion = DecompactPtr(It.get(I)) - Base;
59906c3fb27SDimitry Andric           PageMap.incRange(RegionIndex, getPageIndex(PInRegion),
60006c3fb27SDimitry Andric                            getPageIndex(PInRegion + BlockSize - 1));
601e8d8bef9SDimitry Andric         }
6020b57cec5SDimitry Andric       }
603bdd1243dSDimitry Andric     }
604bdd1243dSDimitry Andric 
60506c3fb27SDimitry Andric     return true;
60606c3fb27SDimitry Andric   }
60706c3fb27SDimitry Andric 
getPageIndexPageReleaseContext60806c3fb27SDimitry Andric   uptr getPageIndex(uptr P) { return (P >> PageSizeLog) - ReleasePageOffset; }
getReleaseOffsetPageReleaseContext60906c3fb27SDimitry Andric   uptr getReleaseOffset() { return ReleasePageOffset << PageSizeLog; }
61006c3fb27SDimitry Andric 
611bdd1243dSDimitry Andric   uptr BlockSize;
612bdd1243dSDimitry Andric   uptr NumberOfRegions;
61306c3fb27SDimitry Andric   // For partial region marking, some pages in front are not needed to be
61406c3fb27SDimitry Andric   // counted.
61506c3fb27SDimitry Andric   uptr ReleasePageOffset;
616bdd1243dSDimitry Andric   uptr PageSize;
617bdd1243dSDimitry Andric   uptr PagesCount;
618bdd1243dSDimitry Andric   uptr PageSizeLog;
619bdd1243dSDimitry Andric   uptr FullPagesBlockCountMax;
620bdd1243dSDimitry Andric   bool SameBlockCountPerPage;
621bdd1243dSDimitry Andric   RegionPageMap PageMap;
622bdd1243dSDimitry Andric };
623bdd1243dSDimitry Andric 
624bdd1243dSDimitry Andric // Try to release the page which doesn't have any in-used block, i.e., they are
625bdd1243dSDimitry Andric // all free blocks. The `PageMap` will record the number of free blocks in each
626bdd1243dSDimitry Andric // page.
627bdd1243dSDimitry Andric template <class ReleaseRecorderT, typename SkipRegionT>
628bdd1243dSDimitry Andric NOINLINE void
releaseFreeMemoryToOS(PageReleaseContext & Context,ReleaseRecorderT & Recorder,SkipRegionT SkipRegion)629bdd1243dSDimitry Andric releaseFreeMemoryToOS(PageReleaseContext &Context,
630bdd1243dSDimitry Andric                       ReleaseRecorderT &Recorder, SkipRegionT SkipRegion) {
631bdd1243dSDimitry Andric   const uptr PageSize = Context.PageSize;
632bdd1243dSDimitry Andric   const uptr BlockSize = Context.BlockSize;
633bdd1243dSDimitry Andric   const uptr PagesCount = Context.PagesCount;
634bdd1243dSDimitry Andric   const uptr NumberOfRegions = Context.NumberOfRegions;
63506c3fb27SDimitry Andric   const uptr ReleasePageOffset = Context.ReleasePageOffset;
636bdd1243dSDimitry Andric   const uptr FullPagesBlockCountMax = Context.FullPagesBlockCountMax;
637bdd1243dSDimitry Andric   const bool SameBlockCountPerPage = Context.SameBlockCountPerPage;
638bdd1243dSDimitry Andric   RegionPageMap &PageMap = Context.PageMap;
6390b57cec5SDimitry Andric 
6400b57cec5SDimitry Andric   // Iterate over pages detecting ranges of pages with chunk Counters equal
6410b57cec5SDimitry Andric   // to the expected number of chunks for the particular page.
6420b57cec5SDimitry Andric   FreePagesRangeTracker<ReleaseRecorderT> RangeTracker(Recorder);
6430b57cec5SDimitry Andric   if (SameBlockCountPerPage) {
6440b57cec5SDimitry Andric     // Fast path, every page has the same number of chunks affecting it.
645e8d8bef9SDimitry Andric     for (uptr I = 0; I < NumberOfRegions; I++) {
646e8d8bef9SDimitry Andric       if (SkipRegion(I)) {
647e8d8bef9SDimitry Andric         RangeTracker.skipPages(PagesCount);
648e8d8bef9SDimitry Andric         continue;
649e8d8bef9SDimitry Andric       }
650bdd1243dSDimitry Andric       for (uptr J = 0; J < PagesCount; J++) {
65106c3fb27SDimitry Andric         const bool CanRelease =
65206c3fb27SDimitry Andric             PageMap.updateAsAllCountedIf(I, J, FullPagesBlockCountMax);
653bdd1243dSDimitry Andric         RangeTracker.processNextPage(CanRelease);
654bdd1243dSDimitry Andric       }
655e8d8bef9SDimitry Andric     }
6560b57cec5SDimitry Andric   } else {
6570b57cec5SDimitry Andric     // Slow path, go through the pages keeping count how many chunks affect
6580b57cec5SDimitry Andric     // each page.
6590b57cec5SDimitry Andric     const uptr Pn = BlockSize < PageSize ? PageSize / BlockSize : 1;
6600b57cec5SDimitry Andric     const uptr Pnc = Pn * BlockSize;
6610b57cec5SDimitry Andric     // The idea is to increment the current page pointer by the first chunk
6620b57cec5SDimitry Andric     // size, middle portion size (the portion of the page covered by chunks
6630b57cec5SDimitry Andric     // except the first and the last one) and then the last chunk size, adding
6640b57cec5SDimitry Andric     // up the number of chunks on the current page and checking on every step
6650b57cec5SDimitry Andric     // whether the page boundary was crossed.
666e8d8bef9SDimitry Andric     for (uptr I = 0; I < NumberOfRegions; I++) {
667e8d8bef9SDimitry Andric       if (SkipRegion(I)) {
668e8d8bef9SDimitry Andric         RangeTracker.skipPages(PagesCount);
669e8d8bef9SDimitry Andric         continue;
670e8d8bef9SDimitry Andric       }
6710b57cec5SDimitry Andric       uptr PrevPageBoundary = 0;
6720b57cec5SDimitry Andric       uptr CurrentBoundary = 0;
67306c3fb27SDimitry Andric       if (ReleasePageOffset > 0) {
67406c3fb27SDimitry Andric         PrevPageBoundary = ReleasePageOffset * PageSize;
67506c3fb27SDimitry Andric         CurrentBoundary = roundUpSlow(PrevPageBoundary, BlockSize);
67606c3fb27SDimitry Andric       }
677e8d8bef9SDimitry Andric       for (uptr J = 0; J < PagesCount; J++) {
6780b57cec5SDimitry Andric         const uptr PageBoundary = PrevPageBoundary + PageSize;
6790b57cec5SDimitry Andric         uptr BlocksPerPage = Pn;
6800b57cec5SDimitry Andric         if (CurrentBoundary < PageBoundary) {
6810b57cec5SDimitry Andric           if (CurrentBoundary > PrevPageBoundary)
6820b57cec5SDimitry Andric             BlocksPerPage++;
6830b57cec5SDimitry Andric           CurrentBoundary += Pnc;
6840b57cec5SDimitry Andric           if (CurrentBoundary < PageBoundary) {
6850b57cec5SDimitry Andric             BlocksPerPage++;
6860b57cec5SDimitry Andric             CurrentBoundary += BlockSize;
6870b57cec5SDimitry Andric           }
6880b57cec5SDimitry Andric         }
6890b57cec5SDimitry Andric         PrevPageBoundary = PageBoundary;
69006c3fb27SDimitry Andric         const bool CanRelease =
69106c3fb27SDimitry Andric             PageMap.updateAsAllCountedIf(I, J, BlocksPerPage);
692bdd1243dSDimitry Andric         RangeTracker.processNextPage(CanRelease);
693e8d8bef9SDimitry Andric       }
6940b57cec5SDimitry Andric     }
6950b57cec5SDimitry Andric   }
6960b57cec5SDimitry Andric   RangeTracker.finish();
6970b57cec5SDimitry Andric }
6980b57cec5SDimitry Andric 
6990b57cec5SDimitry Andric } // namespace scudo
7000b57cec5SDimitry Andric 
7010b57cec5SDimitry Andric #endif // SCUDO_RELEASE_H_
702