1 ///
2 module stdx.allocator.building_blocks.affix_allocator;
3 
4 /**
5 
6 Allocator that adds some extra data before (of type $(D Prefix)) and/or after
7 (of type $(D Suffix)) any allocation made with its parent allocator. This is
8 useful for uses where additional allocation-related information is needed, such
9 as mutexes, reference counts, or walls for debugging memory corruption errors.
10 
11 If $(D Prefix) is not $(D void), $(D Allocator) must guarantee an alignment at
12 least as large as $(D Prefix.alignof).
13 
14 Suffixes are slower to get at because of alignment rounding, so prefixes should
15 be preferred. However, small prefixes blunt the alignment so if a large
16 alignment with a small affix is needed, suffixes should be chosen.
17 
18 The following methods are defined if $(D Allocator) defines them, and forward to it: $(D deallocateAll), $(D empty), $(D owns).
19  */
20 struct AffixAllocator(Allocator, Prefix, Suffix = void)
21 {
22     import mir.utility : min;
23     import mir.conv : emplace;
24     import stdx.allocator : IAllocator, theAllocator;
25     import stdx.allocator.common : stateSize, forwardToMember,
26         roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf,
27         hasStaticallyKnownAlignment;
28     import stdx.allocator.internal : isPowerOf2;
29     import stdx.allocator.internal : Ternary;
30 
31     static if (hasStaticallyKnownAlignment!Allocator)
32     {
33         static assert(
34                 !stateSize!Prefix || Allocator.alignment >= Prefix.alignof,
35                 "AffixAllocator does not work with allocators offering a smaller"
36                 ~ " alignment than the prefix alignment.");
37     }
38     static assert(alignment % Suffix.alignof == 0,
39         "This restriction could be relaxed in the future.");
40 
41     /**
42     If $(D Prefix) is $(D void), the alignment is that of the parent. Otherwise, the alignment is the same as the $(D Prefix)'s alignment.
43     */
44     static if (hasStaticallyKnownAlignment!Allocator)
45     {
46         enum uint alignment = isPowerOf2(stateSize!Prefix)
47             ? min(stateSize!Prefix, Allocator.alignment)
48             : (stateSize!Prefix ? Prefix.alignof : Allocator.alignment);
49     }
50     else static if (is(Prefix == void))
51     {
52         enum uint alignment = platformAlignment;
53     }
54     else
55     {
56         enum uint alignment = Prefix.alignof;
57     }
58 
59     /**
60     If the parent allocator $(D Allocator) is stateful, an instance of it is
61     stored as a member. Otherwise, $(D AffixAllocator) uses
62     `Allocator.instance`. In either case, the name $(D _parent) is uniformly
63     used for accessing the parent allocator.
64     */
65     static if (stateSize!Allocator)
66     {
67         Allocator _parent;
68         static if (is(Allocator == IAllocator))
69         {
70             Allocator parent()
71             {
72                 if (_parent is null) _parent = theAllocator;
73                 assert(alignment <= _parent.alignment);
74                 return _parent;
75             }
76         }
77         else
78         {
79             alias parent = _parent;
80         }
81     }
82     else
83     {
84         alias parent = Allocator.instance;
85     }
86 
87     private template Impl(bool isStatic)
88     {
89 
90         size_t goodAllocSize(size_t s)
91         {
92             import stdx.allocator.common : goodAllocSize;
93             auto a = actualAllocationSize(s);
94             return roundUpToMultipleOf(parent.goodAllocSize(a)
95                     - stateSize!Prefix - stateSize!Suffix,
96                 this.alignment);
97         }
98 
99         static if (isStatic)
100         private size_t actualAllocationSize(size_t s)
101         {
102             assert(s > 0);
103             static if (!stateSize!Suffix)
104             {
105                 return s + stateSize!Prefix;
106             }
107             else
108             {
109                 return
110                     roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof)
111                     + stateSize!Suffix;
112             }
113         }
114         else
115         private size_t actualAllocationSize(size_t s) const
116         {
117             assert(s > 0);
118             static if (!stateSize!Suffix)
119             {
120                 return s + stateSize!Prefix;
121             }
122             else
123             {
124                 return
125                     roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof)
126                     + stateSize!Suffix;
127             }
128         }
129 
130         static if (isStatic)
131         private void[] actualAllocation(void[] b)
132         {
133             assert(b !is null);
134             return (b.ptr - stateSize!Prefix)
135                 [0 .. actualAllocationSize(b.length)];
136         }
137         else
138         private void[] actualAllocation(void[] b) const
139         {
140             assert(b !is null);
141             return (b.ptr - stateSize!Prefix)
142                 [0 .. actualAllocationSize(b.length)];
143         }
144 
145         void[] allocate(size_t bytes)
146         {
147             if (!bytes) return null;
148             auto result = parent.allocate(actualAllocationSize(bytes));
149             if (result is null) return null;
150             static if (stateSize!Prefix)
151             {
152                 assert(result.ptr.alignedAt(Prefix.alignof));
153                 emplace!Prefix(cast(Prefix*) result.ptr);
154             }
155             static if (stateSize!Suffix)
156             {
157                 auto suffixP = result.ptr + result.length - Suffix.sizeof;
158                 assert(suffixP.alignedAt(Suffix.alignof));
159                 emplace!Suffix(cast(Suffix*)(suffixP));
160             }
161             return result[stateSize!Prefix .. stateSize!Prefix + bytes];
162         }
163 
164         static if (__traits(hasMember, Allocator, "allocateAll"))
165         void[] allocateAll()
166         {
167             auto result = parent.allocateAll();
168             if (result is null) return null;
169             if (result.length < actualAllocationSize(1))
170             {
171                 deallocate(result);
172                 return null;
173             }
174             static if (stateSize!Prefix)
175             {
176                 assert(result.length > stateSize!Prefix);
177                 emplace!Prefix(cast(Prefix*) result.ptr);
178                 result = result[stateSize!Prefix .. $];
179             }
180             static if (stateSize!Suffix)
181             {
182                 assert(result.length > stateSize!Suffix);
183                 // Ehm, find a properly aligned place for the suffix
184                 auto p = (result.ptr + result.length - stateSize!Suffix)
185                     .alignDownTo(Suffix.alignof);
186                 assert(p > result.ptr);
187                 emplace!Suffix(cast(Suffix*) p);
188                 result = result[0 .. p - result.ptr];
189             }
190             return result;
191         }
192 
193         static if (__traits(hasMember, Allocator, "owns"))
194         Ternary owns(void[] b)
195         {
196             if (b is null) return Ternary.no;
197             return parent.owns(actualAllocation(b));
198         }
199 
200         static if (__traits(hasMember, Allocator, "resolveInternalPointer"))
201         Ternary resolveInternalPointer(const void* p, ref void[] result)
202         {
203             void[] p1;
204             Ternary r = parent.resolveInternalPointer(p, p1);
205             if (r != Ternary.yes || p1 is null)
206                 return r;
207             p1 = p1[stateSize!Prefix .. $];
208             auto p2 = (p1.ptr + p1.length - stateSize!Suffix)
209                       .alignDownTo(Suffix.alignof);
210             result = p1[0 .. p2 - p1.ptr];
211             return Ternary.yes;
212         }
213 
214         static if (!stateSize!Suffix && __traits(hasMember, Allocator, "expand"))
215         bool expand(ref void[] b, size_t delta)
216         {
217             if (!b.ptr) return delta == 0;
218             auto t = actualAllocation(b);
219             const result = parent.expand(t, delta);
220             if (!result) return false;
221             b = b.ptr[0 .. b.length + delta];
222             return true;
223         }
224 
225         static if (__traits(hasMember, Allocator, "reallocate"))
226         bool reallocate(ref void[] b, size_t s)
227         {
228             if (b is null)
229             {
230                 b = allocate(s);
231                 return b.length == s;
232             }
233             auto t = actualAllocation(b);
234             const result = parent.reallocate(t, actualAllocationSize(s));
235             if (!result) return false; // no harm done
236             b = t.ptr[stateSize!Prefix .. stateSize!Prefix + s];
237             return true;
238         }
239 
240         static if (__traits(hasMember, Allocator, "deallocate"))
241         bool deallocate(void[] b)
242         {
243             if (!b.ptr) return true;
244             return parent.deallocate(actualAllocation(b));
245         }
246 
247         /* The following methods are defined if $(D ParentAllocator) defines
248         them, and forward to it: $(D deallocateAll), $(D empty).*/
249         mixin(forwardToMember("parent",
250             "deallocateAll", "empty"));
251 
252         // Computes suffix type given buffer type
253         private template Payload2Affix(Payload, Affix)
254         {
255             static if (is(Payload[] : void[]))
256                 alias Payload2Affix = Affix;
257             else static if (is(Payload[] : shared(void)[]))
258                 alias Payload2Affix = shared Affix;
259             else static if (is(Payload[] : immutable(void)[]))
260                 alias Payload2Affix = shared Affix;
261             else static if (is(Payload[] : const(shared(void))[]))
262                 alias Payload2Affix = shared Affix;
263             else static if (is(Payload[] : const(void)[]))
264                 alias Payload2Affix = const Affix;
265             else
266                 static assert(0, "Internal error for type " ~ Payload.stringof);
267         }
268 
269         // Extra functions
270         static if (stateSize!Prefix)
271         {
272             static auto ref prefix(T)(T[] b)
273             {
274                 assert(b.ptr && b.ptr.alignedAt(Prefix.alignof));
275                 return (cast(Payload2Affix!(T, Prefix)*) b.ptr)[-1];
276             }
277         }
278         static if (stateSize!Suffix)
279             auto ref suffix(T)(T[] b)
280             {
281                 assert(b.ptr);
282                 auto p = b.ptr - stateSize!Prefix
283                     + actualAllocationSize(b.length);
284                 assert(p && p.alignedAt(Suffix.alignof));
285                 return (cast(Payload2Affix!(T, Suffix)*) p)[-1];
286             }
287     }
288 
289     version (StdDdoc)
290     {
291         /**
292         Standard allocator methods. Each is defined if and only if the parent
293         allocator defines the homonym method (except for $(D goodAllocSize),
294         which may use the global default). Also, the methods will be $(D
295         shared) if the parent allocator defines them as such.
296         */
297         size_t goodAllocSize(size_t);
298         /// Ditto
299         void[] allocate(size_t);
300         /// Ditto
301         Ternary owns(void[]);
302         /// Ditto
303         bool expand(ref void[] b, size_t delta);
304         /// Ditto
305         bool reallocate(ref void[] b, size_t s);
306         /// Ditto
307         bool deallocate(void[] b);
308         /// Ditto
309         bool deallocateAll();
310         /// Ditto
311         Ternary empty();
312 
313         /**
314         The `instance` singleton is defined if and only if the parent allocator
315         has no state and defines its own `it` object.
316         */
317         static AffixAllocator instance;
318 
319         /**
320         Affix access functions offering references to the affixes of a
321         block `b` previously allocated with this allocator. `b` may not be null.
322         They are defined if and only if the corresponding affix is not `void`.
323 
324         The qualifiers of the affix are not always the same as the qualifiers
325         of the argument. This is because the affixes are not part of the data
326         itself, but instead are just $(I associated) with the data and known
327         to the allocator. The table below documents the type of `preffix(b)` and
328         `affix(b)` depending on the type of `b`.
329 
330         $(BOOKTABLE Result of `prefix`/`suffix` depending on argument (`U` is
331         any unqualified type, `Affix` is `Prefix` or `Suffix`),
332             $(TR $(TH Argument$(NBSP)Type) $(TH Return) $(TH Comments))
333 
334             $(TR $(TD `shared(U)[]`) $(TD `ref shared Affix`)
335             $(TD Data is shared across threads and the affix follows suit.))
336 
337             $(TR $(TD `immutable(U)[]`) $(TD `ref shared Affix`)
338             $(TD Although the data is immutable, the allocator "knows" the
339             underlying memory is mutable, so `immutable` is elided for the affix
340             which is independent from the data itself. However, the result is
341             `shared` because `immutable` is implicitly shareable so multiple
342             threads may access and manipulate the affix for the same data.))
343 
344             $(TR $(TD `const(shared(U))[]`) $(TD `ref shared Affix`)
345             $(TD The data is always shareable across threads. Even if the data
346             is `const`, the affix is modifiable by the same reasoning as for
347             `immutable`.))
348 
349             $(TR $(TD `const(U)[]`) $(TD `ref const Affix`)
350             $(TD The input may have originated from `U[]` or `immutable(U)[]`,
351             so it may be actually shared or not. Returning an unqualified affix
352             may result in race conditions, whereas returning a `shared` affix
353             may result in inadvertent sharing of mutable thread-local data
354             across multiple threads. So the returned type is conservatively
355             `ref const`.))
356 
357             $(TR $(TD `U[]`) $(TD `ref Affix`)
358             $(TD Unqualified data has unqualified affixes.))
359         )
360 
361         Precondition: `b !is null` and `b` must have been allocated with
362         this allocator.
363         */
364         static ref auto prefix(T)(T[] b);
365         /// Ditto
366         ref auto suffix(T)(T[] b);
367     }
368     else static if (is(typeof(Allocator.instance) == shared))
369     { // for backward compatability
370         enum shared AffixAllocator instance = AffixAllocator();
371         static { mixin Impl!true; }
372     }
373     else
374     {
375         static if (stateSize!Allocator == 0)
376         {
377             enum AffixAllocator instance = AffixAllocator();
378             static { mixin Impl!true; }
379         }
380         else
381         {
382             mixin Impl!false;
383         }
384     }
385 }
386 
387 ///
388 @system unittest
389 {
390     import stdx.allocator.mallocator : Mallocator;
391     // One word before and after each allocation.
392     alias A = AffixAllocator!(Mallocator, size_t, size_t);
393     auto b = A.instance.allocate(11);
394     A.instance.prefix(b) = 0xCAFE_BABE;
395     A.instance.suffix(b) = 0xDEAD_BEEF;
396     assert(A.instance.prefix(b) == 0xCAFE_BABE
397         && A.instance.suffix(b) == 0xDEAD_BEEF);
398 }
399 
400 @system unittest
401 {
402     import stdx.allocator.gc_allocator : GCAllocator;
403     import stdx.allocator : theAllocator, IAllocator;
404 
405     // One word before and after each allocation.
406     auto A = AffixAllocator!(IAllocator, size_t, size_t)(theAllocator);
407     auto a = A.allocate(11);
408     A.prefix(a) = 0xCAFE_BABE;
409     A.suffix(a) = 0xDEAD_BEEF;
410     assert(A.prefix(a) == 0xCAFE_BABE
411         && A.suffix(a) == 0xDEAD_BEEF);
412 
413     // One word before and after each allocation.
414     auto B = AffixAllocator!(IAllocator, size_t, size_t)();
415     auto b = B.allocate(11);
416     B.prefix(b) = 0xCAFE_BABE;
417     B.suffix(b) = 0xDEAD_BEEF;
418     assert(B.prefix(b) == 0xCAFE_BABE
419         && B.suffix(b) == 0xDEAD_BEEF);
420 }
421 
422 @system unittest
423 {
424     import stdx.allocator.building_blocks.bitmapped_block
425         : BitmappedBlock;
426     import stdx.allocator.common : testAllocator;
427     testAllocator!({
428         auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong)
429             (BitmappedBlock!128(new ubyte[128 * 4096]));
430         return a;
431     });
432 }
433 
434 @system unittest
435 {
436     import stdx.allocator.mallocator : Mallocator;
437     alias A = AffixAllocator!(Mallocator, size_t);
438     auto b = A.instance.allocate(10);
439     A.instance.prefix(b) = 10;
440     assert(A.instance.prefix(b) == 10);
441 
442     import stdx.allocator.building_blocks.null_allocator
443         : NullAllocator;
444     alias B = AffixAllocator!(NullAllocator, size_t);
445     b = B.instance.allocate(100);
446     assert(b is null);
447 }
448 
449 @system unittest
450 {
451     import stdx.allocator;
452     import stdx.allocator.gc_allocator;
453     import stdx.allocator.internal : Ternary;
454     alias MyAllocator = AffixAllocator!(GCAllocator, uint);
455     auto a = MyAllocator.instance.makeArray!(shared int)(100);
456     static assert(is(typeof(&MyAllocator.instance.prefix(a)) == shared(uint)*));
457     auto b = MyAllocator.instance.makeArray!(shared const int)(100);
458     static assert(is(typeof(&MyAllocator.instance.prefix(b)) == shared(uint)*));
459     auto c = MyAllocator.instance.makeArray!(immutable int)(100);
460     static assert(is(typeof(&MyAllocator.instance.prefix(c)) == shared(uint)*));
461     auto d = MyAllocator.instance.makeArray!(int)(100);
462     static assert(is(typeof(&MyAllocator.instance.prefix(d)) == uint*));
463     auto e = MyAllocator.instance.makeArray!(const int)(100);
464     static assert(is(typeof(&MyAllocator.instance.prefix(e)) == const(uint)*));
465 
466     void[] p;
467     assert(MyAllocator.instance.resolveInternalPointer(null, p) == Ternary.no);
468     Ternary r = MyAllocator.instance.resolveInternalPointer(d.ptr, p);
469     assert(p.ptr is d.ptr && p.length >= d.length);
470 }