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