mirror of
https://github.com/dlang/phobos.git
synced 2025-05-02 08:00:48 +03:00

for link in $(grep -Eor 'https?://[^\\)", ]+' | grep -v dlang.org \ | grep -Eo 'https?://.+'); do (printf "%s: " "$link"; curl -Is "$link" \ | head -n1) | grep -E '4..$'; done for link in $(grep -Eor '\\$\\(HTTP .+, ' | grep -v dlang.org \ | cut -d' ' -f2- | sed 's/, .*$//'); do out="$(curl -Is "$link")"; \ (! [ $? = 0 ] || printf '%s' "$out" | head -n1 | grep -Eq '4..') \ && printf '%s\\n' "$link"; done
1704 lines
51 KiB
D
1704 lines
51 KiB
D
// 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());
|
|
}
|
|
}
|