1 ///
2 module stdx.allocator.building_blocks.free_list;
3 
4 import stdx.allocator.common;
5 import std.typecons : Flag, Yes, No;
6 
7 /**
8 
9 $(HTTP en.wikipedia.org/wiki/Free_list, Free list allocator), stackable on top of
10 another allocator. Allocation requests between $(D min) and $(D max) bytes are
11 rounded up to $(D max) and served from a singly-linked list of buffers
12 deallocated in the past. All other allocations are directed to $(D
13 ParentAllocator). Due to the simplicity of free list management, allocations
14 from the free list are fast.
15 
16 One instantiation is of particular interest: $(D FreeList!(0, unbounded)) puts
17 every deallocation in the freelist, and subsequently serves any allocation from
18 the freelist (if not empty). There is no checking of size matching, which would
19 be incorrect for a freestanding allocator but is both correct and fast when an
20 owning allocator on top of the free list allocator (such as $(D Segregator)) is
21 already in charge of handling size checking.
22 
23 The following methods are defined if $(D ParentAllocator) defines them, and
24 forward to it: $(D expand), $(D owns), $(D reallocate).
25 
26 */
27 struct FreeList(ParentAllocator,
28     size_t minSize, size_t maxSize = minSize,
29     Flag!"adaptive" adaptive = No.adaptive)
30 {
31     import stdx.allocator.internal : Ternary;
32 
33     static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
34     static assert(maxSize >= (void*).sizeof,
35         "Maximum size must accommodate a pointer.");
36 
37     private enum unchecked = minSize == 0 && maxSize == unbounded;
38 
39     private enum hasTolerance = !unchecked && (minSize != maxSize
40         || maxSize == chooseAtRuntime);
41 
42     static if (minSize == chooseAtRuntime)
43     {
44         /**
45         Returns the smallest allocation size eligible for allocation from the
46         freelist. (If $(D minSize != chooseAtRuntime), this is simply an alias
47         for $(D minSize).)
48         */
49         @property size_t min() const
50         {
51             assert(_min != chooseAtRuntime);
52             return _min;
53         }
54         /**
55         If $(D FreeList) has been instantiated with $(D minSize ==
56         chooseAtRuntime), then the $(D min) property is writable. Setting it
57         must precede any allocation.
58 
59         Params:
60         low = new value for $(D min)
61 
62         Precondition: $(D low <= max), or $(D maxSize == chooseAtRuntime) and
63         $(D max) has not yet been initialized. Also, no allocation has been
64         yet done with this allocator.
65 
66         Postcondition: $(D min == low)
67         */
68         @property void min(size_t low)
69         {
70             assert(low <= max || max == chooseAtRuntime);
71             minimize;
72             _min = low;
73         }
74     }
75     else
76     {
77         alias min = minSize;
78     }
79 
80     static if (maxSize == chooseAtRuntime)
81     {
82         /**
83         Returns the largest allocation size eligible for allocation from the
84         freelist. (If $(D maxSize != chooseAtRuntime), this is simply an alias
85         for $(D maxSize).) All allocation requests for sizes greater than or
86         equal to $(D min) and less than or equal to $(D max) are rounded to $(D
87         max) and forwarded to the parent allocator. When the block fitting the
88         same constraint gets deallocated, it is put in the freelist with the
89         allocated size assumed to be $(D max).
90         */
91         @property size_t max() const { return _max; }
92 
93         /**
94         If $(D FreeList) has been instantiated with $(D maxSize ==
95         chooseAtRuntime), then the $(D max) property is writable. Setting it
96         must precede any allocation.
97 
98         Params:
99         high = new value for $(D max)
100 
101         Precondition: $(D high >= min), or $(D minSize == chooseAtRuntime) and
102         $(D min) has not yet been initialized. Also $(D high >= (void*).sizeof). Also, no allocation has been yet done with this allocator.
103 
104         Postcondition: $(D max == high)
105         */
106         @property void max(size_t high)
107         {
108             assert((high >= min || min == chooseAtRuntime)
109                 && high >= (void*).sizeof);
110             minimize;
111             _max = high;
112         }
113 
114         @system unittest
115         {
116             import stdx.allocator.common : chooseAtRuntime;
117             import stdx.allocator.mallocator : Mallocator;
118 
119             FreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
120             a.min = 64;
121             a.max = 128;
122             assert(a.min == 64);
123             assert(a.max == 128);
124         }
125     }
126     else
127     {
128         alias max = maxSize;
129     }
130 
131     private bool tooSmall(size_t n) const
132     {
133         static if (minSize == 0) return false;
134         else return n < min;
135     }
136 
137     private bool tooLarge(size_t n) const
138     {
139         static if (maxSize == unbounded) return false;
140         else return n > max;
141     }
142 
143     private bool freeListEligible(size_t n) const
144     {
145         static if (unchecked)
146         {
147             return true;
148         }
149         else
150         {
151             static if (minSize == 0)
152             {
153                 if (!n) return false;
154             }
155             static if (minSize == maxSize && minSize != chooseAtRuntime)
156                 return n == maxSize;
157             else
158                 return !tooSmall(n) && !tooLarge(n);
159         }
160     }
161 
162     static if (!unchecked)
163     private void[] blockFor(Node* p)
164     {
165         assert(p);
166         return (cast(void*) p)[0 .. max];
167     }
168 
169     // statistics
170     static if (adaptive == Yes.adaptive)
171     {
172         private enum double windowLength = 1000.0;
173         private enum double tooFewMisses = 0.01;
174         private double probMiss = 1.0; // start with a high miss probability
175         private uint accumSamples, accumMisses;
176 
177         void updateStats()
178         {
179             assert(accumSamples >= accumMisses);
180             /*
181             Given that for the past windowLength samples we saw misses with
182             estimated probability probMiss, and assuming the new sample wasMiss or
183             not, what's the new estimated probMiss?
184             */
185             probMiss = (probMiss * windowLength + accumMisses)
186                 / (windowLength + accumSamples);
187             assert(probMiss <= 1.0);
188             accumSamples = 0;
189             accumMisses = 0;
190             // If probability to miss is under x%, yank one off the freelist
191             static if (!unchecked)
192             {
193                 if (probMiss < tooFewMisses && _root)
194                 {
195                     auto b = blockFor(_root);
196                     _root = _root.next;
197                     parent.deallocate(b);
198                 }
199             }
200         }
201     }
202 
203     private struct Node { Node* next; }
204     static assert(ParentAllocator.alignment >= Node.alignof);
205 
206     // state
207     /**
208     The parent allocator. Depending on whether $(D ParentAllocator) holds state
209     or not, this is a member variable or an alias for
210     `ParentAllocator.instance`.
211     */
212     static if (stateSize!ParentAllocator) ParentAllocator parent;
213     else alias parent = ParentAllocator.instance;
214     private Node* root;
215     static if (minSize == chooseAtRuntime) private size_t _min = chooseAtRuntime;
216     static if (maxSize == chooseAtRuntime) private size_t _max = chooseAtRuntime;
217 
218     /**
219     Alignment offered.
220     */
221     alias alignment = ParentAllocator.alignment;
222 
223     /**
224     If $(D maxSize == unbounded), returns  $(D parent.goodAllocSize(bytes)).
225     Otherwise, returns $(D max) for sizes in the interval $(D [min, max]), and
226     $(D parent.goodAllocSize(bytes)) otherwise.
227 
228     Precondition:
229     If set at runtime, $(D min) and/or $(D max) must be initialized
230     appropriately.
231 
232     Postcondition:
233     $(D result >= bytes)
234     */
235     size_t goodAllocSize(size_t bytes)
236     {
237         assert(minSize != chooseAtRuntime && maxSize != chooseAtRuntime);
238         static if (maxSize != unbounded)
239         {
240             if (freeListEligible(bytes))
241             {
242                 assert(parent.goodAllocSize(max) == max, "Wrongly configured freelist maximum value");
243                 return max;
244             }
245         }
246         return parent.goodAllocSize(bytes);
247     }
248 
249     private void[] allocateEligible(size_t bytes)
250     {
251         assert(bytes);
252         if (root)
253         {
254             // faster
255             auto result = (cast(ubyte*) root)[0 .. bytes];
256             root = root.next;
257             return result;
258         }
259         // slower
260         static if (hasTolerance)
261         {
262             immutable toAllocate = max;
263         }
264         else
265         {
266             alias toAllocate = bytes;
267         }
268         assert(toAllocate == max || max == unbounded);
269         auto result = parent.allocate(toAllocate);
270         static if (hasTolerance)
271         {
272             if (result) result = result.ptr[0 .. bytes];
273         }
274         static if (adaptive == Yes.adaptive)
275         {
276             ++accumMisses;
277             updateStats;
278         }
279         return result;
280     }
281 
282     /**
283     Allocates memory either off of the free list or from the parent allocator.
284     If $(D n) is within $(D [min, max]) or if the free list is unchecked
285     ($(D minSize == 0 && maxSize == size_t.max)), then the free list is
286     consulted first. If not empty (hit), the block at the front of the free
287     list is removed from the list and returned. Otherwise (miss), a new block
288     of $(D max) bytes is allocated, truncated to $(D n) bytes, and returned.
289 
290     Params:
291     n = number of bytes to allocate
292 
293     Returns:
294     The allocated block, or $(D null).
295 
296     Precondition:
297     If set at runtime, $(D min) and/or $(D max) must be initialized
298     appropriately.
299 
300     Postcondition: $(D result.length == bytes || result is null)
301     */
302     void[] allocate(size_t n)
303     {
304         static if (adaptive == Yes.adaptive) ++accumSamples;
305         assert(n < size_t.max / 2);
306         // fast path
307         if (freeListEligible(n))
308         {
309             return allocateEligible(n);
310         }
311         // slower
312         static if (adaptive == Yes.adaptive)
313         {
314             updateStats;
315         }
316         return parent.allocate(n);
317     }
318 
319     // Forwarding methods
320     mixin(forwardToMember("parent",
321         "expand", "owns", "reallocate"));
322 
323     /**
324     If $(D block.length) is within $(D [min, max]) or if the free list is
325     unchecked ($(D minSize == 0 && maxSize == size_t.max)), then inserts the
326     block at the front of the free list. For all others, forwards to $(D
327     parent.deallocate) if $(D Parent.deallocate) is defined.
328 
329     Params:
330     block = Block to deallocate.
331 
332     Precondition:
333     If set at runtime, $(D min) and/or $(D max) must be initialized
334     appropriately. The block must have been allocated with this
335     freelist, and no dynamic changing of $(D min) or $(D max) is allowed to
336     occur between allocation and deallocation.
337     */
338     bool deallocate(void[] block)
339     {
340         if (freeListEligible(block.length))
341         {
342             if (min == 0)
343             {
344                 // In this case a null pointer might have made it this far.
345                 if (block is null) return true;
346             }
347             auto t = root;
348             root = cast(Node*) block.ptr;
349             root.next = t;
350             return true;
351         }
352         static if (__traits(hasMember, ParentAllocator, "deallocate"))
353             return parent.deallocate(block);
354         else
355             return false;
356     }
357 
358     /**
359     Defined only if $(D ParentAllocator) defines $(D deallocateAll). If so,
360     forwards to it and resets the freelist.
361     */
362     static if (__traits(hasMember, ParentAllocator, "deallocateAll"))
363     bool deallocateAll()
364     {
365         root = null;
366         return parent.deallocateAll();
367     }
368 
369     /**
370     Nonstandard function that minimizes the memory usage of the freelist by
371     freeing each element in turn. Defined only if $(D ParentAllocator) defines
372     $(D deallocate).
373     */
374     static if (__traits(hasMember, ParentAllocator, "deallocate") && !unchecked)
375     void minimize()
376     {
377         while (root)
378         {
379             auto nuke = blockFor(root);
380             root = root.next;
381             parent.deallocate(nuke);
382         }
383     }
384 }
385 
386 @system unittest
387 {
388     import stdx.allocator.gc_allocator : GCAllocator;
389     FreeList!(GCAllocator, 0, 8) fl;
390     assert(fl.root is null);
391     auto b1 = fl.allocate(7);
392     fl.allocate(8);
393     assert(fl.root is null);
394     fl.deallocate(b1);
395     assert(fl.root !is null);
396     fl.allocate(8);
397     assert(fl.root is null);
398 }
399 
400 /**
401 Free list built on top of exactly one contiguous block of memory. The block is
402 assumed to have been allocated with $(D ParentAllocator), and is released in
403 $(D ContiguousFreeList)'s destructor (unless $(D ParentAllocator) is $(D
404 NullAllocator)).
405 
406 $(D ContiguousFreeList) has most advantages of $(D FreeList) but fewer
407 disadvantages. It has better cache locality because items are closer to one
408 another. It imposes less fragmentation on its parent allocator.
409 
410 The disadvantages of $(D ContiguousFreeList) over $(D FreeList) are its pay
411 upfront model (as opposed to $(D FreeList)'s pay-as-you-go approach), and a
412 hard limit on the number of nodes in the list. Thus, a large number of long-
413 lived objects may occupy the entire block, making it unavailable for serving
414 allocations from the free list. However, an absolute cap on the free list size
415 may be beneficial.
416 
417 The options $(D minSize == unbounded) and $(D maxSize == unbounded) are not
418 available for $(D ContiguousFreeList).
419 */
420 struct ContiguousFreeList(ParentAllocator,
421      size_t minSize, size_t maxSize = minSize)
422 {
423     import stdx.allocator.building_blocks.null_allocator
424         : NullAllocator;
425     import stdx.allocator.building_blocks.stats_collector
426         : StatsCollector, Options;
427     import stdx.allocator.internal : Ternary;
428 
429     alias Impl = FreeList!(NullAllocator, minSize, maxSize);
430     enum unchecked = minSize == 0 && maxSize == unbounded;
431     alias Node = Impl.Node;
432 
433     alias SParent = StatsCollector!(ParentAllocator, Options.bytesUsed);
434 
435     // state
436     /**
437     The parent allocator. Depending on whether $(D ParentAllocator) holds state
438     or not, this is a member variable or an alias for
439     `ParentAllocator.instance`.
440     */
441     SParent parent;
442     FreeList!(NullAllocator, minSize, maxSize) fl;
443     void[] support;
444     size_t allocated;
445 
446     /// Alignment offered.
447     enum uint alignment = (void*).alignof;
448 
449     private void initialize(ubyte[] buffer, size_t itemSize = fl.max)
450     {
451         assert(itemSize != unbounded && itemSize != chooseAtRuntime);
452         assert(buffer.ptr.alignedAt(alignment));
453         immutable available = buffer.length / itemSize;
454         if (available == 0) return;
455         support = buffer;
456         fl.root = cast(Node*) buffer.ptr;
457         auto past = cast(Node*) (buffer.ptr + available * itemSize);
458         for (auto n = fl.root; ; )
459         {
460             auto next = cast(Node*) (cast(ubyte*) n + itemSize);
461             if (next == past)
462             {
463                 n.next = null;
464                 break;
465             }
466             assert(next < past);
467             assert(n < next);
468             n.next = next;
469             n = next;
470         }
471     }
472 
473     /**
474     Constructors setting up the memory structured as a free list.
475 
476     Params:
477     buffer = Buffer to structure as a free list. If $(D ParentAllocator) is not
478     $(D NullAllocator), the buffer is assumed to be allocated by $(D parent)
479     and will be freed in the destructor.
480     parent = Parent allocator. For construction from stateless allocators, use
481     their `instance` static member.
482     bytes = Bytes (not items) to be allocated for the free list. Memory will be
483     allocated during construction and deallocated in the destructor.
484     max = Maximum size eligible for freelisting. Construction with this
485     parameter is defined only if $(D maxSize == chooseAtRuntime) or $(D maxSize
486     == unbounded).
487     min = Minimum size eligible for freelisting. Construction with this
488     parameter is defined only if $(D minSize == chooseAtRuntime). If this
489     condition is met and no $(D min) parameter is present, $(D min) is
490     initialized with $(D max).
491     */
492     static if (!stateSize!ParentAllocator)
493     this(ubyte[] buffer)
494     {
495         initialize(buffer);
496     }
497 
498     /// ditto
499     static if (stateSize!ParentAllocator)
500     this(ParentAllocator parent, ubyte[] buffer)
501     {
502         initialize(buffer);
503         this.parent = SParent(parent);
504     }
505 
506     /// ditto
507     static if (!stateSize!ParentAllocator)
508     this(size_t bytes)
509     {
510         initialize(cast(ubyte[])(ParentAllocator.instance.allocate(bytes)));
511     }
512 
513     /// ditto
514     static if (stateSize!ParentAllocator)
515     this(ParentAllocator parent, size_t bytes)
516     {
517         initialize(cast(ubyte[])(parent.allocate(bytes)));
518         this.parent = SParent(parent);
519     }
520 
521     /// ditto
522     static if (!stateSize!ParentAllocator
523         && (maxSize == chooseAtRuntime || maxSize == unbounded))
524     this(size_t bytes, size_t max)
525     {
526         static if (maxSize == chooseAtRuntime) fl.max = max;
527         static if (minSize == chooseAtRuntime) fl.min = max;
528         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
529     }
530 
531     /// ditto
532     static if (stateSize!ParentAllocator
533         && (maxSize == chooseAtRuntime || maxSize == unbounded))
534     this(ParentAllocator parent, size_t bytes, size_t max)
535     {
536         static if (maxSize == chooseAtRuntime) fl.max = max;
537         static if (minSize == chooseAtRuntime) fl.min = max;
538         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
539         this.parent = SParent(parent);
540     }
541 
542     /// ditto
543     static if (!stateSize!ParentAllocator
544         && (maxSize == chooseAtRuntime || maxSize == unbounded)
545         && minSize == chooseAtRuntime)
546     this(size_t bytes, size_t min, size_t max)
547     {
548         static if (maxSize == chooseAtRuntime) fl.max = max;
549         fl.min = min;
550         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
551         static if (stateSize!ParentAllocator)
552             this.parent = SParent(parent);
553     }
554 
555     /// ditto
556     static if (stateSize!ParentAllocator
557         && (maxSize == chooseAtRuntime || maxSize == unbounded)
558         && minSize == chooseAtRuntime)
559     this(ParentAllocator parent, size_t bytes, size_t min, size_t max)
560     {
561         static if (maxSize == chooseAtRuntime) fl.max = max;
562         fl.min = min;
563         initialize(cast(ubyte[])(parent.allocate(bytes)), max);
564         static if (stateSize!ParentAllocator)
565             this.parent = SParent(parent);
566     }
567 
568     /**
569     If $(D n) is eligible for freelisting, returns $(D max). Otherwise, returns
570     $(D parent.goodAllocSize(n)).
571 
572     Precondition:
573     If set at runtime, $(D min) and/or $(D max) must be initialized
574     appropriately.
575 
576     Postcondition:
577     $(D result >= bytes)
578     */
579     size_t goodAllocSize(size_t n)
580     {
581         if (fl.freeListEligible(n)) return fl.max;
582         return parent.goodAllocSize(n);
583     }
584 
585     /**
586     Allocate $(D n) bytes of memory. If $(D n) is eligible for freelist and the
587     freelist is not empty, pops the memory off the free list. In all other
588     cases, uses the parent allocator.
589     */
590     void[] allocate(size_t n)
591     {
592         auto result = fl.allocate(n);
593         if (result)
594         {
595             // Only case we care about: eligible sizes allocated from us
596             ++allocated;
597             return result;
598         }
599         // All others, allocate from parent
600         return parent.allocate(n);
601     }
602 
603     /**
604     Defined if `ParentAllocator` defines it. Checks whether the block
605     belongs to this allocator.
606     */
607     static if (__traits(hasMember, SParent, "owns") || unchecked)
608     Ternary owns(void[] b)
609     {
610         if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length)
611             return Ternary.yes;
612         static if (unchecked)
613             return Ternary.no;
614         else
615             return parent.owns(b);
616     }
617 
618     /**
619     Deallocates $(D b). If it's of eligible size, it's put on the free list.
620     Otherwise, it's returned to $(D parent).
621 
622     Precondition: $(D b) has been allocated with this allocator, or is $(D
623     null).
624     */
625     bool deallocate(void[] b)
626     {
627         if (support.ptr <= b.ptr && b.ptr < support.ptr + support.length)
628         {
629             // we own this guy
630             assert(fl.freeListEligible(b.length));
631             assert(allocated);
632             --allocated;
633             // Put manually in the freelist
634             auto t = fl.root;
635             fl.root = cast(Node*) b.ptr;
636             fl.root.next = t;
637             return true;
638         }
639         return parent.deallocate(b);
640     }
641 
642     /**
643     Deallocates everything from the parent.
644     */
645     static if (__traits(hasMember, ParentAllocator, "deallocateAll")
646         && stateSize!ParentAllocator)
647     bool deallocateAll()
648     {
649         bool result = fl.deallocateAll && parent.deallocateAll;
650         allocated = 0;
651         return result;
652     }
653 
654     /**
655     Returns `Ternary.yes` if no memory is currently allocated with this
656     allocator, `Ternary.no` otherwise. This method never returns
657     `Ternary.unknown`.
658     */
659     Ternary empty()
660     {
661         return Ternary(allocated == 0 && parent.bytesUsed == 0);
662     }
663 }
664 
665 ///
666 @nogc @safe unittest
667 {
668     import stdx.allocator.building_blocks.allocator_list
669         : AllocatorList;
670     import stdx.allocator.mallocator : Mallocator;
671 
672     import stdx.allocator.common : unbounded;
673 
674     alias ScalableFreeList = AllocatorList!((n) =>
675         ContiguousFreeList!(Mallocator, 0, unbounded)(4096)
676     );
677 }
678 
679 @system unittest
680 {
681     import stdx.allocator.building_blocks.null_allocator
682         : NullAllocator;
683     import stdx.allocator.internal : Ternary;
684     alias A = ContiguousFreeList!(NullAllocator, 0, 64);
685     auto a = A(new ubyte[1024]);
686 
687     assert(a.empty == Ternary.yes);
688 
689     assert(a.goodAllocSize(15) == 64);
690     assert(a.goodAllocSize(65) == NullAllocator.instance.goodAllocSize(65));
691 
692     auto b = a.allocate(100);
693     assert(a.empty == Ternary.yes);
694     assert(b.length == 0);
695     a.deallocate(b);
696     b = a.allocate(64);
697     assert(a.empty == Ternary.no);
698     assert(b.length == 64);
699     assert(a.owns(b) == Ternary.yes);
700     assert(a.owns(null) == Ternary.no);
701     a.deallocate(b);
702 }
703 
704 @system unittest
705 {
706     import stdx.allocator.building_blocks.region : Region;
707     import stdx.allocator.gc_allocator : GCAllocator;
708     import stdx.allocator.internal : Ternary;
709     alias A = ContiguousFreeList!(Region!GCAllocator, 0, 64);
710     auto a = A(Region!GCAllocator(1024 * 4), 1024);
711 
712     assert(a.empty == Ternary.yes);
713 
714     assert(a.goodAllocSize(15) == 64);
715     assert(a.goodAllocSize(65) == a.parent.goodAllocSize(65));
716 
717     auto b = a.allocate(100);
718     assert(a.empty == Ternary.no);
719     assert(a.allocated == 0);
720     assert(b.length == 100);
721     a.deallocate(b);
722     assert(a.empty == Ternary.yes);
723     b = a.allocate(64);
724     assert(a.empty == Ternary.no);
725     assert(b.length == 64);
726     assert(a.owns(b) == Ternary.yes);
727     assert(a.owns(null) == Ternary.no);
728     a.deallocate(b);
729 }
730 
731 @system unittest
732 {
733     import stdx.allocator.gc_allocator : GCAllocator;
734     alias A = ContiguousFreeList!(GCAllocator, 64, 64);
735     auto a = A(1024);
736     const b = a.allocate(100);
737     assert(b.length == 100);
738 }
739 
740 // single @nogc instance for all templates.
741 private static immutable excMin = new Exception("SharedFreeList.min must be initialized exactly once.");
742 private static immutable excMax = new Exception("SharedFreeList.max must be initialized exactly once.");
743 private static immutable excBounds = new Exception("Wrong shared free list bounds.");
744 private static immutable excX = new Exception("x should be positive.");
745 
746 /**
747 FreeList shared across threads. Allocation and deallocation are lock-free. The
748 parameters have the same semantics as for $(D FreeList).
749 
750 $(D expand) is defined to forward to $(D ParentAllocator.expand)
751 (it must be also $(D shared)).
752 */
753 struct SharedFreeList(ParentAllocator,
754     size_t minSize, size_t maxSize = minSize, size_t approxMaxNodes = unbounded)
755 {
756     static assert(approxMaxNodes, "approxMaxNodes must not be null.");
757     static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
758     static assert(maxSize >= (void*).sizeof,
759         "Maximum size must accommodate a pointer.");
760 
761     import core.atomic : atomicOp, cas;
762     import core.internal.spinlock : SpinLock;
763 
764     private enum unchecked = minSize == 0 && maxSize == unbounded;
765 
766     static if (minSize != chooseAtRuntime)
767     {
768         alias min = minSize;
769     }
770     else
771     {
772         private shared size_t _min = chooseAtRuntime;
773         @property size_t min() const shared
774         {
775             assert(_min != chooseAtRuntime);
776             return _min;
777         }
778         @property void min(size_t x) shared
779         {
780             if (!(x <= max))
781                 throw excBounds;
782             if (!(cas(&_min, chooseAtRuntime, x)))
783                 throw excMin;
784         }
785         static if (maxSize == chooseAtRuntime)
786         {
787             // Both bounds can be set, provide one function for setting both in
788             // one shot.
789             void setBounds(size_t low, size_t high) shared
790             {
791                 if (!(low <= high && high >= (void*).sizeof))
792                     throw excBounds;
793                 if (!(cas(&_min, chooseAtRuntime, low)))
794                     throw excMin;
795                 if (!(cas(&_max, chooseAtRuntime, high)))
796                     throw excMax;
797             }
798         }
799     }
800 
801     private bool tooSmall(size_t n) const shared
802     {
803         static if (minSize == 0) return false;
804         else static if (minSize == chooseAtRuntime) return n < _min;
805         else return n < minSize;
806     }
807 
808     static if (maxSize != chooseAtRuntime)
809     {
810         alias max = maxSize;
811     }
812     else
813     {
814         private shared size_t _max = chooseAtRuntime;
815         @property size_t max() const shared { return _max; }
816         @property void max(size_t x) shared
817         {
818             if (!(x >= min && x >= (void*).sizeof))
819                 throw excBounds;
820             if (!(cas(&_max, chooseAtRuntime, x)))
821                 throw excMax;
822         }
823     }
824 
825     private bool tooLarge(size_t n) const shared
826     {
827         static if (maxSize == unbounded) return false;
828         else static if (maxSize == chooseAtRuntime) return n > _max;
829         else return n > maxSize;
830     }
831 
832     private bool freeListEligible(size_t n) const shared
833     {
834         static if (minSize == maxSize && minSize != chooseAtRuntime)
835             return n == maxSize;
836         else return !tooSmall(n) && !tooLarge(n);
837     }
838 
839     static if (approxMaxNodes != chooseAtRuntime)
840     {
841         alias approxMaxLength = approxMaxNodes;
842     }
843     else
844     {
845         private shared size_t _approxMaxLength = chooseAtRuntime;
846         @property size_t approxMaxLength() const shared { return _approxMaxLength; }
847 
848         @property void approxMaxLength(size_t x) shared
849         {
850             if (x == 0) throw excX;
851             _approxMaxLength = x;
852         }
853     }
854 
855     static if (approxMaxNodes != unbounded)
856     {
857         private shared size_t nodes;
858         private void incNodes() shared
859         {
860             atomicOp!("+=")(nodes, 1);
861         }
862         private void decNodes() shared
863         {
864             assert(nodes);
865             atomicOp!("-=")(nodes, 1);
866         }
867         private void resetNodes() shared
868         {
869             nodes = 0;
870         }
871         private bool nodesFull() shared
872         {
873             return nodes >= approxMaxLength;
874         }
875     }
876     else
877     {
878         private static void incNodes() { }
879         private static void decNodes() { }
880         private static void resetNodes() { }
881         private enum bool nodesFull = false;
882     }
883 
884     version (StdDdoc)
885     {
886         /**
887         Properties for getting (and possibly setting) the bounds. Setting bounds
888         is allowed only once , and before any allocation takes place. Otherwise,
889         the primitives have the same semantics as those of $(D FreeList).
890         */
891         @property size_t min();
892         /// Ditto
893         @property void min(size_t newMinSize);
894         /// Ditto
895         @property size_t max();
896         /// Ditto
897         @property void max(size_t newMaxSize);
898         /// Ditto
899         void setBounds(size_t newMin, size_t newMax);
900 
901         /**
902         Properties for getting (and possibly setting) the approximate maximum length of a shared freelist.
903         */
904         @property size_t approxMaxLength() const shared;
905         /// ditto
906         @property void approxMaxLength(size_t x) shared;
907     }
908 
909     /**
910     The parent allocator. Depending on whether $(D ParentAllocator) holds state
911     or not, this is a member variable or an alias for
912     `ParentAllocator.instance`.
913     */
914     static if (stateSize!ParentAllocator) shared ParentAllocator parent;
915     else alias parent = ParentAllocator.instance;
916 
917     mixin(forwardToMember("parent", "expand"));
918 
919     private SpinLock lock;
920 
921     private struct Node { Node* next; }
922     static assert(ParentAllocator.alignment >= Node.alignof);
923     private Node* _root;
924 
925     /// Standard primitives.
926     enum uint alignment = ParentAllocator.alignment;
927 
928     /// Ditto
929     size_t goodAllocSize(size_t bytes) shared
930     {
931         if (freeListEligible(bytes)) return maxSize == unbounded ? bytes : max;
932         return parent.goodAllocSize(bytes);
933     }
934 
935     /// Ditto
936     static if (__traits(hasMember, ParentAllocator, "owns"))
937     Ternary owns(void[] b) shared const
938     {
939         return parent.owns(b);
940     }
941 
942     /// Ditto
943     static if (__traits(hasMember, ParentAllocator, "reallocate"))
944     bool reallocate(ref void[] b, size_t s) shared
945     {
946         return parent.reallocate(b, s);
947     }
948 
949     /// Ditto
950     void[] allocate(size_t bytes) shared
951     {
952         assert(bytes < size_t.max / 2);
953         if (!freeListEligible(bytes)) return parent.allocate(bytes);
954         if (maxSize != unbounded) bytes = max;
955 
956         // Try to pop off the freelist
957         lock.lock();
958         if (!_root)
959         {
960             lock.unlock();
961             return allocateFresh(bytes);
962         }
963         else
964         {
965             auto oldRoot = _root;
966             _root = _root.next;
967             decNodes();
968             lock.unlock();
969             return (cast(ubyte*) oldRoot)[0 .. bytes];
970         }
971     }
972 
973     private void[] allocateFresh(const size_t bytes) shared
974     {
975         assert(bytes == max || max == unbounded);
976         return parent.allocate(bytes);
977     }
978 
979     /// Ditto
980     bool deallocate(void[] b) shared
981     {
982         if (!nodesFull && freeListEligible(b.length))
983         {
984             auto newRoot = cast(shared Node*) b.ptr;
985             lock.lock();
986             newRoot.next = _root;
987             _root = newRoot;
988             incNodes();
989             lock.unlock();
990             return true;
991         }
992         static if (__traits(hasMember, ParentAllocator, "deallocate"))
993             return parent.deallocate(b);
994         else
995             return false;
996     }
997 
998     /// Ditto
999     bool deallocateAll() shared
1000     {
1001         bool result = false;
1002         lock.lock();
1003         scope(exit) lock.unlock();
1004         static if (__traits(hasMember, ParentAllocator, "deallocateAll"))
1005         {
1006             result = parent.deallocateAll();
1007         }
1008         else static if (__traits(hasMember, ParentAllocator, "deallocate"))
1009         {
1010             result = true;
1011             for (auto n = _root; n;)
1012             {
1013                 auto tmp = n.next;
1014                 if (!parent.deallocate((cast(ubyte*) n)[0 .. max]))
1015                     result = false;
1016                 n = tmp;
1017             }
1018         }
1019         _root = null;
1020         resetNodes();
1021         return result;
1022     }
1023 
1024     /**
1025     Nonstandard function that minimizes the memory usage of the freelist by
1026     freeing each element in turn. Defined only if $(D ParentAllocator) defines
1027     $(D deallocate).
1028     */
1029     static if (__traits(hasMember, ParentAllocator, "deallocate") && !unchecked)
1030     void minimize() shared
1031     {
1032         lock.lock();
1033         scope(exit) lock.unlock();
1034 
1035         for (auto n = _root; n;)
1036         {
1037             auto tmp = n.next;
1038             parent.deallocate((cast(ubyte*) n)[0 .. max]);
1039             n = tmp;
1040         }
1041 
1042         _root = null;
1043         resetNodes();
1044     }
1045 }
1046 
1047 ///
1048 @nogc @safe unittest
1049 {
1050     import stdx.allocator.common : chooseAtRuntime;
1051     import stdx.allocator.mallocator : Mallocator;
1052 
1053     shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
1054     a.setBounds(64, 128);
1055     assert(a.max == 128);
1056     assert(a.min == 64);
1057 }
1058 
1059 ///
1060 @nogc @safe unittest
1061 {
1062     import stdx.allocator.common : chooseAtRuntime;
1063     import stdx.allocator.mallocator : Mallocator;
1064 
1065     shared SharedFreeList!(Mallocator, 50, 50, chooseAtRuntime) a;
1066     // Set the maxSize first so setting the minSize doesn't throw
1067     a.approxMaxLength = 128;
1068     assert(a.approxMaxLength  == 128);
1069     a.approxMaxLength = 1024;
1070     assert(a.approxMaxLength  == 1024);
1071     a.approxMaxLength = 1;
1072     assert(a.approxMaxLength  == 1);
1073 }
1074 
1075 @system unittest
1076 {
1077     import core.thread : ThreadGroup;
1078     import std.algorithm.comparison : equal;
1079     import stdx.allocator.mallocator : Mallocator;
1080     import std.range : repeat;
1081 
1082     static shared SharedFreeList!(Mallocator, 64, 128, 10) a;
1083 
1084     assert(a.goodAllocSize(1) == platformAlignment);
1085 
1086     auto b = a.allocate(96);
1087     a.deallocate(b);
1088 
1089     void fun() @nogc
1090     {
1091         auto b = cast(size_t[]) a.allocate(96);
1092         b[] = cast(size_t) &b;
1093 
1094         assert(b.equal(repeat(cast(size_t) &b, b.length)));
1095         a.deallocate(b);
1096     }
1097 
1098     auto tg = new ThreadGroup;
1099     foreach (i; 0 .. 20)
1100     {
1101         tg.create(&fun);
1102     }
1103 
1104     tg.joinAll();
1105 }
1106 
1107 @nogc @system unittest
1108 {
1109     import stdx.allocator.mallocator : Mallocator;
1110     static shared SharedFreeList!(Mallocator, 64, 128, 10) a;
1111     auto b = a.allocate(100);
1112     a.deallocate(b);
1113     assert(a.nodes == 1);
1114     b = [];
1115     a.deallocateAll();
1116     assert(a.nodes == 0);
1117 }
1118 
1119 @nogc @system unittest
1120 {
1121     import stdx.allocator.mallocator : Mallocator;
1122     static shared SharedFreeList!(Mallocator, 64, 128, 10) a;
1123     auto b = a.allocate(100);
1124     auto c = a.allocate(100);
1125     a.deallocate(c);
1126     assert(a.nodes == 1);
1127     c = [];
1128     a.minimize();
1129     assert(a.nodes == 0);
1130     a.deallocate(b);
1131     assert(a.nodes == 1);
1132     b = [];
1133     a.minimize();
1134     assert(a.nodes == 0);
1135 }
1136 
1137 @nogc @system unittest
1138 {
1139     import stdx.allocator.mallocator : Mallocator;
1140     static shared SharedFreeList!(Mallocator, 64, 128, 10) a;
1141     auto b = a.allocate(100);
1142     auto c = a.allocate(100);
1143     assert(a.nodes == 0);
1144     a.deallocate(b);
1145     a.deallocate(c);
1146     assert(a.nodes == 2);
1147     b = [];
1148     c = [];
1149     a.minimize();
1150     assert(a.nodes == 0);
1151 }
1152 
1153 @nogc @system unittest
1154 {
1155     import stdx.allocator.mallocator : Mallocator;
1156     shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
1157     scope(exit) a.deallocateAll();
1158     auto c = a.allocate(64);
1159     assert(a.reallocate(c, 96));
1160     assert(c.length == 96);
1161     a.deallocate(c);
1162 }
1163 
1164 @nogc @system unittest
1165 {
1166     import stdx.allocator.mallocator : Mallocator;
1167     shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime, chooseAtRuntime) a;
1168     scope(exit) a.deallocateAll;
1169     a.allocate(64);
1170 }
1171 
1172 @nogc @system unittest
1173 {
1174     import stdx.allocator.mallocator : Mallocator;
1175     shared SharedFreeList!(Mallocator, 30, 40) a;
1176     scope(exit) a.deallocateAll;
1177     a.allocate(64);
1178 }
1179 
1180 @nogc @system unittest
1181 {
1182     import stdx.allocator.mallocator : Mallocator;
1183     shared SharedFreeList!(Mallocator, 30, 40, chooseAtRuntime) a;
1184     scope(exit) a.deallocateAll;
1185     a.allocate(64);
1186 }
1187 
1188 @nogc @system unittest
1189 {
1190     // Pull request #5556
1191     import stdx.allocator.mallocator : Mallocator;
1192     shared SharedFreeList!(Mallocator, 0, chooseAtRuntime) a;
1193     scope(exit) a.deallocateAll;
1194     a.max = 64;
1195     a.allocate(64);
1196 }
1197 
1198 @nogc @system unittest
1199 {
1200     // Pull request #5556
1201     import stdx.allocator.mallocator : Mallocator;
1202     shared SharedFreeList!(Mallocator, chooseAtRuntime, 64) a;
1203     scope(exit) a.deallocateAll;
1204     a.min = 32;
1205     a.allocate(64);
1206 }