| /** | |
| * A sorted array to quickly lookup pools. | |
| * | |
| * Copyright: D Language Foundation 2001 - 2021 | |
| * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
| * Authors: Walter Bright, David Friedman, Sean Kelly, Martin Nowak | |
| */ | |
| module core.internal.gc.pooltable; | |
| static import cstdlib = core.stdc.stdlib; | |
| struct PoolTable(Pool) | |
| { | |
| import core.stdc.string : memmove; | |
| void Dtor() nothrow @nogc | |
| { | |
| cstdlib.free(pools); | |
| pools = null; | |
| npools = 0; | |
| } | |
| bool insert(Pool* pool) nothrow @nogc | |
| { | |
| auto newpools = cast(Pool **)cstdlib.realloc(pools, (npools + 1) * pools[0].sizeof); | |
| if (!newpools) | |
| return false; | |
| pools = newpools; | |
| // Sort pool into newpooltable[] | |
| size_t i; | |
| for (; i < npools; ++i) | |
| { | |
| if (pool.baseAddr < pools[i].baseAddr) | |
| break; | |
| } | |
| if (i != npools) | |
| memmove(pools + i + 1, pools + i, (npools - i) * pools[0].sizeof); | |
| pools[i] = pool; | |
| ++npools; | |
| foreach (idx; i .. npools) | |
| pools[idx].ptIndex = idx; | |
| _minAddr = pools[0].baseAddr; | |
| _maxAddr = pools[npools - 1].topAddr; | |
| return true; | |
| } | |
| @property size_t length() const scope @safe pure nothrow @nogc | |
| { | |
| return npools; | |
| } | |
| ref inout(Pool*) opIndex(size_t idx) inout return @trusted pure nothrow @nogc | |
| in { assert(idx < length); } | |
| do | |
| { | |
| return pools[idx]; | |
| } | |
| inout(Pool*)[] opSlice(size_t a, size_t b) inout return @trusted pure nothrow @nogc | |
| in { assert(a <= length && b <= length); } | |
| do | |
| { | |
| return pools[a .. b]; | |
| } | |
| /// Returns: A slice over all pools in this `PoolTable` | |
| inout(Pool*)[] opSlice() inout return @trusted pure nothrow @nogc | |
| { | |
| return this.pools[0 .. this.length]; | |
| } | |
| alias opDollar = length; | |
| /** | |
| * Find Pool that pointer is in. | |
| * Return null if not in a Pool. | |
| * Assume pooltable[] is sorted. | |
| */ | |
| Pool *findPool(void *p) nothrow @nogc | |
| { | |
| if (p >= minAddr && p < maxAddr) | |
| { | |
| assert(npools); | |
| // let dmd allocate a register for this.pools | |
| auto pools = this.pools; | |
| if (npools == 1) | |
| return pools[0]; | |
| /* The pooltable[] is sorted by address, so do a binary search | |
| */ | |
| size_t low = 0; | |
| size_t high = npools - 1; | |
| while (low <= high) | |
| { | |
| size_t mid = (low + high) >> 1; | |
| auto pool = pools[mid]; | |
| if (p < pool.baseAddr) | |
| high = mid - 1; | |
| else if (p >= pool.topAddr) | |
| low = mid + 1; | |
| else | |
| return pool; | |
| } | |
| } | |
| return null; | |
| } | |
| // semi-stable partition, returns right half for which pred is false | |
| Pool*[] minimize() pure nothrow @nogc | |
| { | |
| static void swap(ref Pool* a, ref Pool* b) | |
| { | |
| auto c = a; a = b; b = c; | |
| } | |
| size_t i; | |
| // find first bad entry | |
| for (; i < npools; ++i) | |
| if (pools[i].isFree) break; | |
| // move good in front of bad entries | |
| size_t j = i + 1; | |
| for (; j < npools; ++j) | |
| { | |
| if (!pools[j].isFree) // keep | |
| { | |
| swap(pools[i], pools[j]); | |
| pools[i].ptIndex = i; | |
| ++i; | |
| } | |
| } | |
| // npooltable[0 .. i] => used pools | |
| // npooltable[i .. npools] => free pools | |
| if (i) | |
| { | |
| _minAddr = pools[0].baseAddr; | |
| _maxAddr = pools[i - 1].topAddr; | |
| } | |
| else | |
| { | |
| _minAddr = _maxAddr = null; | |
| } | |
| immutable len = npools; | |
| npools = i; | |
| // return freed pools to the caller | |
| return pools[npools .. len]; | |
| } | |
| void Invariant() const nothrow @nogc | |
| { | |
| if (!npools) return; | |
| foreach (i; 0 .. npools) | |
| assert(pools[i].ptIndex == i); | |
| foreach (i, pool; pools[0 .. npools - 1]) | |
| assert(pool.baseAddr < pools[i + 1].baseAddr); | |
| assert(_minAddr == pools[0].baseAddr); | |
| assert(_maxAddr == pools[npools - 1].topAddr); | |
| } | |
| @property const(void)* minAddr() const @safe pure nothrow @nogc { return _minAddr; } | |
| @property const(void)* maxAddr() const @safe pure nothrow @nogc { return _maxAddr; } | |
| package: | |
| Pool** pools; | |
| size_t npools; | |
| void* _minAddr, _maxAddr; | |
| } | |
| unittest | |
| { | |
| import core.internal.gc.impl.conservative.gc : PAGESIZE; | |
| enum NPOOLS = 6; | |
| enum NPAGES = 10; | |
| static struct MockPool | |
| { | |
| byte* baseAddr, topAddr; | |
| size_t freepages, npages, ptIndex; | |
| @property bool isFree() const scope pure nothrow @nogc { return freepages == npages; } | |
| } | |
| PoolTable!MockPool pooltable; | |
| void reset() | |
| { | |
| foreach (ref pool; pooltable[0 .. $]) | |
| pool.freepages = pool.npages; | |
| pooltable.minimize(); | |
| assert(pooltable.length == 0); | |
| foreach (i; 0 .. NPOOLS) | |
| { | |
| auto pool = cast(MockPool*)cstdlib.malloc(MockPool.sizeof); | |
| *pool = MockPool.init; | |
| assert(pooltable.insert(pool)); | |
| } | |
| } | |
| void usePools() | |
| { | |
| foreach (pool; pooltable[0 .. $]) | |
| { | |
| pool.npages = NPAGES; | |
| pool.freepages = NPAGES / 2; | |
| } | |
| } | |
| // all pools are free | |
| reset(); | |
| assert(pooltable.length == NPOOLS); | |
| auto freed = pooltable.minimize(); | |
| assert(freed.length == NPOOLS); | |
| assert(pooltable.length == 0); | |
| // all pools used | |
| reset(); | |
| usePools(); | |
| assert(pooltable.length == NPOOLS); | |
| freed = pooltable.minimize(); | |
| assert(freed.length == 0); | |
| assert(pooltable.length == NPOOLS); | |
| // preserves order of used pools | |
| reset(); | |
| usePools(); | |
| { | |
| MockPool*[NPOOLS] opools = pooltable[0 .. NPOOLS]; | |
| // make the 2nd pool free | |
| pooltable[2].freepages = NPAGES; | |
| pooltable.minimize(); | |
| assert(pooltable.length == NPOOLS - 1); | |
| assert(pooltable[0] == opools[0]); | |
| assert(pooltable[1] == opools[1]); | |
| assert(pooltable[2] == opools[3]); | |
| } | |
| // test that PoolTable reduces min/max address span | |
| reset(); | |
| usePools(); | |
| byte* base, top; | |
| { | |
| // fill with fake addresses | |
| size_t i; | |
| foreach (pool; pooltable[0 .. NPOOLS]) | |
| { | |
| pool.baseAddr = cast(byte*)(i++ * NPAGES * PAGESIZE); | |
| pool.topAddr = pool.baseAddr + NPAGES * PAGESIZE; | |
| } | |
| base = pooltable[0].baseAddr; | |
| top = pooltable[NPOOLS - 1].topAddr; | |
| } | |
| freed = pooltable.minimize(); | |
| assert(freed.length == 0); | |
| assert(pooltable.length == NPOOLS); | |
| assert(pooltable.minAddr == base); | |
| assert(pooltable.maxAddr == top); | |
| pooltable[NPOOLS - 1].freepages = NPAGES; | |
| pooltable[NPOOLS - 2].freepages = NPAGES; | |
| freed = pooltable.minimize(); | |
| assert(freed.length == 2); | |
| assert(pooltable.length == NPOOLS - 2); | |
| assert(pooltable.minAddr == base); | |
| assert(pooltable.maxAddr == pooltable[NPOOLS - 3].topAddr); | |
| pooltable[0].freepages = NPAGES; | |
| freed = pooltable.minimize(); | |
| assert(freed.length == 1); | |
| assert(pooltable.length == NPOOLS - 3); | |
| assert(pooltable.minAddr != base); | |
| assert(pooltable.minAddr == pooltable[0].baseAddr); | |
| assert(pooltable.maxAddr == pooltable[NPOOLS - 4].topAddr); | |
| // free all | |
| foreach (pool; pooltable[0 .. $]) | |
| pool.freepages = NPAGES; | |
| freed = pooltable.minimize(); | |
| assert(freed.length == NPOOLS - 3); | |
| assert(pooltable.length == 0); | |
| pooltable.Dtor(); | |
| } |