mirror of
https://github.com/dlang/phobos.git
synced 2025-05-06 19:16:13 +03:00
1236 lines
33 KiB
D
1236 lines
33 KiB
D
/**
|
|
|
|
High-level interface for allocators. Implements bundled allocation/creation
|
|
and destruction/deallocation of data including $(D struct)s and $(D class)es,
|
|
and also array primitives related to allocation.
|
|
|
|
---
|
|
// Allocate an int, initialize it with 42
|
|
int* p = theAllocator.make!int(42);
|
|
assert(*p == 42);
|
|
// Destroy and deallocate it
|
|
theAllocator.dispose(p);
|
|
|
|
// Allocate using the global process allocator
|
|
p = processAllocator.make!int(100);
|
|
assert(*p == 100);
|
|
// Destroy and deallocate
|
|
processAllocator.dispose(p);
|
|
|
|
// Create an array of 50 doubles initialized to -1.0
|
|
double[] arr = theAllocator.makeArray!double(50, -1.0);
|
|
// Append two zeros to it
|
|
theAllocator.growArray(arr, 2, 0.0);
|
|
// On second thought, take that back
|
|
theAllocator.shrinkArray(arr, 2);
|
|
// Destroy and deallocate
|
|
theAllocator.dispose(arr);
|
|
---
|
|
|
|
*/
|
|
|
|
module std.experimental.allocator.porcelain;
|
|
|
|
// Example in intro above
|
|
unittest
|
|
{
|
|
// Allocate an int, initialize it with 42
|
|
int* p = theAllocator.make!int(42);
|
|
assert(*p == 42);
|
|
// Destroy and deallocate it
|
|
theAllocator.dispose(p);
|
|
|
|
// Allocate using the global process allocator
|
|
p = processAllocator.make!int(100);
|
|
assert(*p == 100);
|
|
// Destroy and deallocate
|
|
processAllocator.dispose(p);
|
|
|
|
// Create an array of 50 doubles initialized to -1.0
|
|
double[] arr = theAllocator.makeArray!double(50, -1.0);
|
|
// Append two zeros to it
|
|
theAllocator.growArray(arr, 2, 0.0);
|
|
// On second thought, take that back
|
|
theAllocator.shrinkArray(arr, 2);
|
|
// Destroy and deallocate
|
|
theAllocator.dispose(arr);
|
|
}
|
|
|
|
import std.experimental.allocator.common;
|
|
import std.traits : isPointer, hasElaborateDestructor;
|
|
import std.typecons : Flag, Yes, No;
|
|
import std.algorithm : min;
|
|
import std.range : isInputRange, isForwardRange, walkLength, save, empty,
|
|
front, popFront;
|
|
|
|
/**
|
|
Dynamic version of an allocator. This should be used wherever a uniform type is
|
|
required for encapsulating various allocator implementations.
|
|
|
|
Methods returning $(D Ternary) return $(D Ternary.yes) upon success,
|
|
$(D Ternary.no) upon failure, and $(D Ternary.unknown) if the primitive is not
|
|
implemented by the allocator instance.
|
|
*/
|
|
interface IAllocator
|
|
{
|
|
/**
|
|
Returns the alignment offered.
|
|
*/
|
|
@property uint alignment();
|
|
|
|
/**
|
|
Returns the good allocation size that guarantees zero internal
|
|
fragmentation.
|
|
*/
|
|
size_t goodAllocSize(size_t s);
|
|
|
|
/**
|
|
Allocates memory.
|
|
*/
|
|
void[] allocate(size_t, TypeInfo ti = null);
|
|
|
|
/**
|
|
Allocates memory with specified alignment.
|
|
*/
|
|
Ternary alignedAllocate(size_t, uint, ref void[], TypeInfo ti = null);
|
|
|
|
/**
|
|
Allocates and returns all memory available to this allocator. $(COMMENT By
|
|
default returns $(D null).)
|
|
*/
|
|
Ternary allocateAll(ref void[] result);
|
|
|
|
/**
|
|
Expands a memory block in place. If expansion not supported
|
|
by the allocator, returns $(D Ternary.unknown). If implemented, returns $(D
|
|
Ternary.yes) if expansion succeeded, $(D Ternary.no) otherwise.
|
|
*/
|
|
Ternary expand(ref void[], size_t);
|
|
|
|
/// Reallocates a memory block.
|
|
bool reallocate(ref void[], size_t);
|
|
|
|
/// Reallocates a memory block with specified alignment.
|
|
Ternary alignedReallocate(ref void[] b, size_t size, uint alignment);
|
|
|
|
/**
|
|
Returns $(D Ternary.yes) if the allocator owns $(D b), $(D Ternary.no) if
|
|
the allocator doesn't own $(D b), and $(D Ternary.unknown) if ownership not
|
|
supported by the allocator. $(COMMENT By default returns $(D
|
|
Ternary.unknown).)
|
|
*/
|
|
Ternary owns(void[] b);
|
|
|
|
/**
|
|
Resolves an internal pointer to the full block allocated.
|
|
*/
|
|
Ternary resolveInternalPointer(void* p, ref void[] result);
|
|
|
|
/**
|
|
Deallocates a memory block. Returns $(D Ternary.unknown) if deallocation is
|
|
not supported. A simple way to check that an allocator supports
|
|
deallocation is to call $(D deallocate(null)).
|
|
*/
|
|
Ternary deallocate(void[] b);
|
|
|
|
/**
|
|
Deallocates all memory. Returns $(D Ternary.unknown) if deallocation is
|
|
not supported.
|
|
*/
|
|
Ternary deallocateAll();
|
|
|
|
/**
|
|
Returns $(D Ternary.yes) if no memory is currently allocated from this
|
|
allocator, $(D Ternary.no) if some allocations are currently active, or
|
|
$(D Ternary.unknown) if not supported.
|
|
*/
|
|
Ternary empty();
|
|
|
|
/**
|
|
Returns $(D Ternary.yes) if memory is zeroed upon an allocation,
|
|
$(D Ternary.no) if not, or $(D Ternary.unknown) if primitive not supported.
|
|
*/
|
|
Ternary zeroesAllocations();
|
|
}
|
|
|
|
__gshared IAllocator _processAllocator;
|
|
IAllocator _threadAllocator;
|
|
|
|
shared static this()
|
|
{
|
|
assert(!_processAllocator);
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
_processAllocator = allocatorObject(GCAllocator.it);
|
|
}
|
|
|
|
static this()
|
|
{
|
|
assert(!_threadAllocator);
|
|
_threadAllocator = _processAllocator;
|
|
}
|
|
|
|
/**
|
|
Gets/sets the allocator for the current thread. This is the default allocator
|
|
that should be used for allocating thread-local memory. For allocating memory
|
|
to be shared across threads, use $(D processAllocator) (below). By default,
|
|
$(D theAllocator) ultimately fetches memory from $(D processAllocator), which
|
|
in turn uses the garbage collected heap.
|
|
*/
|
|
@property IAllocator theAllocator()
|
|
{
|
|
return _threadAllocator;
|
|
}
|
|
|
|
/// Ditto
|
|
@property void theAllocator(IAllocator a)
|
|
{
|
|
assert(a);
|
|
_threadAllocator = a;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
// Install a new allocator that is faster for 128-byte allocations.
|
|
import std.experimental.allocator.free_list,
|
|
std.experimental.allocator.gc_allocator;
|
|
auto oldAllocator = theAllocator;
|
|
scope(exit) theAllocator = oldAllocator;
|
|
theAllocator = allocatorObject(FreeList!(GCAllocator, 128)());
|
|
// Use the now changed allocator to allocate an array
|
|
ubyte[] arr = theAllocator.makeArray!ubyte(128);
|
|
assert(arr.ptr);
|
|
//...
|
|
}
|
|
|
|
/**
|
|
Gets/sets the allocator for the current process. This allocator must be used
|
|
for allocating memory shared across threads. Objects created using this
|
|
allocator can be cast to $(D shared).
|
|
*/
|
|
@property IAllocator processAllocator()
|
|
{
|
|
return _processAllocator;
|
|
}
|
|
|
|
/// Ditto
|
|
@property void processAllocator(IAllocator a)
|
|
{
|
|
assert(a);
|
|
_processAllocator = a;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
assert(processAllocator);
|
|
assert(processAllocator is theAllocator);
|
|
}
|
|
|
|
/**
|
|
|
|
Returns a dynamically-typed $(D CAllocator) built around a given statically-
|
|
typed allocator $(D a) of type $(D A). Passing a pointer to the allocator
|
|
creates a dynamic allocator around the allocator pointed to by the pointer,
|
|
without attempting to copy or move it. Passing the allocator by value or
|
|
reference behaves as follows.
|
|
|
|
$(UL
|
|
$(LI If $(D A) has no state, the resulting object is allocated in static
|
|
shared storage.)
|
|
$(LI If $(D A) has state and is copyable, the result will store a copy of it
|
|
within. The result itself is allocated in its own statically-typed allocator.)
|
|
$(LI If $(D A) has state and is not copyable, the result will move the
|
|
passed-in argument into the result. The result itself is allocated in its own
|
|
statically-typed allocator.)
|
|
)
|
|
|
|
*/
|
|
CAllocatorImpl!A allocatorObject(A)(auto ref A a)
|
|
if (!isPointer!A)
|
|
{
|
|
import std.conv : emplace;
|
|
static if (stateSize!A == 0)
|
|
{
|
|
enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof);
|
|
static __gshared ulong[s] state;
|
|
static __gshared CAllocatorImpl!A result;
|
|
if (!result)
|
|
{
|
|
// Don't care about a few races
|
|
result = emplace!(CAllocatorImpl!A)(state[]);
|
|
}
|
|
assert(result);
|
|
return result;
|
|
}
|
|
else static if (is(typeof({ A b = a; A c = b; }))) // copyable
|
|
{
|
|
auto state = a.allocate(stateSize!(CAllocatorImpl!A));
|
|
import std.traits : hasMember;
|
|
static if (hasMember!(A, "deallocate"))
|
|
{
|
|
scope(failure) a.deallocate(state);
|
|
}
|
|
return cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state);
|
|
}
|
|
else // the allocator object is not copyable
|
|
{
|
|
// This is sensitive... create on the stack and then move
|
|
enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof);
|
|
ulong[s] state;
|
|
import std.algorithm : move;
|
|
emplace!(CAllocatorImpl!A)(state[], move(a));
|
|
auto dynState = a.allocate(stateSize!(CAllocatorImpl!A));
|
|
// Bitblast the object in its final destination
|
|
dynState[] = state[];
|
|
return cast(CAllocatorImpl!A) dynState.ptr;
|
|
}
|
|
}
|
|
|
|
/// Ditto
|
|
CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa)
|
|
{
|
|
assert(pa);
|
|
import std.conv : emplace;
|
|
auto state = pa.allocate(stateSize!(CAllocatorImpl!(A, Yes.indirect)));
|
|
import std.traits : hasMember;
|
|
static if (hasMember!(A, "deallocate"))
|
|
{
|
|
scope(failure) pa.deallocate(state);
|
|
}
|
|
return emplace!(CAllocatorImpl!(A, Yes.indirect))
|
|
(state, pa);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
import std.experimental.allocator.mallocator;
|
|
IAllocator a = allocatorObject(Mallocator.it);
|
|
auto b = a.allocate(100);
|
|
assert(b.length == 100);
|
|
assert(a.deallocate(b) == Ternary.yes);
|
|
|
|
// The in-situ region must be used by pointer
|
|
import std.experimental.allocator.region;
|
|
auto r = InSituRegion!1024();
|
|
a = allocatorObject(&r);
|
|
b = a.allocate(200);
|
|
assert(b.length == 200);
|
|
// In-situ regions can't deallocate
|
|
assert(a.deallocate(b) == Ternary.unknown);
|
|
}
|
|
|
|
/**
|
|
Dynamically allocates (using $(D alloc)) and then creates in the memory
|
|
allocated an object of type $(D T), using $(D args) (if any) for its
|
|
initialization. Initialization occurs in the memory allocated and is otherwise
|
|
semantically the same as $(D T(args)).
|
|
(Note that using $(D alloc.make!(T[])) creates a pointer to an (empty) array
|
|
of $(D T)s, not an array. To use an allocator to allocate and initialize an
|
|
array, use $(D alloc.makeArray!T) described below.)
|
|
|
|
Params:
|
|
T = Type of the object being created.
|
|
alloc = The allocator used for getting the needed memory. It may be an object
|
|
implementing the static interface for allocators, or an $(D IAllocator)
|
|
reference.
|
|
args = Optional arguments used for initializing the created object. If not
|
|
present, the object is default constructed.
|
|
|
|
Returns: If $(D T) is a class type, returns a reference to the created $(D T)
|
|
object. Otherwise, returns a $(D T*) pointing to the created object. In all
|
|
cases, returns $(D null) if allocation failed.
|
|
|
|
Throws: If $(D T)'s constructor throws, deallocates the allocated memory and
|
|
propagates the exception.
|
|
*/
|
|
auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args)
|
|
{
|
|
import std.algorithm : max;
|
|
import std.conv : emplace;
|
|
auto m = alloc.allocate(max(stateSize!T, 1));
|
|
if (!m.ptr) return null;
|
|
scope(failure) alloc.deallocate(m);
|
|
static if (is(T == class)) return emplace!T(m, args);
|
|
else return emplace(cast(T*) m.ptr, args);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
// Dynamically allocate one integer
|
|
int* p1 = theAllocator.make!int;
|
|
// It's implicitly initialized with its .init value
|
|
assert(*p1 == 0);
|
|
// Dynamically allocate one double, initialize to 42.5
|
|
double* p2 = theAllocator.make!double(42.5);
|
|
assert(*p2 == 42.5);
|
|
|
|
// Dynamically allocate a struct
|
|
static struct Point
|
|
{
|
|
int x, y, z;
|
|
}
|
|
// Use the generated constructor taking field values in order
|
|
Point* p = theAllocator.make!Point(1, 2);
|
|
assert(p.x == 1 && p.y == 2 && p.z == 0);
|
|
|
|
// Dynamically allocate a class object
|
|
static class Customer
|
|
{
|
|
uint id = uint.max;
|
|
this() {}
|
|
this(uint id) { this.id = id; }
|
|
// ...
|
|
}
|
|
Customer cust = theAllocator.make!Customer;
|
|
assert(cust.id == uint.max); // default initialized
|
|
cust = theAllocator.make!Customer(42);
|
|
assert(cust.id == 42);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void test(Allocator)(auto ref Allocator alloc)
|
|
{
|
|
int* a = alloc.make!int(10);
|
|
assert(*a == 10);
|
|
|
|
struct A
|
|
{
|
|
int x;
|
|
string y;
|
|
double z;
|
|
}
|
|
|
|
A* b = alloc.make!A(42);
|
|
assert(b.x == 42);
|
|
assert(b.y is null);
|
|
import std.math;
|
|
assert(b.z.isNaN);
|
|
|
|
b = alloc.make!A(43, "44", 45);
|
|
assert(b.x == 43);
|
|
assert(b.y == "44");
|
|
assert(b.z == 45);
|
|
|
|
static class B
|
|
{
|
|
int x;
|
|
string y;
|
|
double z;
|
|
this(int _x, string _y = null, double _z = double.init)
|
|
{
|
|
x = _x;
|
|
y = _y;
|
|
z = _z;
|
|
}
|
|
}
|
|
|
|
B c = alloc.make!B(42);
|
|
assert(c.x == 42);
|
|
assert(c.y is null);
|
|
import std.math;
|
|
assert(c.z.isNaN);
|
|
|
|
c = alloc.make!B(43, "44", 45);
|
|
assert(c.x == 43);
|
|
assert(c.y == "44");
|
|
assert(c.z == 45);
|
|
|
|
auto parray = alloc.make!(int[]);
|
|
assert((*parray).empty);
|
|
}
|
|
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow
|
|
{
|
|
import core.stdc.string : memcpy;
|
|
if (!array.length) return;
|
|
memcpy(array.ptr, &filler, T.sizeof);
|
|
// Fill the array from the initialized portion of itself exponentially.
|
|
for (size_t offset = T.sizeof; offset < array.length; )
|
|
{
|
|
size_t extent = min(offset, array.length - offset);
|
|
memcpy(array.ptr + offset, array.ptr, extent);
|
|
offset += extent;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int[] a;
|
|
fillWithMemcpy(a, 42);
|
|
assert(a.length == 0);
|
|
a = [ 1, 2, 3, 4, 5 ];
|
|
fillWithMemcpy(a, 42);
|
|
assert(a == [ 42, 42, 42, 42, 42]);
|
|
}
|
|
|
|
private T[] uninitializedFillDefault(T)(T[] array) nothrow
|
|
{
|
|
static immutable __gshared T t;
|
|
fillWithMemcpy(array, t);
|
|
return array;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
int[] a = [1, 2, 4];
|
|
uninitializedFillDefault(a);
|
|
assert(a == [0, 0, 0]);
|
|
}
|
|
|
|
/**
|
|
Create an array of $(D T) with $(D length) elements using $(D alloc). The array is either default-initialized, filled with copies of $(D init), or initialized with values fetched from $(R range).
|
|
|
|
Params:
|
|
T = element type of the array being created
|
|
alloc = the allocator used for getting memory
|
|
length = length of the newly created array
|
|
init = element used for filling the array
|
|
range = range used for initializing the array elements
|
|
|
|
Returns:
|
|
The newly-created array, or $(D null) if either $(D length) was $(D 0) or
|
|
allocation failed.
|
|
|
|
Throws:
|
|
The first two overloads throw only if $(T alloc)'s primitives do. The
|
|
overloads that involve copy initialization deallocate memory and propagate the
|
|
exception if the copy operation throws.
|
|
*/
|
|
T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length)
|
|
{
|
|
if (!length) return null;
|
|
auto m = alloc.allocate(T.sizeof * length);
|
|
if (!m.ptr) return null;
|
|
return uninitializedFillDefault(cast(T[]) m);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void test(A)(auto ref A alloc)
|
|
{
|
|
int[] a = alloc.makeArray!int(0);
|
|
assert(a.length == 0 && a.ptr is null);
|
|
a = alloc.makeArray!int(5);
|
|
assert(a.length == 5);
|
|
assert(a == [ 0, 0, 0, 0, 0]);
|
|
}
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
/// Ditto
|
|
T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length,
|
|
auto ref T init)
|
|
{
|
|
if (!length) return null;
|
|
auto m = alloc.allocate(T.sizeof * length);
|
|
if (!m.ptr) return null;
|
|
auto result = cast(T[]) m;
|
|
import std.traits : hasElaborateCopyConstructor;
|
|
static if (hasElaborateCopyConstructor!T)
|
|
{
|
|
scope(failure) alloc.deallocate(m);
|
|
size_t i = 0;
|
|
static if (hasElaborateDestructor!T)
|
|
{
|
|
scope (failure)
|
|
{
|
|
foreach (j; 0 .. i)
|
|
{
|
|
destroy(result[j]);
|
|
}
|
|
}
|
|
}
|
|
for (; i < length; ++i)
|
|
{
|
|
emplace!T(result.ptr + i, init);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fillWithMemcpy(result, init);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
int[] a = theAllocator.makeArray!int(2);
|
|
assert(a == [0, 0]);
|
|
a = theAllocator.makeArray!int(3, 42);
|
|
assert(a == [42, 42, 42]);
|
|
import std.range : only;
|
|
a = theAllocator.makeArray!int(only(42, 43, 44));
|
|
assert(a == [42, 43, 44]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void test(A)(auto ref A alloc)
|
|
{
|
|
long[] a = alloc.makeArray!long(0, 42);
|
|
assert(a.length == 0 && a.ptr is null);
|
|
a = alloc.makeArray!long(5, 42);
|
|
assert(a.length == 5);
|
|
assert(a == [ 42, 42, 42, 42, 42 ]);
|
|
}
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
/// Ditto
|
|
T[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range)
|
|
if (isInputRange!R)
|
|
{
|
|
static if (isForwardRange!R)
|
|
{
|
|
size_t length = walkLength(range.save);
|
|
if (!length) return null;
|
|
auto m = alloc.allocate(T.sizeof * length);
|
|
if (!m.ptr) return null;
|
|
auto result = cast(T[]) m;
|
|
|
|
size_t i = 0;
|
|
scope (failure)
|
|
{
|
|
foreach (j; 0 .. i)
|
|
{
|
|
destroy(result[j]);
|
|
}
|
|
alloc.deallocate(m);
|
|
}
|
|
|
|
for (; !range.empty; range.popFront, ++i)
|
|
{
|
|
import std.conv : emplace;
|
|
emplace!T(result.ptr + i, range.front);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// Estimated size
|
|
size_t estimated = 8;
|
|
auto m = alloc.allocate(T.sizeof * estimated);
|
|
if (!m.ptr) return null;
|
|
auto result = cast(T[]) m;
|
|
|
|
size_t initialized = 0;
|
|
void bailout()
|
|
{
|
|
foreach (i; 0 .. initialized)
|
|
{
|
|
destroy(result[i]);
|
|
}
|
|
alloc.deallocate(m);
|
|
}
|
|
scope (failure) bailout;
|
|
|
|
for (; !range.empty; range.popFront, ++initialized)
|
|
{
|
|
if (initialized == estimated)
|
|
{
|
|
// Need to reallocate
|
|
if (!alloc.reallocate(m, T.sizeof * (estimated *= 2)))
|
|
{
|
|
bailout;
|
|
return null;
|
|
}
|
|
result = cast(T[]) m;
|
|
}
|
|
import std.conv : emplace;
|
|
emplace!T(result.ptr + initialized, range.front);
|
|
}
|
|
|
|
// Try to shrink memory, no harm if not possible
|
|
if (initialized < estimated
|
|
&& alloc.reallocate(m, T.sizeof * initialized))
|
|
{
|
|
result = cast(T[]) m;
|
|
}
|
|
|
|
return result[0 .. initialized];
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void test(A)(auto ref A alloc)
|
|
{
|
|
long[] a = alloc.makeArray!long((int[]).init);
|
|
assert(a.length == 0 && a.ptr is null);
|
|
a = alloc.makeArray!long([5, 42]);
|
|
assert(a.length == 2);
|
|
assert(a == [ 5, 42]);
|
|
}
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
version(unittest)
|
|
{
|
|
private struct ForcedInputRange
|
|
{
|
|
int[]* array;
|
|
bool empty() { return !array || (*array).empty; }
|
|
ref int front() { return (*array)[0]; }
|
|
void popFront() { *array = (*array)[1 .. $]; }
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import std.array, std.range;
|
|
int[] arr = iota(10).array;
|
|
|
|
void test(A)(auto ref A alloc)
|
|
{
|
|
ForcedInputRange r;
|
|
long[] a = alloc.makeArray!long(r);
|
|
assert(a.length == 0 && a.ptr is null);
|
|
auto arr2 = arr;
|
|
r.array = &arr2;
|
|
a = alloc.makeArray!long(r);
|
|
assert(a.length == 10);
|
|
assert(a == iota(10).array);
|
|
}
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
/**
|
|
Grows $(D array) by appending $(D delta) more elements. The needed memory is
|
|
allocated using $(D alloc). The extra elements added are either default-initialized, filled with copies of $(D init), or initialized with values fetched from $(R range).
|
|
|
|
Params:
|
|
T = element type of the array being created
|
|
alloc = the allocator used for getting memory
|
|
array = a reference to the array being grown
|
|
delta = number of elements to add (upon success the new length of $(D array) is $(D array.length + delta))
|
|
init = element used for filling the array
|
|
range = range used for initializing the array elements
|
|
|
|
Returns:
|
|
$(D true) upon success, $(D false) if memory could not be allocated. In the latter case $(D array) is left unaffected.
|
|
|
|
Throws:
|
|
The first two overloads throw only if $(T alloc)'s primitives do. The
|
|
overloads that involve copy initialization deallocate memory and propagate the
|
|
exception if the copy operation throws.
|
|
*/
|
|
bool growArray(T, Allocator)(auto ref Allocator alloc, ref T[] array,
|
|
size_t delta)
|
|
{
|
|
if (!delta) return true;
|
|
immutable oldLength = array.length;
|
|
void[] buf = array;
|
|
if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false;
|
|
array = cast(T[]) buf;
|
|
array[oldLength .. $].uninitializedFillDefault;
|
|
return true;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void test(A)(auto ref A alloc)
|
|
{
|
|
auto arr = alloc.makeArray!int([1, 2, 3]);
|
|
assert(alloc.growArray(arr, 3));
|
|
assert(arr == [1, 2, 3, 0, 0, 0]);
|
|
}
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
/// Ditto
|
|
auto growArray(T, Allocator)(auto ref Allocator alloc, T[] array,
|
|
size_t delta, auto ref T init)
|
|
{
|
|
if (!delta) return true;
|
|
void[] buf = array;
|
|
if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false;
|
|
immutable oldLength = array.length;
|
|
array = cast(T[]) buf;
|
|
scope(failure) array[oldLength .. $].uninitializedFillDefault;
|
|
import std.algorithm : uninitializedFill;
|
|
array[oldLength .. $].uninitializedFill(init);
|
|
return true;
|
|
}
|
|
|
|
/// Ditto
|
|
auto growArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array,
|
|
R range)
|
|
if (isInputRange!R)
|
|
{
|
|
static if (isForwardRange!R)
|
|
{
|
|
immutable delta = walkLength(range.save);
|
|
if (!delta) return true;
|
|
immutable oldLength = array.length;
|
|
|
|
// Reallocate support memory
|
|
void[] buf = array;
|
|
if (!alloc.reallocate(buf, buf.length + T.sizeof * delta))
|
|
{
|
|
return false;
|
|
}
|
|
array = cast(T[]) buf;
|
|
// At this point we're committed to the new length.
|
|
|
|
auto toFill = array[oldLength .. $];
|
|
scope (failure)
|
|
{
|
|
// Fill the remainder with default-constructed data
|
|
toFill.uninitializedFillDefault;
|
|
}
|
|
|
|
for (; !range.empty; range.popFront, toFill.popFront)
|
|
{
|
|
assert(!toFill.empty);
|
|
import std.conv : emplace;
|
|
emplace!T(&toFill.front, range.front);
|
|
}
|
|
assert(toFill.empty);
|
|
}
|
|
else
|
|
{
|
|
scope(failure)
|
|
{
|
|
// The last element didn't make it, fill with default
|
|
array[$ - 1 .. $].uninitializedFillDefault;
|
|
}
|
|
void[] buf = array;
|
|
for (; !range.empty; range.popFront)
|
|
{
|
|
if (!alloc.reallocate(buf, buf.length + T.sizeof))
|
|
{
|
|
array = cast(T[]) buf;
|
|
return false;
|
|
}
|
|
import std.conv : emplace;
|
|
emplace!T(buf[$ - T.sizeof .. $], range.front);
|
|
}
|
|
|
|
array = cast(T[]) buf;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
auto arr = theAllocator.makeArray!int([1, 2, 3]);
|
|
assert(theAllocator.growArray(arr, 2));
|
|
assert(arr == [1, 2, 3, 0, 0]);
|
|
import std.range : only;
|
|
assert(theAllocator.growArray(arr, only(4, 5)));
|
|
assert(arr == [1, 2, 3, 0, 0, 4, 5]);
|
|
|
|
ForcedInputRange r;
|
|
int[] b = [ 1, 2, 3, 4 ];
|
|
auto temp = b;
|
|
r.array = &temp;
|
|
assert(theAllocator.growArray(arr, r));
|
|
assert(arr == [1, 2, 3, 0, 0, 4, 5, 1, 2, 3, 4]);
|
|
}
|
|
|
|
/**
|
|
Shrinks an array by $(D delta) elements.
|
|
|
|
If $(D array.length < delta), does nothing and returns false. Otherwise,
|
|
destroys the last $(D array.length - delta) elements in the array and then
|
|
reallocates the array's buffer. If reallocation fails, fills the array with
|
|
default-initialized data.
|
|
|
|
Params:
|
|
T = element type of the array being created
|
|
alloc = the allocator used for getting memory
|
|
array = a reference to the array being shrunk
|
|
delta = number of elements to remove (upon success the new length of $(D array) is $(D array.length - delta))
|
|
|
|
Returns:
|
|
$(D true) upon success, $(D false) if memory could not be reallocated. In the latter case $(D array) is left with all elements default-initialized.
|
|
|
|
Throws:
|
|
The first two overloads throw only if $(T alloc)'s primitives do. The
|
|
overloads that involve copy initialization deallocate memory and propagate the
|
|
exception if the copy operation throws.
|
|
*/
|
|
bool shrinkArray(T, Allocator)(auto ref Allocator alloc,
|
|
ref T[] array, size_t delta)
|
|
{
|
|
if (delta > array.length) return false;
|
|
|
|
// Destroy elements. If a destructor throws, fill the already destroyed
|
|
// stuff with the default initializer.
|
|
{
|
|
size_t destroyed;
|
|
scope(failure)
|
|
{
|
|
array[$ - delta .. $][0 .. destroyed].uninitializedFillDefault;
|
|
}
|
|
foreach (ref e; array[$ - delta .. $])
|
|
{
|
|
e.destroy;
|
|
++destroyed;
|
|
}
|
|
}
|
|
|
|
if (delta == array.length)
|
|
{
|
|
alloc.deallocate(array);
|
|
array = null;
|
|
return true;
|
|
}
|
|
|
|
void[] buf = array;
|
|
if (!alloc.reallocate(buf, buf.length - T.sizeof * delta))
|
|
{
|
|
// urgh, at least fill back with default
|
|
array[$ - delta .. $].uninitializedFillDefault;
|
|
return false;
|
|
}
|
|
array = cast(T[]) buf;
|
|
return true;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
int[] a = theAllocator.makeArray!int(100, 42);
|
|
assert(a.length == 100);
|
|
assert(theAllocator.shrinkArray(a, 98));
|
|
assert(a.length == 2);
|
|
assert(a == [42, 42]);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
void test(A)(auto ref A alloc)
|
|
{
|
|
long[] a = alloc.makeArray!long((int[]).init);
|
|
assert(a.length == 0 && a.ptr is null);
|
|
a = alloc.makeArray!long(100, 42);
|
|
assert(alloc.shrinkArray(a, 98));
|
|
assert(a.length == 2);
|
|
assert(a == [ 42, 42]);
|
|
}
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
test(GCAllocator.it);
|
|
test(theAllocator);
|
|
}
|
|
|
|
/**
|
|
|
|
Destroys and then deallocates (using $(D alloc)) the object pointed to by a
|
|
pointer, the class object referred to by a $(D class) or $(D interface)
|
|
reference, or an entire array. It is assumed the respective entities had been
|
|
allocated with the same allocator.
|
|
|
|
*/
|
|
void dispose(A, T)(auto ref A alloc, T* p)
|
|
{
|
|
static if (hasElaborateDestructor!T)
|
|
{
|
|
destroy(*p);
|
|
}
|
|
alloc.deallocate(p[0 .. T.sizeof]);
|
|
}
|
|
|
|
/// Ditto
|
|
void dispose(A, T)(auto ref A alloc, T p)
|
|
if (is(T == class) || is(T == interface))
|
|
{
|
|
if (!p) return;
|
|
auto support = (cast(void*) p)[0 .. typeid(p).init.length];
|
|
destroy(p);
|
|
alloc.deallocate(support);
|
|
}
|
|
|
|
/// Ditto
|
|
void dispose(A, T)(auto ref A alloc, T[] array)
|
|
{
|
|
static if (hasElaborateDestructor!(typeof(array[0])))
|
|
{
|
|
foreach (ref e; array)
|
|
{
|
|
destroy(e);
|
|
}
|
|
}
|
|
alloc.deallocate(array);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
static int x;
|
|
static interface I
|
|
{
|
|
void method();
|
|
}
|
|
static class A : I
|
|
{
|
|
int y;
|
|
override void method() { x = 21; }
|
|
~this() { x = 42; }
|
|
}
|
|
static class B : A
|
|
{
|
|
}
|
|
auto a = theAllocator.make!A;
|
|
a.method();
|
|
assert(x == 21);
|
|
theAllocator.dispose(a);
|
|
assert(x == 42);
|
|
|
|
B b = theAllocator.make!B;
|
|
b.method();
|
|
assert(x == 21);
|
|
theAllocator.dispose(b);
|
|
assert(x == 42);
|
|
|
|
I i = theAllocator.make!B;
|
|
i.method();
|
|
assert(x == 21);
|
|
theAllocator.dispose(i);
|
|
assert(x == 42);
|
|
|
|
int[] arr = theAllocator.makeArray!int(43);
|
|
theAllocator.dispose(arr);
|
|
}
|
|
|
|
/**
|
|
|
|
Implementation of $(D IAllocator) using $(D Allocator). This adapts a
|
|
statically-built allocator type to $(D IAllocator) that is directly usable by
|
|
non-templated code.
|
|
|
|
Usually $(D CAllocatorImpl) is used indirectly by calling
|
|
$(LREF theAllocator).
|
|
*/
|
|
class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect)
|
|
: IAllocator
|
|
{
|
|
import std.traits : hasMember;
|
|
|
|
/**
|
|
The implementation is available as a public member.
|
|
*/
|
|
static if (indirect)
|
|
{
|
|
private Allocator* pimpl;
|
|
ref Allocator impl()
|
|
{
|
|
return *pimpl;
|
|
}
|
|
this(Allocator* pa)
|
|
{
|
|
pimpl = pa;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static if (stateSize!Allocator) Allocator impl;
|
|
else alias impl = Allocator.it;
|
|
}
|
|
|
|
/// Returns $(D impl.alignment).
|
|
override @property uint alignment()
|
|
{
|
|
return impl.alignment;
|
|
}
|
|
|
|
/**
|
|
Returns $(D impl.goodAllocSize(s)).
|
|
*/
|
|
override size_t goodAllocSize(size_t s)
|
|
{
|
|
return impl.goodAllocSize(s);
|
|
}
|
|
|
|
/**
|
|
Returns $(D impl.allocate(s)).
|
|
*/
|
|
override void[] allocate(size_t s, TypeInfo ti = null)
|
|
{
|
|
return impl.allocate(s);
|
|
}
|
|
|
|
/**
|
|
If $(D impl.alignedAllocate) exists, calls it, puts the result in $(D r),
|
|
and returns $(D Ternary.yes) or $(D Ternary.no) indicating whether
|
|
allocation succeded.
|
|
|
|
If $(D impl.alignedAllocate) is not defined, returns $(D Ternary.unknown).
|
|
*/
|
|
override Ternary alignedAllocate(size_t s, uint a, ref void[] r,
|
|
TypeInfo ti = null)
|
|
{
|
|
static if (!hasMember!(Allocator, "alignedAllocate"))
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
else
|
|
{
|
|
r = impl.alignedAllocate(s, a);
|
|
return Ternary(r.ptr !is null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Overridden only if $(D Allocator) implements $(D owns). In that case,
|
|
returns $(D impl.owns(b)).
|
|
*/
|
|
override Ternary owns(void[] b)
|
|
{
|
|
static if (hasMember!(Allocator, "owns")) return Ternary(impl.owns(b));
|
|
else return Ternary.unknown;
|
|
}
|
|
|
|
/// Returns $(D impl.expand(b, s)) if defined, $(D false) otherwise.
|
|
override Ternary expand(ref void[] b, size_t s)
|
|
{
|
|
static if (hasMember!(Allocator, "expand"))
|
|
return Ternary(impl.expand(b, s));
|
|
else
|
|
return Ternary.unknown;
|
|
}
|
|
|
|
/// Returns $(D impl.reallocate(b, s)).
|
|
override bool reallocate(ref void[] b, size_t s)
|
|
{
|
|
return impl.reallocate(b, s);
|
|
}
|
|
|
|
/// Forwards to $(D impl.alignedReallocate).
|
|
Ternary alignedReallocate(ref void[] b, size_t s, uint a)
|
|
{
|
|
static if (!hasMember!(Allocator, "alignedAllocate"))
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
else
|
|
{
|
|
return Ternary(impl.alignedReallocate(b, s, a));
|
|
}
|
|
}
|
|
|
|
Ternary resolveInternalPointer(void* p, ref void[] result)
|
|
{
|
|
static if (hasMember!(Allocator, "resolveInternalPointer"))
|
|
{
|
|
result = impl.resolveInternalPointer(p);
|
|
return Ternary(result.ptr !is null);
|
|
}
|
|
else
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
}
|
|
|
|
/**
|
|
If $(D impl.deallocate) is not defined, returns $(D Ternary.unknown). If
|
|
$(D impl.deallocate) returns $(D void) (the common case), calls it and
|
|
returns $(D Ternary.yes). If $(D impl.deallocate) returns $(D bool), calls
|
|
it and returns $(D Ternary.yes) for $(D true), $(D Ternary.no) for $(D
|
|
false).
|
|
*/
|
|
override Ternary deallocate(void[] b)
|
|
{
|
|
static if (hasMember!(Allocator, "deallocate"))
|
|
{
|
|
static if (is(typeof(impl.deallocate(b)) == bool))
|
|
{
|
|
return Ternary(impl.deallocate(b));
|
|
}
|
|
else
|
|
{
|
|
impl.deallocate(b);
|
|
return Ternary.yes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Calls $(D impl.deallocateAll()) and returns $(D Ternary.yes) if defined,
|
|
otherwise returns $(D Ternary.unknown).
|
|
*/
|
|
override Ternary deallocateAll()
|
|
{
|
|
static if (hasMember!(Allocator, "deallocateAll"))
|
|
{
|
|
impl.deallocateAll();
|
|
return Ternary.yes;
|
|
}
|
|
else
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Forwards to $(D impl.empty()) if defined, otherwise returns
|
|
$(D Ternary.unknown).
|
|
*/
|
|
override Ternary empty()
|
|
{
|
|
static if (hasMember!(Allocator, "empty"))
|
|
{
|
|
return Ternary(impl.empty);
|
|
}
|
|
else
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns $(D impl.allocateAll()) if present, $(D null) otherwise.
|
|
*/
|
|
override Ternary allocateAll(ref void[] result)
|
|
{
|
|
static if (hasMember!(Allocator, "allocateAll"))
|
|
{
|
|
result = impl.allocateAll();
|
|
return Ternary(result.ptr !is null);
|
|
}
|
|
else
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Forwards to $(D impl.zeroesAllocations) if defined, otherwise returns
|
|
$(D Ternary.unknown).
|
|
*/
|
|
override Ternary zeroesAllocations()
|
|
{
|
|
static if (hasMember!(Allocator, "zeroesAllocations"))
|
|
{
|
|
return Ternary(impl.zeroesAllocations);
|
|
}
|
|
else
|
|
{
|
|
return Ternary.unknown;
|
|
}
|
|
}
|
|
}
|