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 }