| // Written in the D programming language. | |
| /** | |
| Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/region.d) | |
| */ | |
| module std.experimental.allocator.building_blocks.region; | |
| import std.experimental.allocator.building_blocks.null_allocator; | |
| import std.experimental.allocator.common; | |
| import std.typecons : Flag, Yes, No; | |
| version (OSX) | |
| version = Darwin; | |
| else version (iOS) | |
| version = Darwin; | |
| else version (TVOS) | |
| version = Darwin; | |
| else version (WatchOS) | |
| version = Darwin; | |
| /** | |
| A `Region` allocator allocates memory straight from one contiguous chunk. | |
| There is no deallocation, and once the region is full, allocation requests | |
| return `null`. Therefore, `Region`s are often used (a) in conjunction with | |
| more sophisticated allocators; or (b) for batch-style very fast allocations | |
| that deallocate everything at once. | |
| The region only stores three pointers, corresponding to the current position in | |
| the store and the limits. One allocation entails rounding up the allocation | |
| size for alignment purposes, bumping the current pointer, and comparing it | |
| against the limit. | |
| `Region` deallocates the chunk of memory during destruction. | |
| The `minAlign` parameter establishes alignment. If $(D minAlign > 1), the | |
| sizes of all allocation requests are rounded up to a multiple of `minAlign`. | |
| Applications aiming at maximum speed may want to choose $(D minAlign = 1) and | |
| control alignment externally. | |
| */ | |
| struct Region(ParentAllocator, | |
| uint minAlign = platformAlignment, | |
| Flag!"growDownwards" growDownwards = No.growDownwards) | |
| { | |
| static assert(minAlign.isGoodStaticAlignment); | |
| static assert(ParentAllocator.alignment >= minAlign); | |
| import std.traits : hasMember; | |
| import std.typecons : Ternary; | |
| // state | |
| /** | |
| The _parent allocator. Depending on whether `ParentAllocator` holds state | |
| or not, this is a member variable or an alias for | |
| `ParentAllocator.instance`. | |
| */ | |
| static if (stateSize!ParentAllocator) | |
| { | |
| ParentAllocator parent; | |
| } | |
| else | |
| { | |
| alias parent = ParentAllocator.instance; | |
| } | |
| private BorrowedRegion!(minAlign, growDownwards) _impl; | |
| private void* roundedBegin() const pure nothrow @trusted @nogc | |
| { | |
| return _impl.roundedBegin; | |
| } | |
| private void* roundedEnd() const pure nothrow @trusted @nogc | |
| { | |
| return _impl.roundedEnd; | |
| } | |
| /** | |
| Constructs a region backed by a user-provided store. | |
| Assumes the memory was allocated with `ParentAllocator`. | |
| Params: | |
| store = User-provided store backing up the region. Assumed to have been | |
| allocated with `ParentAllocator`. | |
| n = Bytes to allocate using `ParentAllocator`. If `parent.allocate(n)` | |
| returns `null`, the region will be initialized as empty (correctly | |
| initialized but unable to allocate). | |
| */ | |
| this(ubyte[] store) pure nothrow @nogc | |
| { | |
| _impl = store; | |
| } | |
| /// Ditto | |
| static if (!stateSize!ParentAllocator) | |
| this(size_t n) | |
| { | |
| this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); | |
| } | |
| /// Ditto | |
| static if (stateSize!ParentAllocator) | |
| this(ParentAllocator parent, size_t n) | |
| { | |
| this.parent = parent; | |
| this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); | |
| } | |
| /* | |
| TODO: The postblit of `BasicRegion` should be disabled because such objects | |
| should not be copied around naively. | |
| */ | |
| /** | |
| If `ParentAllocator` defines `deallocate`, the region defines a destructor | |
| that uses `ParentAllocator.deallocate` to free the memory chunk. | |
| */ | |
| static if (hasMember!(ParentAllocator, "deallocate")) | |
| ~this() | |
| { | |
| with (_impl) parent.deallocate(_begin[0 .. _end - _begin]); | |
| } | |
| /** | |
| Rounds the given size to a multiple of the `alignment` | |
| */ | |
| size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc | |
| { | |
| return _impl.goodAllocSize(n); | |
| } | |
| /** | |
| Alignment offered. | |
| */ | |
| alias alignment = minAlign; | |
| /** | |
| Allocates `n` bytes of memory. The shortest path involves an alignment | |
| adjustment (if $(D alignment > 1)), an increment, and a comparison. | |
| Params: | |
| n = number of bytes to allocate | |
| Returns: | |
| A properly-aligned buffer of size `n` or `null` if request could not | |
| be satisfied. | |
| */ | |
| void[] allocate(size_t n) pure nothrow @trusted @nogc | |
| { | |
| return _impl.allocate(n); | |
| } | |
| /** | |
| Allocates `n` bytes of memory aligned at alignment `a`. | |
| Params: | |
| n = number of bytes to allocate | |
| a = alignment for the allocated block | |
| Returns: | |
| Either a suitable block of `n` bytes aligned at `a`, or `null`. | |
| */ | |
| void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc | |
| { | |
| return _impl.alignedAllocate(n, a); | |
| } | |
| /// Allocates and returns all memory available to this region. | |
| void[] allocateAll() pure nothrow @trusted @nogc | |
| { | |
| return _impl.allocateAll; | |
| } | |
| /** | |
| Expands an allocated block in place. Expansion will succeed only if the | |
| block is the last allocated. Defined only if `growDownwards` is | |
| `No.growDownwards`. | |
| */ | |
| static if (growDownwards == No.growDownwards) | |
| bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc | |
| { | |
| return _impl.expand(b, delta); | |
| } | |
| /** | |
| Deallocates `b`. This works only if `b` was obtained as the last call | |
| to `allocate`; otherwise (i.e. another allocation has occurred since) it | |
| does nothing. | |
| Params: | |
| b = Block previously obtained by a call to `allocate` against this | |
| allocator (`null` is allowed). | |
| */ | |
| bool deallocate(void[] b) pure nothrow @nogc | |
| { | |
| return _impl.deallocate(b); | |
| } | |
| /** | |
| Deallocates all memory allocated by this region, which can be subsequently | |
| reused for new allocations. | |
| */ | |
| bool deallocateAll() pure nothrow @nogc | |
| { | |
| return _impl.deallocateAll; | |
| } | |
| /** | |
| Queries whether `b` has been allocated with this region. | |
| Params: | |
| b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns | |
| `false`). | |
| Returns: | |
| `true` if `b` has been allocated with this region, `false` otherwise. | |
| */ | |
| Ternary owns(const void[] b) const pure nothrow @trusted @nogc | |
| { | |
| return _impl.owns(b); | |
| } | |
| /** | |
| Returns `Ternary.yes` if no memory has been allocated in this region, | |
| `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) | |
| */ | |
| Ternary empty() const pure nothrow @safe @nogc | |
| { | |
| return _impl.empty; | |
| } | |
| /// Nonstandard property that returns bytes available for allocation. | |
| size_t available() const @safe pure nothrow @nogc | |
| { | |
| return _impl.available; | |
| } | |
| } | |
| /// | |
| @system nothrow unittest | |
| { | |
| import std.algorithm.comparison : max; | |
| import std.experimental.allocator.building_blocks.allocator_list | |
| : AllocatorList; | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| import std.typecons : Ternary; | |
| // Create a scalable list of regions. Each gets at least 1MB at a time by | |
| // using malloc. | |
| auto batchAllocator = AllocatorList!( | |
| (size_t n) => Region!Mallocator(max(n, 1024 * 1024)) | |
| )(); | |
| assert(batchAllocator.empty == Ternary.yes); | |
| auto b = batchAllocator.allocate(101); | |
| assert(b.length == 101); | |
| assert(batchAllocator.empty == Ternary.no); | |
| // This will cause a second allocation | |
| b = batchAllocator.allocate(2 * 1024 * 1024); | |
| assert(b.length == 2 * 1024 * 1024); | |
| // Destructor will free the memory | |
| } | |
| @system nothrow @nogc unittest | |
| { | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| import std.typecons : Ternary; | |
| static void testAlloc(Allocator)(ref Allocator a) | |
| { | |
| assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); | |
| const b = a.allocate(101); | |
| assert(b.length == 101); | |
| assert((() nothrow @safe @nogc => a.owns(b))() == Ternary.yes); | |
| // Ensure deallocate inherits from parent allocators | |
| auto c = a.allocate(42); | |
| assert(c.length == 42); | |
| assert((() nothrow @nogc => a.deallocate(c))()); | |
| assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no); | |
| } | |
| // Create a 64 KB region allocated with malloc | |
| auto reg = Region!(Mallocator, Mallocator.alignment, | |
| Yes.growDownwards)(1024 * 64); | |
| testAlloc(reg); | |
| // Create a 64 KB shared region allocated with malloc | |
| auto sharedReg = SharedRegion!(Mallocator, Mallocator.alignment, | |
| Yes.growDownwards)(1024 * 64); | |
| testAlloc(sharedReg); | |
| } | |
| @system nothrow @nogc unittest | |
| { | |
| // test 'this(ubyte[] store)' constructed regions properly clean up | |
| // their inner storage after destruction | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| static shared struct LocalAllocator | |
| { | |
| nothrow @nogc: | |
| enum alignment = Mallocator.alignment; | |
| void[] buf; | |
| bool deallocate(void[] b) | |
| { | |
| assert(buf.ptr == b.ptr && buf.length == b.length); | |
| return true; | |
| } | |
| void[] allocate(size_t n) | |
| { | |
| return null; | |
| } | |
| } | |
| enum bufLen = 10 * Mallocator.alignment; | |
| void[] tmp = Mallocator.instance.allocate(bufLen); | |
| LocalAllocator a; | |
| a.buf = cast(typeof(a.buf)) tmp[1 .. $]; | |
| auto reg = Region!(LocalAllocator, Mallocator.alignment, | |
| Yes.growDownwards)(cast(ubyte[]) a.buf); | |
| auto sharedReg = SharedRegion!(LocalAllocator, Mallocator.alignment, | |
| Yes.growDownwards)(cast(ubyte[]) a.buf); | |
| reg.parent = a; | |
| sharedReg.parent = a; | |
| Mallocator.instance.deallocate(tmp); | |
| } | |
| version (StdUnittest) | |
| @system unittest | |
| { | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| testAllocator!(() => Region!(Mallocator)(1024 * 64)); | |
| testAllocator!(() => Region!(Mallocator, Mallocator.alignment, Yes.growDownwards)(1024 * 64)); | |
| testAllocator!(() => SharedRegion!(Mallocator)(1024 * 64)); | |
| testAllocator!(() => SharedRegion!(Mallocator, Mallocator.alignment, Yes.growDownwards)(1024 * 64)); | |
| } | |
| @system nothrow @nogc unittest | |
| { | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| auto reg = Region!(Mallocator)(1024 * 64); | |
| auto b = reg.allocate(101); | |
| assert(b.length == 101); | |
| assert((() pure nothrow @safe @nogc => reg.expand(b, 20))()); | |
| assert((() pure nothrow @safe @nogc => reg.expand(b, 73))()); | |
| assert((() pure nothrow @safe @nogc => !reg.expand(b, 1024 * 64))()); | |
| assert((() nothrow @nogc => reg.deallocateAll())()); | |
| } | |
| /** | |
| A `BorrowedRegion` allocates directly from a user-provided block of memory. | |
| Unlike a `Region`, a `BorrowedRegion` does not own the memory it allocates from | |
| and will not deallocate that memory upon destruction. Instead, it is the user's | |
| responsibility to ensure that the memory is properly disposed of. | |
| In all other respects, a `BorrowedRegion` behaves exactly like a `Region`. | |
| */ | |
| struct BorrowedRegion(uint minAlign = platformAlignment, | |
| Flag!"growDownwards" growDownwards = No.growDownwards) | |
| { | |
| static assert(minAlign.isGoodStaticAlignment); | |
| import std.typecons : Ternary; | |
| // state | |
| private void* _current, _begin, _end; | |
| private void* roundedBegin() const pure nothrow @trusted @nogc | |
| { | |
| return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); | |
| } | |
| private void* roundedEnd() const pure nothrow @trusted @nogc | |
| { | |
| return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); | |
| } | |
| /** | |
| Constructs a region backed by a user-provided store. | |
| Params: | |
| store = User-provided store backing up the region. | |
| */ | |
| this(ubyte[] store) pure nothrow @nogc | |
| { | |
| _begin = store.ptr; | |
| _end = store.ptr + store.length; | |
| static if (growDownwards) | |
| _current = roundedEnd(); | |
| else | |
| _current = roundedBegin(); | |
| } | |
| /* | |
| TODO: The postblit of `BorrowedRegion` should be disabled because such objects | |
| should not be copied around naively. | |
| */ | |
| /** | |
| Rounds the given size to a multiple of the `alignment` | |
| */ | |
| size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc | |
| { | |
| return n.roundUpToAlignment(alignment); | |
| } | |
| /** | |
| Alignment offered. | |
| */ | |
| alias alignment = minAlign; | |
| /** | |
| Allocates `n` bytes of memory. The shortest path involves an alignment | |
| adjustment (if $(D alignment > 1)), an increment, and a comparison. | |
| Params: | |
| n = number of bytes to allocate | |
| Returns: | |
| A properly-aligned buffer of size `n` or `null` if request could not | |
| be satisfied. | |
| */ | |
| void[] allocate(size_t n) pure nothrow @trusted @nogc | |
| { | |
| const rounded = goodAllocSize(n); | |
| if (n == 0 || rounded < n || available < rounded) return null; | |
| static if (growDownwards) | |
| { | |
| assert(available >= rounded); | |
| auto result = (_current - rounded)[0 .. n]; | |
| assert(result.ptr >= _begin); | |
| _current = result.ptr; | |
| assert(owns(result) == Ternary.yes); | |
| } | |
| else | |
| { | |
| auto result = _current[0 .. n]; | |
| _current += rounded; | |
| } | |
| return result; | |
| } | |
| /** | |
| Allocates `n` bytes of memory aligned at alignment `a`. | |
| Params: | |
| n = number of bytes to allocate | |
| a = alignment for the allocated block | |
| Returns: | |
| Either a suitable block of `n` bytes aligned at `a`, or `null`. | |
| */ | |
| void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc | |
| { | |
| import std.math.traits : isPowerOf2; | |
| assert(a.isPowerOf2); | |
| const rounded = goodAllocSize(n); | |
| if (n == 0 || rounded < n || available < rounded) return null; | |
| static if (growDownwards) | |
| { | |
| auto tmpCurrent = _current - rounded; | |
| auto result = tmpCurrent.alignDownTo(a); | |
| if (result <= tmpCurrent && result >= _begin) | |
| { | |
| _current = result; | |
| return cast(void[]) result[0 .. n]; | |
| } | |
| } | |
| else | |
| { | |
| // Just bump the pointer to the next good allocation | |
| auto newCurrent = _current.alignUpTo(a); | |
| if (newCurrent < _current || newCurrent > _end) | |
| return null; | |
| auto save = _current; | |
| _current = newCurrent; | |
| auto result = allocate(n); | |
| if (result.ptr) | |
| { | |
| assert(result.length == n); | |
| return result; | |
| } | |
| // Failed, rollback | |
| _current = save; | |
| } | |
| return null; | |
| } | |
| /// Allocates and returns all memory available to this region. | |
| void[] allocateAll() pure nothrow @trusted @nogc | |
| { | |
| static if (growDownwards) | |
| { | |
| auto result = _begin[0 .. available]; | |
| _current = _begin; | |
| } | |
| else | |
| { | |
| auto result = _current[0 .. available]; | |
| _current = _end; | |
| } | |
| return result; | |
| } | |
| /** | |
| Expands an allocated block in place. Expansion will succeed only if the | |
| block is the last allocated. Defined only if `growDownwards` is | |
| `No.growDownwards`. | |
| */ | |
| static if (growDownwards == No.growDownwards) | |
| bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc | |
| { | |
| assert(owns(b) == Ternary.yes || b is null); | |
| assert((() @trusted => b.ptr + b.length <= _current)() || b is null); | |
| if (b is null || delta == 0) return delta == 0; | |
| auto newLength = b.length + delta; | |
| if ((() @trusted => _current < b.ptr + b.length + alignment)()) | |
| { | |
| immutable currentGoodSize = this.goodAllocSize(b.length); | |
| immutable newGoodSize = this.goodAllocSize(newLength); | |
| immutable goodDelta = newGoodSize - currentGoodSize; | |
| // This was the last allocation! Allocate some more and we're done. | |
| if (goodDelta == 0 | |
| || (() @trusted => allocate(goodDelta).length == goodDelta)()) | |
| { | |
| b = (() @trusted => b.ptr[0 .. newLength])(); | |
| assert((() @trusted => _current < b.ptr + b.length + alignment)()); | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| Deallocates `b`. This works only if `b` was obtained as the last call | |
| to `allocate`; otherwise (i.e. another allocation has occurred since) it | |
| does nothing. | |
| Params: | |
| b = Block previously obtained by a call to `allocate` against this | |
| allocator (`null` is allowed). | |
| */ | |
| bool deallocate(void[] b) pure nothrow @nogc | |
| { | |
| assert(owns(b) == Ternary.yes || b.ptr is null); | |
| auto rounded = goodAllocSize(b.length); | |
| static if (growDownwards) | |
| { | |
| if (b.ptr == _current) | |
| { | |
| _current += rounded; | |
| return true; | |
| } | |
| } | |
| else | |
| { | |
| if (b.ptr + rounded == _current) | |
| { | |
| assert(b.ptr !is null || _current is null); | |
| _current = b.ptr; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| Deallocates all memory allocated by this region, which can be subsequently | |
| reused for new allocations. | |
| */ | |
| bool deallocateAll() pure nothrow @nogc | |
| { | |
| static if (growDownwards) | |
| { | |
| _current = roundedEnd(); | |
| } | |
| else | |
| { | |
| _current = roundedBegin(); | |
| } | |
| return true; | |
| } | |
| /** | |
| Queries whether `b` has been allocated with this region. | |
| Params: | |
| b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns | |
| `false`). | |
| Returns: | |
| `true` if `b` has been allocated with this region, `false` otherwise. | |
| */ | |
| Ternary owns(const void[] b) const pure nothrow @trusted @nogc | |
| { | |
| return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); | |
| } | |
| /** | |
| Returns `Ternary.yes` if no memory has been allocated in this region, | |
| `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) | |
| */ | |
| Ternary empty() const pure nothrow @safe @nogc | |
| { | |
| static if (growDownwards) | |
| return Ternary(_current == roundedEnd()); | |
| else | |
| return Ternary(_current == roundedBegin()); | |
| } | |
| /// Nonstandard property that returns bytes available for allocation. | |
| size_t available() const @safe pure nothrow @nogc | |
| { | |
| static if (growDownwards) | |
| { | |
| return _current - _begin; | |
| } | |
| else | |
| { | |
| return _end - _current; | |
| } | |
| } | |
| } | |
| /// | |
| @system nothrow @nogc unittest | |
| { | |
| import std.typecons : Ternary; | |
| ubyte[1024] store; | |
| auto myRegion = BorrowedRegion!(1)(store[]); | |
| assert(myRegion.empty == Ternary.yes); | |
| assert(myRegion.available == store.length); | |
| void[] b = myRegion.allocate(101); | |
| assert(b.length == 101); | |
| assert(myRegion.empty == Ternary.no); | |
| assert(myRegion.owns(b) == Ternary.yes); | |
| assert(myRegion.available == store.length - b.length); | |
| void[] b2 = myRegion.allocate(256); | |
| // Can only free the most recent allocation | |
| assert(myRegion.deallocate(b) == false); | |
| assert(myRegion.deallocate(b2) == true); | |
| myRegion.deallocateAll(); | |
| assert(myRegion.empty == Ternary.yes); | |
| } | |
| @system nothrow @nogc unittest | |
| { | |
| import std.experimental.allocator.mallocator : AlignedMallocator; | |
| import std.typecons : Ternary; | |
| ubyte[] buf = cast(ubyte[]) AlignedMallocator.instance.alignedAllocate(64, 64); | |
| auto reg = BorrowedRegion!(64, Yes.growDownwards)(buf); | |
| assert(reg.alignedAllocate(10, 32).length == 10); | |
| assert(!reg.available); | |
| } | |
| /** | |
| `InSituRegion` is a convenient region that carries its storage within itself | |
| (in the form of a statically-sized array). | |
| The first template argument is the size of the region and the second is the | |
| needed alignment. Depending on the alignment requested and platform details, | |
| the actual available storage may be smaller than the compile-time parameter. To | |
| make sure that at least `n` bytes are available in the region, use | |
| $(D InSituRegion!(n + a - 1, a)). | |
| Given that the most frequent use of `InSituRegion` is as a stack allocator, it | |
| allocates starting at the end on systems where stack grows downwards, such that | |
| hot memory is used first. | |
| */ | |
| struct InSituRegion(size_t size, size_t minAlign = platformAlignment) | |
| { | |
| import std.algorithm.comparison : max; | |
| import std.conv : to; | |
| import std.traits : hasMember; | |
| import std.typecons : Ternary; | |
| import core.thread.types : isStackGrowingDown; | |
| static assert(minAlign.isGoodStaticAlignment); | |
| static assert(size >= minAlign); | |
| static if (isStackGrowingDown) | |
| enum growDownwards = Yes.growDownwards; | |
| else | |
| enum growDownwards = No.growDownwards; | |
| @disable this(this); | |
| // state { | |
| private BorrowedRegion!(minAlign, growDownwards) _impl; | |
| union | |
| { | |
| private ubyte[size] _store = void; | |
| private double _forAlignmentOnly1; | |
| } | |
| // } | |
| /** | |
| An alias for `minAlign`, which must be a valid alignment (nonzero power | |
| of 2). The start of the region and all allocation requests will be rounded | |
| up to a multiple of the alignment. | |
| ---- | |
| InSituRegion!(4096) a1; | |
| assert(a1.alignment == platformAlignment); | |
| InSituRegion!(4096, 64) a2; | |
| assert(a2.alignment == 64); | |
| ---- | |
| */ | |
| alias alignment = minAlign; | |
| private void lazyInit() | |
| { | |
| assert(!_impl._current); | |
| _impl = typeof(_impl)(_store); | |
| assert(_impl._current.alignedAt(alignment)); | |
| } | |
| /** | |
| Allocates `bytes` and returns them, or `null` if the region cannot | |
| accommodate the request. For efficiency reasons, if $(D bytes == 0) the | |
| function returns an empty non-null slice. | |
| */ | |
| void[] allocate(size_t n) | |
| { | |
| // Fast path | |
| entry: | |
| auto result = _impl.allocate(n); | |
| if (result.length == n) return result; | |
| // Slow path | |
| if (_impl._current) return null; // no more room | |
| lazyInit; | |
| assert(_impl._current); | |
| goto entry; | |
| } | |
| /** | |
| As above, but the memory allocated is aligned at `a` bytes. | |
| */ | |
| void[] alignedAllocate(size_t n, uint a) | |
| { | |
| // Fast path | |
| entry: | |
| auto result = _impl.alignedAllocate(n, a); | |
| if (result.length == n) return result; | |
| // Slow path | |
| if (_impl._current) return null; // no more room | |
| lazyInit; | |
| assert(_impl._current); | |
| goto entry; | |
| } | |
| /** | |
| Deallocates `b`. This works only if `b` was obtained as the last call | |
| to `allocate`; otherwise (i.e. another allocation has occurred since) it | |
| does nothing. This semantics is tricky and therefore `deallocate` is | |
| defined only if `Region` is instantiated with `Yes.defineDeallocate` | |
| as the third template argument. | |
| Params: | |
| b = Block previously obtained by a call to `allocate` against this | |
| allocator (`null` is allowed). | |
| */ | |
| bool deallocate(void[] b) | |
| { | |
| if (!_impl._current) return b is null; | |
| return _impl.deallocate(b); | |
| } | |
| /** | |
| Returns `Ternary.yes` if `b` is the result of a previous allocation, | |
| `Ternary.no` otherwise. | |
| */ | |
| Ternary owns(const void[] b) pure nothrow @safe @nogc | |
| { | |
| if (!_impl._current) return Ternary.no; | |
| return _impl.owns(b); | |
| } | |
| /** | |
| Expands an allocated block in place. Expansion will succeed only if the | |
| block is the last allocated. | |
| */ | |
| static if (hasMember!(typeof(_impl), "expand")) | |
| bool expand(ref void[] b, size_t delta) | |
| { | |
| if (!_impl._current) lazyInit; | |
| return _impl.expand(b, delta); | |
| } | |
| /** | |
| Deallocates all memory allocated with this allocator. | |
| */ | |
| bool deallocateAll() | |
| { | |
| // We don't care to lazily init the region | |
| return _impl.deallocateAll; | |
| } | |
| /** | |
| Allocates all memory available with this allocator. | |
| */ | |
| void[] allocateAll() | |
| { | |
| if (!_impl._current) lazyInit; | |
| return _impl.allocateAll; | |
| } | |
| /** | |
| Nonstandard function that returns the bytes available for allocation. | |
| */ | |
| size_t available() | |
| { | |
| if (!_impl._current) lazyInit; | |
| return _impl.available; | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| // 128KB region, allocated to x86's cache line | |
| InSituRegion!(128 * 1024, 16) r1; | |
| auto a1 = r1.allocate(101); | |
| assert(a1.length == 101); | |
| // 128KB region, with fallback to the garbage collector. | |
| import std.experimental.allocator.building_blocks.fallback_allocator | |
| : FallbackAllocator; | |
| import std.experimental.allocator.building_blocks.free_list | |
| : FreeList; | |
| import std.experimental.allocator.building_blocks.bitmapped_block | |
| : BitmappedBlock; | |
| import std.experimental.allocator.gc_allocator : GCAllocator; | |
| FallbackAllocator!(InSituRegion!(128 * 1024), GCAllocator) r2; | |
| const a2 = r2.allocate(102); | |
| assert(a2.length == 102); | |
| // Reap with GC fallback. | |
| InSituRegion!(128 * 1024, 8) tmp3; | |
| FallbackAllocator!(BitmappedBlock!(64, 8), GCAllocator) r3; | |
| r3.primary = BitmappedBlock!(64, 8)(cast(ubyte[]) (tmp3.allocateAll())); | |
| const a3 = r3.allocate(103); | |
| assert(a3.length == 103); | |
| // Reap/GC with a freelist for small objects up to 16 bytes. | |
| InSituRegion!(128 * 1024, 64) tmp4; | |
| FreeList!(FallbackAllocator!(BitmappedBlock!(64, 64), GCAllocator), 0, 16) r4; | |
| r4.parent.primary = BitmappedBlock!(64, 64)(cast(ubyte[]) (tmp4.allocateAll())); | |
| const a4 = r4.allocate(104); | |
| assert(a4.length == 104); | |
| } | |
| @system pure nothrow unittest | |
| { | |
| import std.typecons : Ternary; | |
| InSituRegion!(4096, 1) r1; | |
| auto a = r1.allocate(2001); | |
| assert(a.length == 2001); | |
| import std.conv : text; | |
| assert(r1.available == 2095, text(r1.available)); | |
| // Ensure deallocate inherits from parent | |
| assert((() nothrow @nogc => r1.deallocate(a))()); | |
| assert((() nothrow @nogc => r1.deallocateAll())()); | |
| InSituRegion!(65_536, 1024*4) r2; | |
| assert(r2.available <= 65_536); | |
| a = r2.allocate(2001); | |
| assert(a.length == 2001); | |
| const void[] buff = r2.allocate(42); | |
| assert((() nothrow @safe @nogc => r2.owns(buff))() == Ternary.yes); | |
| assert((() nothrow @nogc => r2.deallocateAll())()); | |
| } | |
| version (CRuntime_Musl) | |
| { | |
| // sbrk and brk are disabled in Musl: | |
| // https://git.musl-libc.org/cgit/musl/commit/?id=7a995fe706e519a4f55399776ef0df9596101f93 | |
| // https://git.musl-libc.org/cgit/musl/commit/?id=863d628d93ea341b6a32661a1654320ce69f6a07 | |
| } | |
| version (DragonFlyBSD) | |
| { | |
| // sbrk is deprecated in favor of mmap (we could implement a mmap + MAP_NORESERVE + PROT_NONE version) | |
| // brk has been removed | |
| // https://web.archive.org/web/20221006070113/https://www.dragonflydigest.com/2019/02/22/22586.html | |
| // http://gitweb.dragonflybsd.org/dragonfly.git/commitdiff/dc676eaefa61b0f47bbea1c53eab86fd5ccd78c6 | |
| // http://gitweb.dragonflybsd.org/dragonfly.git/commitdiff/4b5665564ef37dc939a3a9ffbafaab9894c18885 | |
| // http://gitweb.dragonflybsd.org/dragonfly.git/commitdiff/8618d94a0e2ff8303ad93c123a3fa598c26a116e | |
| } | |
| else | |
| { | |
| private extern(C) void* sbrk(long) nothrow @nogc; | |
| private extern(C) int brk(shared void*) nothrow @nogc; | |
| } | |
| /** | |
| Allocator backed by $(D $(LINK2 https://en.wikipedia.org/wiki/Sbrk, sbrk)) | |
| for Posix systems. Due to the fact that `sbrk` is not thread-safe | |
| $(HTTP lifecs.likai.org/2010/02/sbrk-is-not-thread-safe.html, by design), | |
| `SbrkRegion` uses a mutex internally. This implies | |
| that uncontrolled calls to `brk` and `sbrk` may affect the workings of $(D | |
| SbrkRegion) adversely. | |
| */ | |
| version (CRuntime_Musl) {} else | |
| version (DragonFlyBSD) {} else | |
| version (Posix) struct SbrkRegion(uint minAlign = platformAlignment) | |
| { | |
| import core.sys.posix.pthread : pthread_mutex_init, pthread_mutex_destroy, | |
| pthread_mutex_t, pthread_mutex_lock, pthread_mutex_unlock, | |
| PTHREAD_MUTEX_INITIALIZER; | |
| private static shared pthread_mutex_t sbrkMutex = PTHREAD_MUTEX_INITIALIZER; | |
| import std.typecons : Ternary; | |
| static assert(minAlign.isGoodStaticAlignment); | |
| static assert(size_t.sizeof == (void*).sizeof); | |
| private shared void* _brkInitial, _brkCurrent; | |
| /** | |
| Instance shared by all callers. | |
| */ | |
| static shared SbrkRegion instance; | |
| /** | |
| Standard allocator primitives. | |
| */ | |
| enum uint alignment = minAlign; | |
| /** | |
| Rounds the given size to a multiple of thew `alignment` | |
| */ | |
| size_t goodAllocSize(size_t n) shared const pure nothrow @safe @nogc | |
| { | |
| return n.roundUpToMultipleOf(alignment); | |
| } | |
| /// Ditto | |
| void[] allocate(size_t bytes) shared @trusted nothrow @nogc | |
| { | |
| // Take alignment rounding into account | |
| const rounded = goodAllocSize(bytes); | |
| pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); | |
| scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 | |
| || assert(0); | |
| // Assume sbrk returns the old break. Most online documentation confirms | |
| // that, except for https://web.archive.org/web/20171014020821/http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf, | |
| // which claims the returned value is not portable. | |
| auto p = sbrk(rounded); | |
| if (p == cast(void*) -1) | |
| { | |
| return null; | |
| } | |
| if (!_brkInitial) | |
| { | |
| _brkInitial = cast(shared) p; | |
| assert(cast(size_t) _brkInitial % minAlign == 0, | |
| "Too large alignment chosen for " ~ typeof(this).stringof); | |
| } | |
| _brkCurrent = cast(shared) (p + rounded); | |
| return p[0 .. bytes]; | |
| } | |
| /// Ditto | |
| void[] alignedAllocate(size_t bytes, uint a) shared @trusted nothrow @nogc | |
| { | |
| pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); | |
| scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 | |
| || assert(0); | |
| if (!_brkInitial) | |
| { | |
| // This is one extra call, but it'll happen only once. | |
| _brkInitial = cast(shared) sbrk(0); | |
| assert(cast(size_t) _brkInitial % minAlign == 0, | |
| "Too large alignment chosen for " ~ typeof(this).stringof); | |
| (_brkInitial != cast(void*) -1) || assert(0); | |
| _brkCurrent = _brkInitial; | |
| } | |
| immutable size_t delta = cast(shared void*) roundUpToMultipleOf( | |
| cast(size_t) _brkCurrent, a) - _brkCurrent; | |
| // Still must make sure the total size is aligned to the allocator's | |
| // alignment. | |
| immutable rounded = (bytes + delta).roundUpToMultipleOf(alignment); | |
| auto p = sbrk(rounded); | |
| if (p == cast(void*) -1) | |
| { | |
| return null; | |
| } | |
| _brkCurrent = cast(shared) (p + rounded); | |
| return p[delta .. delta + bytes]; | |
| } | |
| /** | |
| The `expand` method may only succeed if the argument is the last block | |
| allocated. In that case, `expand` attempts to push the break pointer to | |
| the right. | |
| */ | |
| bool expand(ref void[] b, size_t delta) shared nothrow @trusted @nogc | |
| { | |
| if (b is null || delta == 0) return delta == 0; | |
| assert(_brkInitial && _brkCurrent); // otherwise where did b come from? | |
| pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); | |
| scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 | |
| || assert(0); | |
| // Take alignment rounding into account | |
| const rounded = goodAllocSize(b.length); | |
| const slack = rounded - b.length; | |
| if (delta <= slack) | |
| { | |
| b = b.ptr[0 .. b.length + delta]; | |
| return true; | |
| } | |
| if (_brkCurrent != b.ptr + rounded) return false; | |
| // Great, can expand the last block | |
| delta -= slack; | |
| const roundedDelta = goodAllocSize(delta); | |
| auto p = sbrk(roundedDelta); | |
| if (p == cast(void*) -1) | |
| { | |
| return false; | |
| } | |
| _brkCurrent = cast(shared) (p + roundedDelta); | |
| b = b.ptr[0 .. b.length + slack + delta]; | |
| return true; | |
| } | |
| /// Ditto | |
| Ternary owns(const void[] b) shared pure nothrow @trusted @nogc | |
| { | |
| // No need to lock here. | |
| assert(!_brkCurrent || !b || &b[0] + b.length <= _brkCurrent); | |
| return Ternary(_brkInitial && b && (&b[0] >= _brkInitial)); | |
| } | |
| /** | |
| The `deallocate` method only works (and returns `true`) on systems | |
| that support reducing the break address (i.e. accept calls to `sbrk` | |
| with negative offsets). OSX does not accept such. In addition the argument | |
| must be the last block allocated. | |
| */ | |
| bool deallocate(void[] b) shared nothrow @nogc | |
| { | |
| // Take alignment rounding into account | |
| const rounded = goodAllocSize(b.length); | |
| pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); | |
| scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 | |
| || assert(0); | |
| if (_brkCurrent != b.ptr + rounded) return false; | |
| assert(b.ptr >= _brkInitial); | |
| if (sbrk(-rounded) == cast(void*) -1) | |
| return false; | |
| _brkCurrent = cast(shared) b.ptr; | |
| return true; | |
| } | |
| /** | |
| The `deallocateAll` method only works (and returns `true`) on systems | |
| that support reducing the break address (i.e. accept calls to `sbrk` | |
| with negative offsets). OSX does not accept such. | |
| */ | |
| nothrow @nogc | |
| bool deallocateAll() shared | |
| { | |
| pthread_mutex_lock(cast(pthread_mutex_t*) &sbrkMutex) == 0 || assert(0); | |
| scope(exit) pthread_mutex_unlock(cast(pthread_mutex_t*) &sbrkMutex) == 0 | |
| || assert(0); | |
| return !_brkInitial || brk(_brkInitial) == 0; | |
| } | |
| /// Standard allocator API. | |
| Ternary empty() shared pure nothrow @safe @nogc | |
| { | |
| // Also works when they're both null. | |
| return Ternary(_brkCurrent == _brkInitial); | |
| } | |
| } | |
| version (CRuntime_Musl) {} else | |
| version (DragonFlyBSD) {} else | |
| version (Posix) @system nothrow @nogc unittest | |
| { | |
| // Let's test the assumption that sbrk(n) returns the old address | |
| const p1 = sbrk(0); | |
| const p2 = sbrk(4096); | |
| assert(p1 == p2); | |
| const p3 = sbrk(0); | |
| assert(p3 == p2 + 4096); | |
| // Try to reset brk, but don't make a fuss if it doesn't work | |
| sbrk(-4096); | |
| } | |
| version (CRuntime_Musl) {} else | |
| version (DragonFlyBSD) {} else | |
| version (Posix) @system nothrow @nogc unittest | |
| { | |
| import std.typecons : Ternary; | |
| import std.algorithm.comparison : min; | |
| alias alloc = SbrkRegion!(min(8, platformAlignment)).instance; | |
| assert((() nothrow @safe @nogc => alloc.empty)() == Ternary.yes); | |
| auto a = alloc.alignedAllocate(2001, 4096); | |
| assert(a.length == 2001); | |
| assert((() nothrow @safe @nogc => alloc.empty)() == Ternary.no); | |
| auto oldBrkCurr = alloc._brkCurrent; | |
| auto b = alloc.allocate(2001); | |
| assert(b.length == 2001); | |
| assert((() nothrow @safe @nogc => alloc.expand(b, 0))()); | |
| assert(b.length == 2001); | |
| // Expand with a small size to fit the rounded slack due to alignment | |
| assert((() nothrow @safe @nogc => alloc.expand(b, 1))()); | |
| assert(b.length == 2002); | |
| // Exceed the rounded slack due to alignment | |
| assert((() nothrow @safe @nogc => alloc.expand(b, 10))()); | |
| assert(b.length == 2012); | |
| assert((() nothrow @safe @nogc => alloc.owns(a))() == Ternary.yes); | |
| assert((() nothrow @safe @nogc => alloc.owns(b))() == Ternary.yes); | |
| // reducing the brk does not work on OSX | |
| version (Darwin) {} else | |
| { | |
| assert((() nothrow @nogc => alloc.deallocate(b))()); | |
| // Check that expand and deallocate work well | |
| assert(oldBrkCurr == alloc._brkCurrent); | |
| assert((() nothrow @nogc => alloc.deallocate(a))()); | |
| assert((() nothrow @nogc => alloc.deallocateAll())()); | |
| } | |
| const void[] c = alloc.allocate(2001); | |
| assert(c.length == 2001); | |
| assert((() nothrow @safe @nogc => alloc.owns(c))() == Ternary.yes); | |
| assert((() nothrow @safe @nogc => alloc.owns(null))() == Ternary.no); | |
| } | |
| /** | |
| The threadsafe version of the `Region` allocator. | |
| Allocations and deallocations are lock-free based using $(REF cas, core,atomic). | |
| */ | |
| shared struct SharedRegion(ParentAllocator, | |
| uint minAlign = platformAlignment, | |
| Flag!"growDownwards" growDownwards = No.growDownwards) | |
| { | |
| static assert(minAlign.isGoodStaticAlignment); | |
| static assert(ParentAllocator.alignment >= minAlign); | |
| import std.traits : hasMember; | |
| import std.typecons : Ternary; | |
| // state | |
| /** | |
| The _parent allocator. Depending on whether `ParentAllocator` holds state | |
| or not, this is a member variable or an alias for | |
| `ParentAllocator.instance`. | |
| */ | |
| static if (stateSize!ParentAllocator) | |
| { | |
| ParentAllocator parent; | |
| } | |
| else | |
| { | |
| alias parent = ParentAllocator.instance; | |
| } | |
| private shared SharedBorrowedRegion!(minAlign, growDownwards) _impl; | |
| private void* roundedBegin() const pure nothrow @trusted @nogc | |
| { | |
| return _impl.roundedBegin; | |
| } | |
| private void* roundedEnd() const pure nothrow @trusted @nogc | |
| { | |
| return _impl.roundedEnd; | |
| } | |
| /** | |
| Constructs a region backed by a user-provided store. | |
| Assumes the memory was allocated with `ParentAllocator`. | |
| Params: | |
| store = User-provided store backing up the region. Assumed to have been | |
| allocated with `ParentAllocator`. | |
| n = Bytes to allocate using `ParentAllocator`. If `parent.allocate(n)` | |
| returns `null`, the region will be initialized as empty (correctly | |
| initialized but unable to allocate). | |
| */ | |
| this(ubyte[] store) pure nothrow @nogc | |
| { | |
| _impl = store; | |
| } | |
| /// Ditto | |
| this(size_t n) | |
| { | |
| this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); | |
| } | |
| /** | |
| Rounds the given size to a multiple of the `alignment` | |
| */ | |
| size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc | |
| { | |
| return _impl.goodAllocSize(n); | |
| } | |
| /** | |
| Alignment offered. | |
| */ | |
| alias alignment = minAlign; | |
| /** | |
| Allocates `n` bytes of memory. The allocation is served by atomically incrementing | |
| a pointer which keeps track of the current used space. | |
| Params: | |
| n = number of bytes to allocate | |
| Returns: | |
| A properly-aligned buffer of size `n`, or `null` if request could not | |
| be satisfied. | |
| */ | |
| void[] allocate(size_t n) pure nothrow @trusted @nogc | |
| { | |
| return _impl.allocate(n); | |
| } | |
| /** | |
| Deallocates `b`. This works only if `b` was obtained as the last call | |
| to `allocate`; otherwise (i.e. another allocation has occurred since) it | |
| does nothing. | |
| Params: | |
| b = Block previously obtained by a call to `allocate` against this | |
| allocator (`null` is allowed). | |
| */ | |
| bool deallocate(void[] b) pure nothrow @nogc | |
| { | |
| return _impl.deallocate(b); | |
| } | |
| /** | |
| Deallocates all memory allocated by this region, which can be subsequently | |
| reused for new allocations. | |
| */ | |
| bool deallocateAll() pure nothrow @nogc | |
| { | |
| return _impl.deallocateAll; | |
| } | |
| /** | |
| Allocates `n` bytes of memory aligned at alignment `a`. | |
| Params: | |
| n = number of bytes to allocate | |
| a = alignment for the allocated block | |
| Returns: | |
| Either a suitable block of `n` bytes aligned at `a`, or `null`. | |
| */ | |
| void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc | |
| { | |
| return _impl.alignedAllocate(n, a); | |
| } | |
| /** | |
| Queries whether `b` has been allocated with this region. | |
| Params: | |
| b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns | |
| `false`). | |
| Returns: | |
| `true` if `b` has been allocated with this region, `false` otherwise. | |
| */ | |
| Ternary owns(const void[] b) const pure nothrow @trusted @nogc | |
| { | |
| return _impl.owns(b); | |
| } | |
| /** | |
| Returns `Ternary.yes` if no memory has been allocated in this region, | |
| `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) | |
| */ | |
| Ternary empty() const pure nothrow @safe @nogc | |
| { | |
| return _impl.empty; | |
| } | |
| /** | |
| If `ParentAllocator` defines `deallocate`, the region defines a destructor | |
| that uses `ParentAllocator.deallocate` to free the memory chunk. | |
| */ | |
| static if (hasMember!(ParentAllocator, "deallocate")) | |
| ~this() | |
| { | |
| with (_impl) parent.deallocate(cast(void[]) _begin[0 .. _end - _begin]); | |
| } | |
| } | |
| @system unittest | |
| { | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| static void testAlloc(Allocator)(ref Allocator a, bool growDownwards) | |
| { | |
| import core.thread : ThreadGroup; | |
| import std.algorithm.sorting : sort; | |
| import core.internal.spinlock : SpinLock; | |
| SpinLock lock = SpinLock(SpinLock.Contention.brief); | |
| enum numThreads = 100; | |
| void[][numThreads] buf; | |
| size_t count = 0; | |
| void fun() | |
| { | |
| void[] b = a.allocate(63); | |
| assert(b.length == 63); | |
| lock.lock(); | |
| buf[count] = b; | |
| count++; | |
| lock.unlock(); | |
| } | |
| auto tg = new ThreadGroup; | |
| foreach (i; 0 .. numThreads) | |
| { | |
| tg.create(&fun); | |
| } | |
| tg.joinAll(); | |
| sort!((a, b) => a.ptr < b.ptr)(buf[0 .. numThreads]); | |
| foreach (i; 0 .. numThreads - 1) | |
| { | |
| assert(buf[i].ptr + a.goodAllocSize(buf[i].length) == buf[i + 1].ptr); | |
| } | |
| assert(!a.deallocate(buf[1])); | |
| foreach (i; 0 .. numThreads) | |
| { | |
| if (!growDownwards) | |
| assert(a.deallocate(buf[numThreads - 1 - i])); | |
| else | |
| assert(a.deallocate(buf[i])); | |
| } | |
| assert(a.deallocateAll()); | |
| void[] b = a.allocate(63); | |
| assert(b.length == 63); | |
| assert(a.deallocate(b)); | |
| } | |
| auto a1 = SharedRegion!(Mallocator, Mallocator.alignment, | |
| Yes.growDownwards)(1024 * 64); | |
| auto a2 = SharedRegion!(Mallocator, Mallocator.alignment, | |
| No.growDownwards)(1024 * 64); | |
| testAlloc(a1, true); | |
| testAlloc(a2, false); | |
| } | |
| @system unittest | |
| { | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| static void testAlloc(Allocator)(ref Allocator a, bool growDownwards) | |
| { | |
| import core.thread : ThreadGroup; | |
| import std.algorithm.sorting : sort; | |
| import core.internal.spinlock : SpinLock; | |
| SpinLock lock = SpinLock(SpinLock.Contention.brief); | |
| enum numThreads = 100; | |
| void[][2 * numThreads] buf; | |
| size_t count = 0; | |
| void fun() | |
| { | |
| void[] b = a.allocate(63); | |
| assert(b.length == 63); | |
| lock.lock(); | |
| buf[count] = b; | |
| count++; | |
| lock.unlock(); | |
| b = a.alignedAllocate(63, 32); | |
| assert(b.length == 63); | |
| assert(cast(size_t) b.ptr % 32 == 0); | |
| lock.lock(); | |
| buf[count] = b; | |
| count++; | |
| lock.unlock(); | |
| } | |
| auto tg = new ThreadGroup; | |
| foreach (i; 0 .. numThreads) | |
| { | |
| tg.create(&fun); | |
| } | |
| tg.joinAll(); | |
| sort!((a, b) => a.ptr < b.ptr)(buf[0 .. 2 * numThreads]); | |
| foreach (i; 0 .. 2 * numThreads - 1) | |
| { | |
| assert(buf[i].ptr + buf[i].length <= buf[i + 1].ptr); | |
| } | |
| assert(!a.deallocate(buf[1])); | |
| assert(a.deallocateAll()); | |
| void[] b = a.allocate(13); | |
| assert(b.length == 13); | |
| assert(a.deallocate(b)); | |
| } | |
| auto a1 = SharedRegion!(Mallocator, Mallocator.alignment, | |
| Yes.growDownwards)(1024 * 64); | |
| auto a2 = SharedRegion!(Mallocator, Mallocator.alignment, | |
| No.growDownwards)(1024 * 64); | |
| testAlloc(a1, true); | |
| testAlloc(a2, false); | |
| } | |
| /** | |
| A `SharedBorrowedRegion` allocates directly from a user-provided block of memory. | |
| Unlike a `SharedRegion`, a `SharedBorrowedRegion` does not own the memory it | |
| allocates from and will not deallocate that memory upon destruction. Instead, | |
| it is the user's responsibility to ensure that the memory is properly disposed | |
| of. | |
| In all other respects, a `SharedBorrowedRegion` behaves exactly like a `SharedRegion`. | |
| */ | |
| shared struct SharedBorrowedRegion(uint minAlign = platformAlignment, | |
| Flag!"growDownwards" growDownwards = No.growDownwards) | |
| { | |
| static assert(minAlign.isGoodStaticAlignment); | |
| import std.typecons : Ternary; | |
| // state | |
| private void* _current, _begin, _end; | |
| private void* roundedBegin() shared const pure nothrow @trusted @nogc | |
| { | |
| return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); | |
| } | |
| private void* roundedEnd() shared const pure nothrow @trusted @nogc | |
| { | |
| return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); | |
| } | |
| /** | |
| Constructs a region backed by a user-provided store. | |
| Params: | |
| store = User-provided store backing up the region. Must not be aliased. | |
| */ | |
| this(ubyte[] store) shared pure nothrow @nogc | |
| { | |
| _begin = cast(typeof(_begin)) store.ptr; | |
| _end = cast(typeof(_end)) (store.ptr + store.length); | |
| static if (growDownwards) | |
| _current = cast(typeof(_current)) roundedEnd(); | |
| else | |
| _current = cast(typeof(_current)) roundedBegin(); | |
| } | |
| /* | |
| TODO: The postblit of `SharedBorrowedRegion` should be disabled because | |
| such objects should not be copied around naively. | |
| */ | |
| /** | |
| Rounds the given size to a multiple of the `alignment` | |
| */ | |
| size_t goodAllocSize(size_t n) shared const pure nothrow @safe @nogc | |
| { | |
| return n.roundUpToAlignment(alignment); | |
| } | |
| /** | |
| Alignment offered. | |
| */ | |
| alias alignment = minAlign; | |
| /** | |
| Allocates `n` bytes of memory. The allocation is served by atomically incrementing | |
| a pointer which keeps track of the current used space. | |
| Params: | |
| n = number of bytes to allocate | |
| Returns: | |
| A properly-aligned buffer of size `n`, or `null` if request could not | |
| be satisfied. | |
| */ | |
| void[] allocate(size_t n) shared pure nothrow @trusted @nogc | |
| { | |
| import core.atomic : cas, atomicLoad; | |
| if (n == 0) return null; | |
| const rounded = goodAllocSize(n); | |
| shared void* localCurrent, localNewCurrent; | |
| static if (growDownwards) | |
| { | |
| do | |
| { | |
| localCurrent = atomicLoad(_current); | |
| localNewCurrent = localCurrent - rounded; | |
| if (localNewCurrent > localCurrent || localNewCurrent < _begin) | |
| return null; | |
| } while (!cas(&_current, localCurrent, localNewCurrent)); | |
| return cast(void[]) localNewCurrent[0 .. n]; | |
| } | |
| else | |
| { | |
| do | |
| { | |
| localCurrent = atomicLoad(_current); | |
| localNewCurrent = localCurrent + rounded; | |
| if (localNewCurrent < localCurrent || localNewCurrent > _end) | |
| return null; | |
| } while (!cas(&_current, localCurrent, localNewCurrent)); | |
| return cast(void[]) localCurrent[0 .. n]; | |
| } | |
| assert(0, "Unexpected error in SharedBorrowedRegion.allocate"); | |
| } | |
| /** | |
| Allocates `n` bytes of memory aligned at alignment `a`. | |
| Params: | |
| n = number of bytes to allocate | |
| a = alignment for the allocated block | |
| Returns: | |
| Either a suitable block of `n` bytes aligned at `a`, or `null`. | |
| */ | |
| void[] alignedAllocate(size_t n, uint a) shared pure nothrow @trusted @nogc | |
| { | |
| import core.atomic : cas, atomicLoad; | |
| import std.math.traits : isPowerOf2; | |
| assert(a.isPowerOf2); | |
| if (n == 0) return null; | |
| const rounded = goodAllocSize(n); | |
| shared void* localCurrent, localNewCurrent; | |
| static if (growDownwards) | |
| { | |
| do | |
| { | |
| localCurrent = atomicLoad(_current); | |
| auto alignedCurrent = cast(void*)(localCurrent - rounded); | |
| localNewCurrent = cast(shared(void*)) alignedCurrent.alignDownTo(a); | |
| if (alignedCurrent > localCurrent || localNewCurrent > alignedCurrent || | |
| localNewCurrent < _begin) | |
| return null; | |
| } while (!cas(&_current, localCurrent, localNewCurrent)); | |
| return cast(void[]) localNewCurrent[0 .. n]; | |
| } | |
| else | |
| { | |
| do | |
| { | |
| localCurrent = atomicLoad(_current); | |
| auto alignedCurrent = alignUpTo(cast(void*) localCurrent, a); | |
| localNewCurrent = cast(shared(void*)) (alignedCurrent + rounded); | |
| if (alignedCurrent < localCurrent || localNewCurrent < alignedCurrent || | |
| localNewCurrent > _end) | |
| return null; | |
| } while (!cas(&_current, localCurrent, localNewCurrent)); | |
| return cast(void[]) (localNewCurrent - rounded)[0 .. n]; | |
| } | |
| assert(0, "Unexpected error in SharedBorrowedRegion.alignedAllocate"); | |
| } | |
| /** | |
| Deallocates `b`. This works only if `b` was obtained as the last call | |
| to `allocate`; otherwise (i.e. another allocation has occurred since) it | |
| does nothing. | |
| Params: | |
| b = Block previously obtained by a call to `allocate` against this | |
| allocator (`null` is allowed). | |
| */ | |
| bool deallocate(void[] b) shared pure nothrow @nogc | |
| { | |
| import core.atomic : cas, atomicLoad; | |
| const rounded = goodAllocSize(b.length); | |
| shared void* localCurrent, localNewCurrent; | |
| // The cas is done only once, because only the last allocation can be reverted | |
| localCurrent = atomicLoad(_current); | |
| static if (growDownwards) | |
| { | |
| localNewCurrent = localCurrent + rounded; | |
| if (b.ptr == localCurrent) | |
| return cas(&_current, localCurrent, localNewCurrent); | |
| } | |
| else | |
| { | |
| localNewCurrent = localCurrent - rounded; | |
| if (b.ptr == localNewCurrent) | |
| return cas(&_current, localCurrent, localNewCurrent); | |
| } | |
| return false; | |
| } | |
| /** | |
| Deallocates all memory allocated by this region, which can be subsequently | |
| reused for new allocations. | |
| */ | |
| bool deallocateAll() shared pure nothrow @nogc | |
| { | |
| import core.atomic : atomicStore; | |
| static if (growDownwards) | |
| { | |
| atomicStore(_current, cast(shared(void*)) roundedEnd()); | |
| } | |
| else | |
| { | |
| atomicStore(_current, cast(shared(void*)) roundedBegin()); | |
| } | |
| return true; | |
| } | |
| /** | |
| Queries whether `b` has been allocated with this region. | |
| Params: | |
| b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns | |
| `false`). | |
| Returns: | |
| `true` if `b` has been allocated with this region, `false` otherwise. | |
| */ | |
| Ternary owns(const void[] b) shared const pure nothrow @trusted @nogc | |
| { | |
| return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); | |
| } | |
| /** | |
| Returns `Ternary.yes` if no memory has been allocated in this region, | |
| `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) | |
| */ | |
| Ternary empty() shared const pure nothrow @safe @nogc | |
| { | |
| import core.atomic : atomicLoad; | |
| auto localCurrent = atomicLoad(_current); | |
| static if (growDownwards) | |
| return Ternary(localCurrent == roundedEnd()); | |
| else | |
| return Ternary(localCurrent == roundedBegin()); | |
| } | |
| } |