1 // Written in the D programming language.
3 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/affix_allocator.d)
5 module std.experimental.allocator.building_blocks.affix_allocator;
9 Allocator that adds some extra data before (of type `Prefix`) and/or after
10 (of type `Suffix`) any allocation made with its parent allocator. This is
11 useful for uses where additional allocation-related information is needed, such
12 as mutexes, reference counts, or walls for debugging memory corruption errors.
14 If `Prefix` is not `void`, `Allocator` must guarantee an alignment at
15 least as large as `Prefix.alignof`.
17 Suffixes are slower to get at because of alignment rounding, so prefixes should
18 be preferred. However, small prefixes blunt the alignment so if a large
19 alignment with a small affix is needed, suffixes should be chosen.
21 The following methods are defined if `Allocator` defines them, and forward to it: `deallocateAll`, `empty`, `owns`.
23 struct AffixAllocator(Allocator, Prefix, Suffix = void)
25 import std.algorithm.comparison : min;
26 import core.lifetime : emplace;
27 import std.experimental.allocator : RCIAllocator, theAllocator;
28 import std.experimental.allocator.common : stateSize, forwardToMember,
29 roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf,
30 hasStaticallyKnownAlignment;
31 import std.math.traits : isPowerOf2;
32 import std.traits : hasMember;
33 import std.typecons : Ternary;
35 static if (hasStaticallyKnownAlignment!Allocator)
38 !stateSize!Prefix || Allocator.alignment >= Prefix.alignof,
39 "AffixAllocator does not work with allocators offering a smaller"
40 ~ " alignment than the prefix alignment.");
42 static assert(alignment % Suffix.alignof == 0,
43 "This restriction could be relaxed in the future.");
46 If `Prefix` is `void`, the alignment is that of the parent. Otherwise, the alignment is the same as the `Prefix`'s alignment.
48 static if (hasStaticallyKnownAlignment!Allocator)
50 enum uint alignment = isPowerOf2(stateSize!Prefix)
51 ? min(stateSize!Prefix, Allocator.alignment)
52 : (stateSize!Prefix ? Prefix.alignof : Allocator.alignment);
54 else static if (is(Prefix == void))
56 enum uint alignment = platformAlignment;
60 enum uint alignment = Prefix.alignof;
64 If the parent allocator `Allocator` is stateful, an instance of it is
65 stored as a member. Otherwise, `AffixAllocator` uses
66 `Allocator.instance`. In either case, the name `_parent` is uniformly
67 used for accessing the parent allocator.
69 static if (stateSize!Allocator)
72 static if (is(Allocator == RCIAllocator))
74 @nogc nothrow pure @safe
78 RCIAllocator wrapAllocatorObject()
80 import std.experimental.allocator.gc_allocator : GCAllocator;
81 import std.experimental.allocator : allocatorObject;
83 return allocatorObject(GCAllocator.instance);
88 // If the `_parent` allocator is `null` we will assign
89 // an object that references the GC as the `parent`.
90 auto fn = (() @trusted =>
91 cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
95 // `RCIAllocator.alignment` currently doesn't have any attributes
96 // so we must cast; throughout the allocators module, `alignment`
97 // is defined as an `enum` for the existing allocators.
98 // `alignment` should always be `@nogc nothrow pure @safe`; once
99 // this is enforced by the interface we can remove the cast
100 auto pureAlign = (() @trusted =>
101 cast(uint delegate() @nogc nothrow pure @safe)(&_parent.alignment))();
102 assert(alignment <= pureAlign());
108 alias parent = _parent;
113 alias parent = Allocator.instance;
116 private template Impl()
119 size_t goodAllocSize(size_t s)
121 import std.experimental.allocator.common : goodAllocSize;
122 auto a = actualAllocationSize(s);
123 return roundUpToMultipleOf(parent.goodAllocSize(a)
124 - stateSize!Prefix - stateSize!Suffix,
128 private size_t actualAllocationSize(size_t s) const
131 static if (!stateSize!Suffix)
133 return s + stateSize!Prefix;
138 roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof)
143 private void[] actualAllocation(void[] b) const
146 return (b.ptr - stateSize!Prefix)
147 [0 .. actualAllocationSize(b.length)];
150 // Common code shared between allocate and allocateZeroed.
151 private enum _processAndReturnAllocateResult =
153 if (result is null) return null;
154 static if (stateSize!Prefix)
156 assert(result.ptr.alignedAt(Prefix.alignof));
157 emplace!Prefix(cast(Prefix*) result.ptr);
159 static if (stateSize!Suffix)
161 auto suffixP = result.ptr + result.length - Suffix.sizeof;
162 assert(suffixP.alignedAt(Suffix.alignof));
163 emplace!Suffix(cast(Suffix*)(suffixP));
165 return result[stateSize!Prefix .. stateSize!Prefix + bytes];
168 void[] allocate(size_t bytes)
170 if (!bytes) return null;
171 auto result = parent.allocate(actualAllocationSize(bytes));
172 mixin(_processAndReturnAllocateResult);
175 static if (hasMember!(Allocator, "allocateZeroed"))
176 package(std) void[] allocateZeroed()(size_t bytes)
178 if (!bytes) return null;
179 auto result = parent.allocateZeroed(actualAllocationSize(bytes));
180 mixin(_processAndReturnAllocateResult);
183 static if (hasMember!(Allocator, "allocateAll"))
186 auto result = parent.allocateAll();
187 if (result is null) return null;
188 if (result.length < actualAllocationSize(1))
193 static if (stateSize!Prefix)
195 assert(result.length > stateSize!Prefix);
196 emplace!Prefix(cast(Prefix*) result.ptr);
197 result = result[stateSize!Prefix .. $];
199 static if (stateSize!Suffix)
201 assert(result.length > stateSize!Suffix);
202 // Ehm, find a properly aligned place for the suffix
203 auto p = (result.ptr + result.length - stateSize!Suffix)
204 .alignDownTo(Suffix.alignof);
205 assert(p > result.ptr);
206 emplace!Suffix(cast(Suffix*) p);
207 result = result[0 .. p - result.ptr];
212 static if (hasMember!(Allocator, "owns"))
213 Ternary owns(void[] b)
215 if (b is null) return Ternary.no;
216 return parent.owns((() @trusted => actualAllocation(b))());
219 static if (hasMember!(Allocator, "resolveInternalPointer"))
220 Ternary resolveInternalPointer(const void* p, ref void[] result)
223 Ternary r = parent.resolveInternalPointer(p, p1);
224 if (r != Ternary.yes || p1 is null)
226 p1 = p1[stateSize!Prefix .. $];
227 auto p2 = (() @trusted => (&p1[0] + p1.length - stateSize!Suffix)
228 .alignDownTo(Suffix.alignof))();
229 result = p1[0 .. p2 - &p1[0]];
233 static if (!stateSize!Suffix && hasMember!(Allocator, "expand")
234 && hasMember!(Allocator, "owns"))
235 bool expand(ref void[] b, size_t delta)
237 if (!b || delta == 0) return delta == 0;
238 if (owns(b) == Ternary.no) return false;
239 auto t = (() @trusted => actualAllocation(b))();
240 const result = parent.expand(t, delta);
241 if (!result) return false;
242 b = (() @trusted => b.ptr[0 .. b.length + delta])();
246 static if (hasMember!(Allocator, "reallocate"))
247 bool reallocate(ref void[] b, size_t s)
252 return b.length == s;
254 auto t = actualAllocation(b);
255 const result = parent.reallocate(t, actualAllocationSize(s));
256 if (!result) return false; // no harm done
257 b = t.ptr[stateSize!Prefix .. stateSize!Prefix + s];
261 static if (hasMember!(Allocator, "deallocate"))
262 bool deallocate(void[] b)
264 if (!b.ptr) return true;
265 return parent.deallocate(actualAllocation(b));
268 /* The following methods are defined if `ParentAllocator` defines
269 them, and forward to it: `deallocateAll`, `empty`.*/
270 mixin(forwardToMember("parent",
271 "deallocateAll", "empty"));
273 // Computes suffix type given buffer type
274 private template Payload2Affix(Payload, Affix)
276 static if (is(Payload[] : void[]))
277 alias Payload2Affix = Affix;
278 else static if (is(Payload[] : shared(void)[]))
279 alias Payload2Affix = shared Affix;
280 else static if (is(Payload[] : immutable(void)[]))
281 alias Payload2Affix = shared Affix;
282 else static if (is(Payload[] : const(shared(void))[]))
283 alias Payload2Affix = shared Affix;
284 else static if (is(Payload[] : const(void)[]))
285 alias Payload2Affix = const Affix;
287 static assert(0, "Internal error for type " ~ Payload.stringof);
291 static if (stateSize!Prefix)
293 static auto ref prefix(T)(T[] b)
295 assert(b.ptr && b.ptr.alignedAt(Prefix.alignof));
296 return (cast(Payload2Affix!(T, Prefix)*) b.ptr)[-1];
299 static if (stateSize!Suffix)
300 auto ref suffix(T)(T[] b)
303 auto p = b.ptr - stateSize!Prefix
304 + actualAllocationSize(b.length);
305 assert(p && p.alignedAt(Suffix.alignof));
306 return (cast(Payload2Affix!(T, Suffix)*) p)[-1];
313 Standard allocator methods. Each is defined if and only if the parent
314 allocator defines the homonym method (except for `goodAllocSize`,
315 which may use the global default). Also, the methods will be $(D
316 shared) if the parent allocator defines them as such.
318 size_t goodAllocSize(size_t);
320 void[] allocate(size_t);
322 Ternary owns(void[]);
324 bool expand(ref void[] b, size_t delta);
326 bool reallocate(ref void[] b, size_t s);
328 bool deallocate(void[] b);
330 bool deallocateAll();
335 The `instance` singleton is defined if and only if the parent allocator
336 has no state and defines its own `it` object.
338 static AffixAllocator instance;
341 Affix access functions offering references to the affixes of a
342 block `b` previously allocated with this allocator. `b` may not be null.
343 They are defined if and only if the corresponding affix is not `void`.
345 The qualifiers of the affix are not always the same as the qualifiers
346 of the argument. This is because the affixes are not part of the data
347 itself, but instead are just $(I associated) with the data and known
348 to the allocator. The table below documents the type of `preffix(b)` and
349 `affix(b)` depending on the type of `b`.
351 $(BOOKTABLE Result of `prefix`/`suffix` depending on argument (`U` is
352 any unqualified type, `Affix` is `Prefix` or `Suffix`),
353 $(TR $(TH Argument$(NBSP)Type) $(TH Return) $(TH Comments))
355 $(TR $(TD `shared(U)[]`) $(TD `ref shared Affix`)
356 $(TD Data is shared across threads and the affix follows suit.))
358 $(TR $(TD `immutable(U)[]`) $(TD `ref shared Affix`)
359 $(TD Although the data is immutable, the allocator "knows" the
360 underlying memory is mutable, so `immutable` is elided for the affix
361 which is independent from the data itself. However, the result is
362 `shared` because `immutable` is implicitly shareable so multiple
363 threads may access and manipulate the affix for the same data.))
365 $(TR $(TD `const(shared(U))[]`) $(TD `ref shared Affix`)
366 $(TD The data is always shareable across threads. Even if the data
367 is `const`, the affix is modifiable by the same reasoning as for
370 $(TR $(TD `const(U)[]`) $(TD `ref const Affix`)
371 $(TD The input may have originated from `U[]` or `immutable(U)[]`,
372 so it may be actually shared or not. Returning an unqualified affix
373 may result in race conditions, whereas returning a `shared` affix
374 may result in inadvertent sharing of mutable thread-local data
375 across multiple threads. So the returned type is conservatively
378 $(TR $(TD `U[]`) $(TD `ref Affix`)
379 $(TD Unqualified data has unqualified affixes.))
382 Precondition: `b !is null` and `b` must have been allocated with
385 static ref auto prefix(T)(T[] b);
387 ref auto suffix(T)(T[] b);
389 else static if (is(typeof(Allocator.instance) == shared))
391 static assert(stateSize!Allocator == 0);
392 static shared AffixAllocator instance;
393 shared { mixin Impl!(); }
395 else static if (is(Allocator == shared))
397 static assert(stateSize!Allocator != 0);
398 shared { mixin Impl!(); }
403 static if (stateSize!Allocator == 0)
404 __gshared AffixAllocator instance;
411 import std.experimental.allocator.mallocator : Mallocator;
412 // One word before and after each allocation.
413 alias A = AffixAllocator!(Mallocator, size_t, size_t);
414 auto b = A.instance.allocate(11);
415 A.instance.prefix(b) = 0xCAFE_BABE;
416 A.instance.suffix(b) = 0xDEAD_BEEF;
417 assert(A.instance.prefix(b) == 0xCAFE_BABE
418 && A.instance.suffix(b) == 0xDEAD_BEEF);
423 import std.experimental.allocator.gc_allocator : GCAllocator;
424 import std.experimental.allocator : theAllocator, RCIAllocator;
426 // One word before and after each allocation.
427 auto A = AffixAllocator!(RCIAllocator, size_t, size_t)(theAllocator);
428 auto a = A.allocate(11);
429 A.prefix(a) = 0xCAFE_BABE;
430 A.suffix(a) = 0xDEAD_BEEF;
431 assert(A.prefix(a) == 0xCAFE_BABE
432 && A.suffix(a) == 0xDEAD_BEEF);
434 // One word before and after each allocation.
435 auto B = AffixAllocator!(RCIAllocator, size_t, size_t)();
436 auto b = B.allocate(11);
437 B.prefix(b) = 0xCAFE_BABE;
438 B.suffix(b) = 0xDEAD_BEEF;
439 assert(B.prefix(b) == 0xCAFE_BABE
440 && B.suffix(b) == 0xDEAD_BEEF);
443 version (StdUnittest)
446 import std.experimental.allocator.building_blocks.bitmapped_block
448 import std.experimental.allocator.common : testAllocator;
450 auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong)
451 (BitmappedBlock!128(new ubyte[128 * 4096]));
459 import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock;
460 import std.typecons : Ternary;
462 auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong)
463 (BitmappedBlock!128(new ubyte[128 * 4096]));
464 assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes);
465 auto b = a.allocate(42);
466 assert(b.length == 42);
467 assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.no);
472 import std.experimental.allocator.mallocator : Mallocator;
473 alias A = AffixAllocator!(Mallocator, size_t);
474 auto b = A.instance.allocate(10);
475 A.instance.prefix(b) = 10;
476 assert(A.instance.prefix(b) == 10);
478 import std.experimental.allocator.building_blocks.null_allocator
480 alias B = AffixAllocator!(NullAllocator, size_t);
481 b = B.instance.allocate(100);
487 import std.experimental.allocator;
488 import std.experimental.allocator.gc_allocator;
489 import std.typecons : Ternary;
490 alias MyAllocator = AffixAllocator!(GCAllocator, uint);
491 auto a = MyAllocator.instance.makeArray!(shared int)(100);
492 static assert(is(typeof(&MyAllocator.instance.prefix(a)) == shared(uint)*));
493 auto b = MyAllocator.instance.makeArray!(shared const int)(100);
494 static assert(is(typeof(&MyAllocator.instance.prefix(b)) == shared(uint)*));
495 auto c = MyAllocator.instance.makeArray!(immutable int)(100);
496 static assert(is(typeof(&MyAllocator.instance.prefix(c)) == shared(uint)*));
497 auto d = MyAllocator.instance.makeArray!(int)(100);
498 static assert(is(typeof(&MyAllocator.instance.prefix(d)) == uint*));
499 auto e = MyAllocator.instance.makeArray!(const int)(100);
500 static assert(is(typeof(&MyAllocator.instance.prefix(e)) == const(uint)*));
503 assert((() nothrow @safe @nogc => MyAllocator.instance.resolveInternalPointer(null, p))() == Ternary.no);
504 assert((() nothrow @safe => MyAllocator.instance.resolveInternalPointer(&d[0], p))() == Ternary.yes);
505 assert(p.ptr is d.ptr && p.length >= d.length);
510 import std.experimental.allocator.gc_allocator;
511 alias a = AffixAllocator!(GCAllocator, uint).instance;
513 // Check that goodAllocSize inherits from parent, i.e. GCAllocator
514 assert(__traits(compiles, (() nothrow @safe @nogc => a.goodAllocSize(1))()));
516 // Ensure deallocate inherits from parent
517 auto b = a.allocate(42);
518 assert(b.length == 42);
519 () nothrow @nogc { a.deallocate(b); }();
524 import std.experimental.allocator.building_blocks.region : Region;
526 auto a = AffixAllocator!(Region!(), uint)(Region!()(new ubyte[1024 * 64]));
527 auto b = a.allocate(42);
528 assert(b.length == 42);
529 // Test that expand infers from parent
530 assert((() pure nothrow @safe @nogc => a.expand(b, 58))());
531 assert(b.length == 100);
532 // Test that deallocateAll infers from parent
533 assert((() nothrow @nogc => a.deallocateAll())());
536 // Test that reallocate infers from parent
539 import std.experimental.allocator.mallocator : Mallocator;
541 alias a = AffixAllocator!(Mallocator, uint).instance;
542 auto b = a.allocate(42);
543 assert(b.length == 42);
544 assert((() nothrow @nogc => a.reallocate(b, 100))());
545 assert(b.length == 100);
546 assert((() nothrow @nogc => a.deallocate(b))());
551 import std.experimental.allocator : processAllocator, RCISharedAllocator;
554 alias SharedAllocT = shared AffixAllocator!(RCISharedAllocator, int);
555 static assert(is(RCISharedAllocator == shared));
556 static assert(!is(SharedAllocT.instance));
558 SharedAllocT a = SharedAllocT(processAllocator);
559 auto buf = a.allocate(10);
560 static assert(is(typeof(a.allocate) == shared));
561 assert(buf.length == 10);