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