]> git.ipfire.org Git - thirdparty/gcc.git/blob - libphobos/src/std/experimental/allocator/building_blocks/affix_allocator.d
d: Import dmd b8384668f, druntime e6caaab9, phobos 5ab9ad256 (v2.098.0-beta.1)
[thirdparty/gcc.git] / libphobos / src / std / experimental / allocator / building_blocks / affix_allocator.d
1 // Written in the D programming language.
2 /**
3 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/affix_allocator.d)
4 */
5 module std.experimental.allocator.building_blocks.affix_allocator;
6
7 /**
8
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.
13
14 If `Prefix` is not `void`, `Allocator` must guarantee an alignment at
15 least as large as `Prefix.alignof`.
16
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.
20
21 The following methods are defined if `Allocator` defines them, and forward to it: `deallocateAll`, `empty`, `owns`.
22 */
23 struct AffixAllocator(Allocator, Prefix, Suffix = void)
24 {
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;
34
35 static if (hasStaticallyKnownAlignment!Allocator)
36 {
37 static assert(
38 !stateSize!Prefix || Allocator.alignment >= Prefix.alignof,
39 "AffixAllocator does not work with allocators offering a smaller"
40 ~ " alignment than the prefix alignment.");
41 }
42 static assert(alignment % Suffix.alignof == 0,
43 "This restriction could be relaxed in the future.");
44
45 /**
46 If `Prefix` is `void`, the alignment is that of the parent. Otherwise, the alignment is the same as the `Prefix`'s alignment.
47 */
48 static if (hasStaticallyKnownAlignment!Allocator)
49 {
50 enum uint alignment = isPowerOf2(stateSize!Prefix)
51 ? min(stateSize!Prefix, Allocator.alignment)
52 : (stateSize!Prefix ? Prefix.alignof : Allocator.alignment);
53 }
54 else static if (is(Prefix == void))
55 {
56 enum uint alignment = platformAlignment;
57 }
58 else
59 {
60 enum uint alignment = Prefix.alignof;
61 }
62
63 /**
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.
68 */
69 static if (stateSize!Allocator)
70 {
71 Allocator _parent;
72 static if (is(Allocator == RCIAllocator))
73 {
74 @nogc nothrow pure @safe
75 Allocator parent()
76 {
77 static @nogc nothrow
78 RCIAllocator wrapAllocatorObject()
79 {
80 import std.experimental.allocator.gc_allocator : GCAllocator;
81 import std.experimental.allocator : allocatorObject;
82
83 return allocatorObject(GCAllocator.instance);
84 }
85
86 if (_parent.isNull)
87 {
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))();
92 _parent = fn();
93 }
94
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());
103 return _parent;
104 }
105 }
106 else
107 {
108 alias parent = _parent;
109 }
110 }
111 else
112 {
113 alias parent = Allocator.instance;
114 }
115
116 private template Impl()
117 {
118
119 size_t goodAllocSize(size_t s)
120 {
121 import std.experimental.allocator.common : goodAllocSize;
122 auto a = actualAllocationSize(s);
123 return roundUpToMultipleOf(parent.goodAllocSize(a)
124 - stateSize!Prefix - stateSize!Suffix,
125 this.alignment);
126 }
127
128 private size_t actualAllocationSize(size_t s) const
129 {
130 assert(s > 0);
131 static if (!stateSize!Suffix)
132 {
133 return s + stateSize!Prefix;
134 }
135 else
136 {
137 return
138 roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof)
139 + stateSize!Suffix;
140 }
141 }
142
143 private void[] actualAllocation(void[] b) const
144 {
145 assert(b !is null);
146 return (b.ptr - stateSize!Prefix)
147 [0 .. actualAllocationSize(b.length)];
148 }
149
150 // Common code shared between allocate and allocateZeroed.
151 private enum _processAndReturnAllocateResult =
152 q{
153 if (result is null) return null;
154 static if (stateSize!Prefix)
155 {
156 assert(result.ptr.alignedAt(Prefix.alignof));
157 emplace!Prefix(cast(Prefix*) result.ptr);
158 }
159 static if (stateSize!Suffix)
160 {
161 auto suffixP = result.ptr + result.length - Suffix.sizeof;
162 assert(suffixP.alignedAt(Suffix.alignof));
163 emplace!Suffix(cast(Suffix*)(suffixP));
164 }
165 return result[stateSize!Prefix .. stateSize!Prefix + bytes];
166 };
167
168 void[] allocate(size_t bytes)
169 {
170 if (!bytes) return null;
171 auto result = parent.allocate(actualAllocationSize(bytes));
172 mixin(_processAndReturnAllocateResult);
173 }
174
175 static if (hasMember!(Allocator, "allocateZeroed"))
176 package(std) void[] allocateZeroed()(size_t bytes)
177 {
178 if (!bytes) return null;
179 auto result = parent.allocateZeroed(actualAllocationSize(bytes));
180 mixin(_processAndReturnAllocateResult);
181 }
182
183 static if (hasMember!(Allocator, "allocateAll"))
184 void[] allocateAll()
185 {
186 auto result = parent.allocateAll();
187 if (result is null) return null;
188 if (result.length < actualAllocationSize(1))
189 {
190 deallocate(result);
191 return null;
192 }
193 static if (stateSize!Prefix)
194 {
195 assert(result.length > stateSize!Prefix);
196 emplace!Prefix(cast(Prefix*) result.ptr);
197 result = result[stateSize!Prefix .. $];
198 }
199 static if (stateSize!Suffix)
200 {
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];
208 }
209 return result;
210 }
211
212 static if (hasMember!(Allocator, "owns"))
213 Ternary owns(void[] b)
214 {
215 if (b is null) return Ternary.no;
216 return parent.owns((() @trusted => actualAllocation(b))());
217 }
218
219 static if (hasMember!(Allocator, "resolveInternalPointer"))
220 Ternary resolveInternalPointer(const void* p, ref void[] result)
221 {
222 void[] p1;
223 Ternary r = parent.resolveInternalPointer(p, p1);
224 if (r != Ternary.yes || p1 is null)
225 return r;
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]];
230 return Ternary.yes;
231 }
232
233 static if (!stateSize!Suffix && hasMember!(Allocator, "expand")
234 && hasMember!(Allocator, "owns"))
235 bool expand(ref void[] b, size_t delta)
236 {
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])();
243 return true;
244 }
245
246 static if (hasMember!(Allocator, "reallocate"))
247 bool reallocate(ref void[] b, size_t s)
248 {
249 if (b is null)
250 {
251 b = allocate(s);
252 return b.length == s;
253 }
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];
258 return true;
259 }
260
261 static if (hasMember!(Allocator, "deallocate"))
262 bool deallocate(void[] b)
263 {
264 if (!b.ptr) return true;
265 return parent.deallocate(actualAllocation(b));
266 }
267
268 /* The following methods are defined if `ParentAllocator` defines
269 them, and forward to it: `deallocateAll`, `empty`.*/
270 mixin(forwardToMember("parent",
271 "deallocateAll", "empty"));
272
273 // Computes suffix type given buffer type
274 private template Payload2Affix(Payload, Affix)
275 {
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;
286 else
287 static assert(0, "Internal error for type " ~ Payload.stringof);
288 }
289
290 // Extra functions
291 static if (stateSize!Prefix)
292 {
293 static auto ref prefix(T)(T[] b)
294 {
295 assert(b.ptr && b.ptr.alignedAt(Prefix.alignof));
296 return (cast(Payload2Affix!(T, Prefix)*) b.ptr)[-1];
297 }
298 }
299 static if (stateSize!Suffix)
300 auto ref suffix(T)(T[] b)
301 {
302 assert(b.ptr);
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];
307 }
308 }
309
310 version (StdDdoc)
311 {
312 /**
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.
317 */
318 size_t goodAllocSize(size_t);
319 /// Ditto
320 void[] allocate(size_t);
321 /// Ditto
322 Ternary owns(void[]);
323 /// Ditto
324 bool expand(ref void[] b, size_t delta);
325 /// Ditto
326 bool reallocate(ref void[] b, size_t s);
327 /// Ditto
328 bool deallocate(void[] b);
329 /// Ditto
330 bool deallocateAll();
331 /// Ditto
332 Ternary empty();
333
334 /**
335 The `instance` singleton is defined if and only if the parent allocator
336 has no state and defines its own `it` object.
337 */
338 static AffixAllocator instance;
339
340 /**
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`.
344
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`.
350
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))
354
355 $(TR $(TD `shared(U)[]`) $(TD `ref shared Affix`)
356 $(TD Data is shared across threads and the affix follows suit.))
357
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.))
364
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
368 `immutable`.))
369
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
376 `ref const`.))
377
378 $(TR $(TD `U[]`) $(TD `ref Affix`)
379 $(TD Unqualified data has unqualified affixes.))
380 )
381
382 Precondition: `b !is null` and `b` must have been allocated with
383 this allocator.
384 */
385 static ref auto prefix(T)(T[] b);
386 /// Ditto
387 ref auto suffix(T)(T[] b);
388 }
389 else static if (is(typeof(Allocator.instance) == shared))
390 {
391 static assert(stateSize!Allocator == 0);
392 static shared AffixAllocator instance;
393 shared { mixin Impl!(); }
394 }
395 else static if (is(Allocator == shared))
396 {
397 static assert(stateSize!Allocator != 0);
398 shared { mixin Impl!(); }
399 }
400 else
401 {
402 mixin Impl!();
403 static if (stateSize!Allocator == 0)
404 __gshared AffixAllocator instance;
405 }
406 }
407
408 ///
409 @system unittest
410 {
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);
419 }
420
421 @system unittest
422 {
423 import std.experimental.allocator.gc_allocator : GCAllocator;
424 import std.experimental.allocator : theAllocator, RCIAllocator;
425
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);
433
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);
441 }
442
443 version (StdUnittest)
444 @system unittest
445 {
446 import std.experimental.allocator.building_blocks.bitmapped_block
447 : BitmappedBlock;
448 import std.experimental.allocator.common : testAllocator;
449 testAllocator!({
450 auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong)
451 (BitmappedBlock!128(new ubyte[128 * 4096]));
452 return a;
453 });
454 }
455
456 // Test empty
457 @system unittest
458 {
459 import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock;
460 import std.typecons : Ternary;
461
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);
468 }
469
470 @system unittest
471 {
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);
477
478 import std.experimental.allocator.building_blocks.null_allocator
479 : NullAllocator;
480 alias B = AffixAllocator!(NullAllocator, size_t);
481 b = B.instance.allocate(100);
482 assert(b is null);
483 }
484
485 @system unittest
486 {
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)*));
501
502 void[] p;
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);
506 }
507
508 @system unittest
509 {
510 import std.experimental.allocator.gc_allocator;
511 alias a = AffixAllocator!(GCAllocator, uint).instance;
512
513 // Check that goodAllocSize inherits from parent, i.e. GCAllocator
514 assert(__traits(compiles, (() nothrow @safe @nogc => a.goodAllocSize(1))()));
515
516 // Ensure deallocate inherits from parent
517 auto b = a.allocate(42);
518 assert(b.length == 42);
519 () nothrow @nogc { a.deallocate(b); }();
520 }
521
522 @system unittest
523 {
524 import std.experimental.allocator.building_blocks.region : Region;
525
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())());
534 }
535
536 // Test that reallocate infers from parent
537 @system unittest
538 {
539 import std.experimental.allocator.mallocator : Mallocator;
540
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))());
547 }
548
549 @system unittest
550 {
551 import std.experimental.allocator : processAllocator, RCISharedAllocator;
552 import std.traits;
553
554 alias SharedAllocT = shared AffixAllocator!(RCISharedAllocator, int);
555 static assert(is(RCISharedAllocator == shared));
556 static assert(!is(SharedAllocT.instance));
557
558 SharedAllocT a = SharedAllocT(processAllocator);
559 auto buf = a.allocate(10);
560 static assert(is(typeof(a.allocate) == shared));
561 assert(buf.length == 10);
562 }