1 // Modified version of std/experimental/allocator/building_blocks/_free_list.d
2 // Orginal version did not support deallocateAll without parent allocator supporting it
3 // This version supports deallocateAll by using parents Allocator.deallocate to deallocate whole support buffer
4 module mutils.allocator.free_list;
5 
6 import std.experimental.allocator.common;
7 import std.typecons : Flag, Yes, No;
8 
9 /*
10 Returns `true` if `ptr` is aligned at `alignment`.
11 */
12 @nogc nothrow pure
13 package bool alignedAt(T)(T* ptr, uint alignment)
14 {
15     return cast(size_t) ptr % alignment == 0;
16 }
17 
18 struct FreeList(ParentAllocator,
19     size_t minSize, size_t maxSize = minSize,
20     Flag!"adaptive" adaptive = No.adaptive)
21 {
22     import std.conv : text;
23     import std.exception : enforce;
24     import std.traits : hasMember;
25     import std.typecons : Ternary;
26     import std.experimental.allocator.building_blocks.null_allocator : NullAllocator;
27 
28     static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
29     static assert(maxSize >= (void*).sizeof,
30         "Maximum size must accommodate a pointer.");
31 
32     private enum unchecked = minSize == 0 && maxSize == unbounded;
33 
34     private enum hasTolerance = !unchecked && (minSize != maxSize
35         || maxSize == chooseAtRuntime);
36 
37     static if (minSize == chooseAtRuntime)
38     {
39         /**
40         Returns the smallest allocation size eligible for allocation from the
41         freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias
42         for `minSize`.)
43         */
44         @property size_t min() const
45         {
46             assert(_min != chooseAtRuntime);
47             return _min;
48         }
49         /**
50         If `FreeList` has been instantiated with $(D minSize ==
51         chooseAtRuntime), then the `min` property is writable. Setting it
52         must precede any allocation.
53 
54         Params:
55         low = new value for `min`
56 
57         Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and
58         `max` has not yet been initialized. Also, no allocation has been
59         yet done with this allocator.
60 
61         Postcondition: $(D min == low)
62         */
63         @property void min(size_t low)
64         {
65             assert(low <= max || max == chooseAtRuntime);
66             minimize;
67             _min = low;
68         }
69     }
70     else
71     {
72         alias min = minSize;
73     }
74 
75     static if (maxSize == chooseAtRuntime)
76     {
77         /**
78         Returns the largest allocation size eligible for allocation from the
79         freelist. (If $(D maxSize != chooseAtRuntime), this is simply an alias
80         for `maxSize`.) All allocation requests for sizes greater than or
81         equal to `min` and less than or equal to `max` are rounded to $(D
82         max) and forwarded to the parent allocator. When the block fitting the
83         same constraint gets deallocated, it is put in the freelist with the
84         allocated size assumed to be `max`.
85         */
86         @property size_t max() const { return _max; }
87 
88         /**
89         If `FreeList` has been instantiated with $(D maxSize ==
90         chooseAtRuntime), then the `max` property is writable. Setting it
91         must precede any allocation.
92 
93         Params:
94         high = new value for `max`
95 
96         Precondition: $(D high >= min), or $(D minSize == chooseAtRuntime) and
97         `min` has not yet been initialized. Also $(D high >= (void*).sizeof). Also, no allocation has been yet done with this allocator.
98 
99         Postcondition: $(D max == high)
100         */
101         @property void max(size_t high)
102         {
103             assert((high >= min || min == chooseAtRuntime)
104                 && high >= (void*).sizeof);
105             minimize;
106             _max = high;
107         }
108 
109         @system unittest
110         {
111             import std.experimental.allocator.common : chooseAtRuntime;
112             import std.experimental.allocator.mallocator : Mallocator;
113 
114             FreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
115             a.min = 64;
116             a.max = 128;
117             assert(a.min == 64);
118             assert(a.max == 128);
119         }
120     }
121     else
122     {
123         alias max = maxSize;
124     }
125 
126     private bool tooSmall(size_t n) const
127     {
128         static if (minSize == 0) return false;
129         else return n < min;
130     }
131 
132     private bool tooLarge(size_t n) const
133     {
134         static if (maxSize == unbounded) return false;
135         else return n > max;
136     }
137 
138     private bool freeListEligible(size_t n) const
139     {
140         static if (unchecked)
141         {
142             return true;
143         }
144         else
145         {
146             static if (minSize == 0)
147             {
148                 if (!n) return false;
149             }
150             static if (minSize == maxSize && minSize != chooseAtRuntime)
151                 return n == maxSize;
152             else
153                 return !tooSmall(n) && !tooLarge(n);
154         }
155     }
156 
157     static if (!unchecked)
158     private void[] blockFor(Node* p)
159     {
160         assert(p);
161         return (cast(void*) p)[0 .. max];
162     }
163 
164     // statistics
165     static if (adaptive == Yes.adaptive)
166     {
167         private enum double windowLength = 1000.0;
168         private enum double tooFewMisses = 0.01;
169         private double probMiss = 1.0; // start with a high miss probability
170         private uint accumSamples, accumMisses;
171 
172         void updateStats()
173         {
174             assert(accumSamples >= accumMisses);
175             /*
176             Given that for the past windowLength samples we saw misses with
177             estimated probability probMiss, and assuming the new sample wasMiss or
178             not, what's the new estimated probMiss?
179             */
180             probMiss = (probMiss * windowLength + accumMisses)
181                 / (windowLength + accumSamples);
182             assert(probMiss <= 1.0);
183             accumSamples = 0;
184             accumMisses = 0;
185             // If probability to miss is under x%, yank one off the freelist
186             static if (!unchecked)
187             {
188                 if (probMiss < tooFewMisses && _root)
189                 {
190                     auto b = blockFor(_root);
191                     _root = _root.next;
192                     parent.deallocate(b);
193                 }
194             }
195         }
196     }
197 
198     private struct Node { Node* next; }
199     static assert(ParentAllocator.alignment >= Node.alignof);
200 
201     // state
202     /**
203     The parent allocator. Depending on whether `ParentAllocator` holds state
204     or not, this is a member variable or an alias for
205     `ParentAllocator.instance`.
206     */
207     static if (stateSize!ParentAllocator) ParentAllocator parent;
208     else alias parent = ParentAllocator.instance;
209     private Node* root;
210     static if (minSize == chooseAtRuntime) private size_t _min = chooseAtRuntime;
211     static if (maxSize == chooseAtRuntime) private size_t _max = chooseAtRuntime;
212 
213     /**
214     Alignment offered.
215     */
216     alias alignment = ParentAllocator.alignment;
217 
218     /**
219     If $(D maxSize == unbounded), returns  `parent.goodAllocSize(bytes)`.
220     Otherwise, returns `max` for sizes in the interval $(D [min, max]), and
221     `parent.goodAllocSize(bytes)` otherwise.
222 
223     Precondition:
224     If set at runtime, `min` and/or `max` must be initialized
225     appropriately.
226 
227     Postcondition:
228     $(D result >= bytes)
229     */
230     size_t goodAllocSize(size_t bytes)
231     {
232         assert(minSize != chooseAtRuntime && maxSize != chooseAtRuntime);
233         static if (maxSize != unbounded)
234         {
235             if (freeListEligible(bytes))
236             {
237                 assert(parent.goodAllocSize(max) == max,
238                     text("Wrongly configured freelist: maximum should be ",
239                         parent.goodAllocSize(max), " instead of ", max));
240                 return max;
241             }
242         }
243         return parent.goodAllocSize(bytes);
244     }
245 
246     private void[] allocateEligible(size_t bytes)
247     {
248         assert(bytes);
249         if (root)
250         {
251             // faster
252             auto result = (cast(ubyte*) root)[0 .. bytes];
253             root = root.next;
254             return result;
255         }
256         // slower
257         static if (hasTolerance)
258         {
259             immutable toAllocate = max;
260         }
261         else
262         {
263             alias toAllocate = bytes;
264         }
265         assert(toAllocate == max || max == unbounded);
266         auto result = parent.allocate(toAllocate);
267         static if (hasTolerance)
268         {
269             if (result) result = result.ptr[0 .. bytes];
270         }
271         static if (adaptive == Yes.adaptive)
272         {
273             ++accumMisses;
274             updateStats;
275         }
276         return result;
277     }
278 
279     /**
280     Allocates memory either off of the free list or from the parent allocator.
281     If `n` is within $(D [min, max]) or if the free list is unchecked
282     ($(D minSize == 0 && maxSize == size_t.max)), then the free list is
283     consulted first. If not empty (hit), the block at the front of the free
284     list is removed from the list and returned. Otherwise (miss), a new block
285     of `max` bytes is allocated, truncated to `n` bytes, and returned.
286 
287     Params:
288     n = number of bytes to allocate
289 
290     Returns:
291     The allocated block, or `null`.
292 
293     Precondition:
294     If set at runtime, `min` and/or `max` must be initialized
295     appropriately.
296 
297     Postcondition: $(D result.length == bytes || result is null)
298     */
299     void[] allocate(size_t n)
300     {
301         static if (adaptive == Yes.adaptive) ++accumSamples;
302         assert(n < size_t.max / 2);
303         // fast path
304         if (freeListEligible(n))
305         {
306             return allocateEligible(n);
307         }
308         // slower
309         static if (adaptive == Yes.adaptive)
310         {
311             updateStats;
312         }
313         return parent.allocate(n);
314     }
315 
316     // Forwarding methods
317     mixin(forwardToMember("parent",
318         "expand", "owns", "reallocate"));
319 
320     /**
321     If `block.length` is within $(D [min, max]) or if the free list is
322     unchecked ($(D minSize == 0 && maxSize == size_t.max)), then inserts the
323     block at the front of the free list. For all others, forwards to $(D
324     parent.deallocate) if `Parent.deallocate` is defined.
325 
326     Params:
327     block = Block to deallocate.
328 
329     Precondition:
330     If set at runtime, `min` and/or `max` must be initialized
331     appropriately. The block must have been allocated with this
332     freelist, and no dynamic changing of `min` or `max` is allowed to
333     occur between allocation and deallocation.
334     */
335     bool deallocate(void[] block)
336     {
337         if (freeListEligible(block.length))
338         {
339             if (min == 0)
340             {
341                 // In this case a null pointer might have made it this far.
342                 if (block is null) return true;
343             }
344             auto t = root;
345             root = cast(Node*) block.ptr;
346             root.next = t;
347             return true;
348         }
349         static if (hasMember!(ParentAllocator, "deallocate"))
350             return parent.deallocate(block);
351         else
352             return false;
353     }
354 
355     /**
356     Defined only if `ParentAllocator` defines `deallocateAll`. If so,
357     forwards to it and resets the freelist.
358     */
359     static if (hasMember!(ParentAllocator, "deallocateAll"))
360     bool deallocateAll()
361     {
362         root = null;
363         return parent.deallocateAll();
364     }
365 
366     /**
367     Nonstandard function that minimizes the memory usage of the freelist by
368     freeing each element in turn. Defined only if `ParentAllocator` defines
369     `deallocate`. $(D FreeList!(0, unbounded)) does not have this function.
370     */
371     static if (hasMember!(ParentAllocator, "deallocate") && !unchecked)
372     void minimize()
373     {
374         while (root)
375         {
376             auto nuke = blockFor(root);
377             root = root.next;
378             parent.deallocate(nuke);
379         }
380     }
381 
382     /**
383     If `ParentAllocator` defines `deallocate`, the list frees all nodes
384     on destruction. $(D FreeList!(0, unbounded)) does not deallocate the memory
385     on destruction.
386     */
387     static if (!is(ParentAllocator == NullAllocator) &&
388         hasMember!(ParentAllocator, "deallocate") && !unchecked)
389     ~this()
390     {
391         minimize();
392     }
393 }
394 
395 
396 /**
397 Free list built on top of exactly one contiguous block of memory. The block is
398 assumed to have been allocated with `ParentAllocator`, and is released in
399 `ContiguousFreeList`'s destructor (unless `ParentAllocator` is $(D
400 NullAllocator)).
401 
402 `ContiguousFreeList` has most advantages of `FreeList` but fewer
403 disadvantages. It has better cache locality because items are closer to one
404 another. It imposes less fragmentation on its parent allocator.
405 
406 The disadvantages of `ContiguousFreeList` over `FreeList` are its pay
407 upfront model (as opposed to `FreeList`'s pay-as-you-go approach), and a
408 hard limit on the number of nodes in the list. Thus, a large number of long-
409 lived objects may occupy the entire block, making it unavailable for serving
410 allocations from the free list. However, an absolute cap on the free list size
411 may be beneficial.
412 
413 The options $(D minSize == unbounded) and $(D maxSize == unbounded) are not
414 available for `ContiguousFreeList`.
415 */
416 struct ContiguousFreeList(ParentAllocator,
417      size_t minSize, size_t maxSize = minSize)
418 {
419     import std.experimental.allocator.building_blocks.null_allocator
420         : NullAllocator;
421     import std.experimental.allocator.building_blocks.stats_collector
422         : StatsCollector, Options;
423     import std.traits : hasMember;
424     import std.typecons : Ternary;
425 
426     alias Impl = FreeList!(NullAllocator, minSize, maxSize);
427     enum unchecked = minSize == 0 && maxSize == unbounded;
428     alias Node = Impl.Node;
429 
430     alias SParent = StatsCollector!(ParentAllocator, Options.bytesUsed);
431 
432     // state
433     /**
434     The parent allocator. Depending on whether `ParentAllocator` holds state
435     or not, this is a member variable or an alias for
436     `ParentAllocator.instance`.
437     */
438     SParent parent;
439     FreeList!(NullAllocator, minSize, maxSize) fl;
440     void[] support;
441     size_t allocated;
442 
443     /// Alignment offered.
444     enum uint alignment = (void*).alignof;
445 
446     private void initialize(ubyte[] buffer, size_t itemSize = fl.max)
447     {
448         assert(itemSize != unbounded && itemSize != chooseAtRuntime);
449         assert(buffer.ptr.alignedAt(alignment));
450         immutable available = buffer.length / itemSize;
451         if (available == 0) return;
452         support = buffer;
453         fl.root = cast(Node*) buffer.ptr;
454         auto past = cast(Node*) (buffer.ptr + available * itemSize);
455         for (auto n = fl.root; ; )
456         {
457             auto next = cast(Node*) (cast(ubyte*) n + itemSize);
458             if (next == past)
459             {
460                 n.next = null;
461                 break;
462             }
463             assert(next < past);
464             assert(n < next);
465             n.next = next;
466             n = next;
467         }
468     }
469 
470     /**
471     Constructors setting up the memory structured as a free list.
472 
473     Params:
474     buffer = Buffer to structure as a free list. If `ParentAllocator` is not
475     `NullAllocator`, the buffer is assumed to be allocated by `parent`
476     and will be freed in the destructor.
477     parent = Parent allocator. For construction from stateless allocators, use
478     their `instance` static member.
479     bytes = Bytes (not items) to be allocated for the free list. Memory will be
480     allocated during construction and deallocated in the destructor.
481     max = Maximum size eligible for freelisting. Construction with this
482     parameter is defined only if $(D maxSize == chooseAtRuntime) or $(D maxSize
483     == unbounded).
484     min = Minimum size eligible for freelisting. Construction with this
485     parameter is defined only if $(D minSize == chooseAtRuntime). If this
486     condition is met and no `min` parameter is present, `min` is
487     initialized with `max`.
488     */
489     static if (!stateSize!ParentAllocator)
490     this(ubyte[] buffer)
491     {
492         initialize(buffer);
493     }
494 
495     /// ditto
496     static if (stateSize!ParentAllocator)
497     this(ParentAllocator parent, ubyte[] buffer)
498     {
499         initialize(buffer);
500         this.parent = SParent(parent);
501     }
502 
503     /// ditto
504     static if (!stateSize!ParentAllocator)
505     this(size_t bytes)
506     {
507         initialize(cast(ubyte[])(ParentAllocator.instance.allocate(bytes)));
508     }
509 
510     /// ditto
511     static if (stateSize!ParentAllocator)
512     this(ParentAllocator parent, size_t bytes)
513     {
514         initialize(cast(ubyte[])(parent.allocate(bytes)));
515         this.parent = SParent(parent);
516     }
517 
518     /// ditto
519     static if (!stateSize!ParentAllocator
520         && (maxSize == chooseAtRuntime || maxSize == unbounded))
521     this(size_t bytes, size_t max)
522     {
523         static if (maxSize == chooseAtRuntime) fl.max = max;
524         static if (minSize == chooseAtRuntime) fl.min = max;
525         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
526     }
527 
528     /// ditto
529     static if (stateSize!ParentAllocator
530         && (maxSize == chooseAtRuntime || maxSize == unbounded))
531     this(ParentAllocator parent, size_t bytes, size_t max)
532     {
533         static if (maxSize == chooseAtRuntime) fl.max = max;
534         static if (minSize == chooseAtRuntime) fl.min = max;
535         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
536         this.parent = SParent(parent);
537     }
538 
539     /// ditto
540     static if (!stateSize!ParentAllocator
541         && (maxSize == chooseAtRuntime || maxSize == unbounded)
542         && minSize == chooseAtRuntime)
543     this(size_t bytes, size_t min, size_t max)
544     {
545         static if (maxSize == chooseAtRuntime) fl.max = max;
546         fl.min = min;
547         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
548         static if (stateSize!ParentAllocator)
549             this.parent = SParent(parent);
550     }
551 
552     /// ditto
553     static if (stateSize!ParentAllocator
554         && (maxSize == chooseAtRuntime || maxSize == unbounded)
555         && minSize == chooseAtRuntime)
556     this(ParentAllocator parent, size_t bytes, size_t min, size_t max)
557     {
558         static if (maxSize == chooseAtRuntime) fl.max = max;
559         fl.min = min;
560         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
561         static if (stateSize!ParentAllocator)
562             this.parent = SParent(parent);
563     }
564 
565     /**
566     If `n` is eligible for freelisting, returns `max`. Otherwise, returns
567     `parent.goodAllocSize(n)`.
568 
569     Precondition:
570     If set at runtime, `min` and/or `max` must be initialized
571     appropriately.
572 
573     Postcondition:
574     $(D result >= bytes)
575     */
576     size_t goodAllocSize(size_t n)
577     {
578         if (fl.freeListEligible(n)) return fl.max;
579         return parent.goodAllocSize(n);
580     }
581 
582     /**
583     Allocate `n` bytes of memory. If `n` is eligible for freelist and the
584     freelist is not empty, pops the memory off the free list. In all other
585     cases, uses the parent allocator.
586     */
587     void[] allocate(size_t n)
588     {
589         auto result = fl.allocate(n);
590         if (result)
591         {
592             // Only case we care about: eligible sizes allocated from us
593             ++allocated;
594             return result;
595         }
596         // All others, allocate from parent
597         return parent.allocate(n);
598     }
599 
600     /**
601     Defined if `ParentAllocator` defines it. Checks whether the block
602     belongs to this allocator.
603     */
604     static if (hasMember!(SParent, "owns") || unchecked)
605     // Ternary owns(const void[] b) const ?
606     Ternary owns(void[] b)
607     {
608         if ((() @trusted => support && b
609                             && (&support[0] <= &b[0])
610                             && (&b[0] < &support[0] + support.length)
611             )())
612             return Ternary.yes;
613         static if (unchecked)
614             return Ternary.no;
615         else
616             return parent.owns(b);
617     }
618 
619     /**
620     Deallocates `b`. If it's of eligible size, it's put on the free list.
621     Otherwise, it's returned to `parent`.
622 
623     Precondition: `b` has been allocated with this allocator, or is $(D
624     null).
625     */
626     bool deallocate(void[] b)
627     {
628         if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length)
629         {
630             // we own this guy
631             assert(fl.freeListEligible(b.length));
632             assert(allocated);
633             --allocated;
634             // Put manually in the freelist
635             auto t = fl.root;
636             fl.root = cast(Node*) b.ptr;
637             fl.root.next = t;
638             return true;
639         }
640         return parent.deallocate(b);
641     }
642 
643     /**
644     Deallocates everything from the parent.
645     */
646     static if (hasMember!(ParentAllocator, "deallocateAll")
647         && stateSize!ParentAllocator)
648     bool deallocateAll()
649     {
650         bool result = fl.deallocateAll && parent.deallocateAll;
651         allocated = 0;
652         return result;
653     }
654 
655     /**    
656     Deallocates everything(support) using the parent.
657     */
658     static if (!stateSize!ParentAllocator)
659     bool deallocateAll()
660     {
661         bool result = parent.deallocate(support);
662         allocated = 0;
663         return result;
664     }
665     /**
666     Returns `Ternary.yes` if no memory is currently allocated with this
667     allocator, `Ternary.no` otherwise. This method never returns
668     `Ternary.unknown`.
669     */
670     Ternary empty()
671     {
672         return Ternary(allocated == 0 && parent.bytesUsed == 0);
673     }
674 }