2 This module defines `TypedAllocator`, a statically-typed allocator that
3 aggregates multiple untyped allocators and uses them depending on the static
4 properties of the types allocated. For example, distinct allocators may be used
5 for thread-local vs. thread-shared data, or for fixed-size data (`struct`,
6 `class` objects) vs. resizable data (arrays).
9 T2=$(TR <td style="text-align:left">$(D $1)</td> $(TD $(ARGS $+)))
12 module std.experimental.allocator.typed;
14 import std.experimental.allocator;
15 import std.experimental.allocator.common;
16 import std.range : isInputRange, isForwardRange, walkLength, save, empty,
18 import std.traits : isPointer, hasElaborateDestructor;
19 import std.typecons : Flag, Yes, No;
22 Allocation-related flags dictated by type characteristics. `TypedAllocator`
23 deduces these flags from the type being allocated and uses the appropriate
24 allocator accordingly.
30 Fixed-size allocation (unlikely to get reallocated later). Examples: `int`,
31 `double`, any `struct` or `class` type. By default it is assumed that the
32 allocation is variable-size, i.e. susceptible to later reallocation
33 (for example all array types). This flag is advisory, i.e. in-place resizing
34 may be attempted for `fixedSize` allocations and may succeed. The flag is
35 just a hint to the compiler it may use allocation strategies that work well
36 with objects of fixed size.
40 The type being allocated embeds no pointers. Examples: `int`, `int[]`, $(D
41 Tuple!(int, float)). The implicit conservative assumption is that the type
42 has members with indirections so it needs to be scanned if garbage
43 collected. Example of types with pointers: `int*[]`, $(D Tuple!(int,
46 hasNoIndirections = 4,
48 By default it is conservatively assumed that allocated memory may be `cast`
49 to `shared`, passed across threads, and deallocated in a different thread
50 than the one that allocated it. If that's not the case, there are two
51 options. First, `immutableShared` means the memory is allocated for
52 `immutable` data and will be deallocated in the same thread it was
53 allocated in. Second, `threadLocal` means the memory is not to be shared
54 across threads at all. The two flags cannot be simultaneously present.
62 `TypedAllocator` acts like a chassis on which several specialized allocators
63 can be assembled. To let the system make a choice about a particular kind of
64 allocation, use `Default` for the respective parameters.
66 There is a hierarchy of allocation kinds. When an allocator is implemented for
67 a given combination of flags, it is used. Otherwise, the next down the list is
72 $(TR $(TH `AllocFlag` combination) $(TH Description))
74 $(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections
75 |$(NBSP)AllocFlag.fixedSize,
76 This is the most specific allocation policy: the memory being allocated is
77 thread local, has no indirections at all, and will not be reallocated. Examples
78 of types fitting this description: `int`, `double`, $(D Tuple!(int, long)), but
79 not $(D Tuple!(int, string)), which contains an indirection.)
81 $(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections,
82 As above, but may be reallocated later. Examples of types fitting this
83 description are $(D int[]), $(D double[]), $(D Tuple!(int, long)[]), but not
84 $(D Tuple!(int, string)[]), which contains an indirection.)
86 $(T2 AllocFlag.threadLocal,
87 As above, but may embed indirections. Examples of types fitting this
88 description are $(D int*[]), $(D Object[]), $(D Tuple!(int, string)[]).)
90 $(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections
91 |$(NBSP)AllocFlag.fixedSize,
92 The type being allocated is `immutable` and has no pointers. The thread that
93 allocated it must also deallocate it. Example: `immutable(int)`.)
95 $(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections,
96 As above, but the type may be appended to in the future. Example: `string`.)
98 $(T2 AllocFlag.immutableShared,
99 As above, but the type may embed references. Example: `immutable(Object)[]`.)
101 $(T2 AllocFlag.hasNoIndirections |$(NBSP)AllocFlag.fixedSize,
102 The type being allocated may be shared across threads, embeds no indirections,
105 $(T2 AllocFlag.hasNoIndirections,
106 The type being allocated may be shared across threads, may embed indirections,
107 and has variable size.)
109 $(T2 AllocFlag.fixedSize,
110 The type being allocated may be shared across threads, may embed indirections,
113 $(T2 0, The most conservative/general allocation: memory may be shared,
114 deallocated in a different thread, may or may not be resized, and may embed
119 PrimaryAllocator = The default allocator.
120 Policies = Zero or more pairs consisting of an `AllocFlag` and an allocator
123 struct TypedAllocator(PrimaryAllocator, Policies...)
125 import std.algorithm.sorting : isSorted;
126 import std.meta : AliasSeq;
127 import std.typecons : Tuple;
129 static assert(Policies.length == 0 || isSorted([Stride2!Policies]));
131 private template Stride2(T...)
133 static if (T.length >= 2)
135 alias Stride2 = AliasSeq!(T[0], Stride2!(T[2 .. $]));
139 alias Stride2 = AliasSeq!(T[0 .. $]);
144 static if (stateSize!PrimaryAllocator) private PrimaryAllocator primary;
145 else alias primary = PrimaryAllocator.instance;
146 static if (Policies.length > 0)
147 private Tuple!(Stride2!(Policies[1 .. $])) extras;
149 private static bool match(uint have, uint want)
152 ~(AllocFlag.immutableShared | AllocFlag.threadLocal);
153 // Do we offer thread local?
154 if (have & AllocFlag.threadLocal)
156 if (want & AllocFlag.threadLocal)
157 return match(have & maskAway, want & maskAway);
160 if (have & AllocFlag.immutableShared)
162 // Okay to ask for either thread local or immutable shared
163 if (want & (AllocFlag.threadLocal
164 | AllocFlag.immutableShared))
165 return match(have & maskAway, want & maskAway);
168 // From here on we have full-blown thread sharing.
169 if (have & AllocFlag.hasNoIndirections)
171 if (want & AllocFlag.hasNoIndirections)
172 return match(have & ~AllocFlag.hasNoIndirections,
173 want & ~AllocFlag.hasNoIndirections);
176 // Fixed size or variable size both match.
181 Given `flags` as a combination of `AllocFlag` values, or a type `T`, returns
182 the allocator that's a closest fit in capabilities.
184 auto ref allocatorFor(uint flags)()
186 static if (Policies.length == 0 || !match(Policies[0], flags))
190 else static if (Policies.length && match(Policies[$ - 2], flags))
192 return extras[$ - 1];
196 foreach (i, choice; Stride2!Policies)
198 static if (!match(choice, flags))
200 return extras[i - 1];
208 auto ref allocatorFor(T)()
210 static if (is(T == void[]))
216 return allocatorFor!(type2flags!T)();
221 Given a type `T`, returns its allocation-related flags as a combination of
224 static uint type2flags(T)()
227 static if (is(T == immutable))
228 result |= AllocFlag.immutableShared;
229 else static if (is(T == shared))
230 result |= AllocFlag.forSharing;
231 static if (!is(T == U[], U))
232 result |= AllocFlag.fixedSize;
233 import std.traits : hasIndirections;
234 static if (!hasIndirections!T)
235 result |= AllocFlag.hasNoIndirections;
240 Dynamically allocates (using the appropriate allocator chosen with
241 `allocatorFor!T`) and then creates in the memory allocated an object of
242 type `T`, using `args` (if any) for its initialization. Initialization
243 occurs in the memory allocated and is otherwise semantically the same as
244 `T(args)`. (Note that using `make!(T[])` creates a pointer to an
245 (empty) array of `T`s, not an array. To allocate and initialize an
246 array, use `makeArray!T` described below.)
249 T = Type of the object being created.
250 args = Optional arguments used for initializing the created object. If not
251 present, the object is default constructed.
253 Returns: If `T` is a class type, returns a reference to the created `T`
254 object. Otherwise, returns a `T*` pointing to the created object. In all
255 cases, returns `null` if allocation failed.
257 Throws: If `T`'s constructor throws, deallocates the allocated memory and
258 propagates the exception.
260 auto make(T, A...)(auto ref A args)
262 return .make!T(allocatorFor!T, args);
266 Create an array of `T` with `length` elements. The array is either
267 default-initialized, filled with copies of `init`, or initialized with
268 values fetched from `range`.
271 T = element type of the array being created
272 length = length of the newly created array
273 init = element used for filling the array
274 range = range used for initializing the array elements
277 The newly-created array, or `null` if either `length` was `0` or
281 The first two overloads throw only if the used allocator's primitives do.
282 The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws.
284 T[] makeArray(T)(size_t length)
286 return .makeArray!T(allocatorFor!(T[]), length);
290 T[] makeArray(T)(size_t length, auto ref T init)
292 return .makeArray!T(allocatorFor!(T[]), init, length);
296 T[] makeArray(T, R)(R range)
299 return .makeArray!T(allocatorFor!(T[]), range);
303 Grows `array` by appending `delta` more elements. The needed memory is
304 allocated using the same allocator that was used for the array type. The
305 extra elements added are either default-initialized, filled with copies of
306 `init`, or initialized with values fetched from `range`.
309 T = element type of the array being created
310 array = a reference to the array being grown
311 delta = number of elements to add (upon success the new length of `array`
312 is $(D array.length + delta))
313 init = element used for filling the array
314 range = range used for initializing the array elements
317 `true` upon success, `false` if memory could not be allocated. In the
318 latter case `array` is left unaffected.
321 The first two overloads throw only if the used allocator's primitives do.
322 The overloads that involve copy initialization deallocate memory and
323 propagate the exception if the copy operation throws.
325 bool expandArray(T)(ref T[] array, size_t delta)
327 return .expandArray(allocatorFor!(T[]), array, delta);
330 bool expandArray(T)(T[] array, size_t delta, auto ref T init)
332 return .expandArray(allocatorFor!(T[]), array, delta, init);
335 bool expandArray(T, R)(ref T[] array, R range)
338 return .expandArray(allocatorFor!(T[]), array, range);
342 Shrinks an array by `delta` elements using `allocatorFor!(T[])`.
344 If $(D arr.length < delta), does nothing and returns `false`. Otherwise,
345 destroys the last $(D arr.length - delta) elements in the array and then
346 reallocates the array's buffer. If reallocation fails, fills the array with
347 default-initialized data.
350 T = element type of the array being created
351 arr = a reference to the array being shrunk
352 delta = number of elements to remove (upon success the new length of
353 `arr` is $(D arr.length - delta))
356 `true` upon success, `false` if memory could not be reallocated. In the
357 latter case $(D arr[$ - delta .. $]) is left with default-initialized
361 The first two overloads throw only if the used allocator's primitives do.
362 The overloads that involve copy initialization deallocate memory and
363 propagate the exception if the copy operation throws.
365 bool shrinkArray(T)(ref T[] arr, size_t delta)
367 return .shrinkArray(allocatorFor!(T[]), arr, delta);
371 Destroys and then deallocates (using `allocatorFor!T`) the object pointed
372 to by a pointer, the class object referred to by a `class` or `interface`
373 reference, or an entire array. It is assumed the respective entities had
374 been allocated with the same allocator.
376 void dispose(T)(T* p)
378 return .dispose(allocatorFor!T, p);
382 if (is(T == class) || is(T == interface))
384 return .dispose(allocatorFor!T, p);
387 void dispose(T)(T[] array)
389 return .dispose(allocatorFor!(T[]), array);
396 import std.experimental.allocator.gc_allocator : GCAllocator;
397 import std.experimental.allocator.mallocator : Mallocator;
398 import std.experimental.allocator.mmap_allocator : MmapAllocator;
399 alias MyAllocator = TypedAllocator!(GCAllocator,
400 AllocFlag.fixedSize | AllocFlag.threadLocal, Mallocator,
401 AllocFlag.fixedSize | AllocFlag.threadLocal
402 | AllocFlag.hasNoIndirections,
406 auto b = &a.allocatorFor!0();
407 static assert(is(typeof(*b) == shared GCAllocator));
408 enum f1 = AllocFlag.fixedSize | AllocFlag.threadLocal;
409 auto c = &a.allocatorFor!f1();
410 static assert(is(typeof(*c) == Mallocator));
411 enum f2 = AllocFlag.fixedSize | AllocFlag.threadLocal;
412 static assert(is(typeof(a.allocatorFor!f2()) == Mallocator));
414 enum f3 = AllocFlag.threadLocal;
415 static assert(is(typeof(a.allocatorFor!f3()) == Mallocator));
418 scope(exit) a.dispose(p);
419 int[] arr = a.makeArray!int(42);
420 scope(exit) a.dispose(arr);
421 assert(a.expandArray(arr, 3));
422 assert(a.shrinkArray(arr, 4));