1 ///
2 module std.experimental.allocator.building_blocks.segregator;
3 
4 import std.experimental.allocator.common;
5 
6 /**
7 Dispatches allocations (and deallocations) between two allocators ($(D
8 SmallAllocator) and $(D LargeAllocator)) depending on the size allocated, as
9 follows. All allocations smaller than or equal to $(D threshold) will be
10 dispatched to $(D SmallAllocator). The others will go to $(D LargeAllocator).
11 
12 If both allocators are $(D shared), the $(D Segregator) will also offer $(D
13 shared) methods.
14 */
Segregator(size_t threshold,SmallAllocator,LargeAllocator)15 struct Segregator(size_t threshold, SmallAllocator, LargeAllocator)
16 {
17     import std.algorithm.comparison : min;
18     import std.traits : hasMember;
19     import std.typecons : Ternary;
20 
21     static if (stateSize!SmallAllocator) private SmallAllocator _small;
22     else private alias _small = SmallAllocator.instance;
23     static if (stateSize!LargeAllocator) private LargeAllocator _large;
24     else private alias _large = LargeAllocator.instance;
25 
26     version (StdDdoc)
27     {
28         /**
29         The alignment offered is the minimum of the two allocators' alignment.
30         */
31         enum uint alignment;
32         /**
33         This method is defined only if at least one of the allocators defines
34         it. The good allocation size is obtained from $(D SmallAllocator) if $(D
35         s <= threshold), or $(D LargeAllocator) otherwise. (If one of the
36         allocators does not define $(D goodAllocSize), the default
37         implementation in this module applies.)
38         */
39         static size_t goodAllocSize(size_t s);
40         /**
41         The memory is obtained from $(D SmallAllocator) if $(D s <= threshold),
42         or $(D LargeAllocator) otherwise.
43         */
44         void[] allocate(size_t);
45         /**
46         This method is defined if both allocators define it, and forwards to
47         $(D SmallAllocator) or $(D LargeAllocator) appropriately.
48         */
49         void[] alignedAllocate(size_t, uint);
50         /**
51         This method is defined only if at least one of the allocators defines
52         it. If $(D SmallAllocator) defines $(D expand) and $(D b.length +
53         delta <= threshold), the call is forwarded to $(D SmallAllocator). If $(D
54         LargeAllocator) defines $(D expand) and $(D b.length > threshold), the
55         call is forwarded to $(D LargeAllocator). Otherwise, the call returns
56         $(D false).
57         */
58         bool expand(ref void[] b, size_t delta);
59         /**
60         This method is defined only if at least one of the allocators defines
61         it. If $(D SmallAllocator) defines $(D reallocate) and $(D b.length <=
62         threshold && s <= threshold), the call is forwarded to $(D
63         SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D
64         b.length > threshold && s > threshold), the call is forwarded to $(D
65         LargeAllocator). Otherwise, the call returns $(D false).
66         */
67         bool reallocate(ref void[] b, size_t s);
68         /**
69         This method is defined only if at least one of the allocators defines
70         it, and work similarly to $(D reallocate).
71         */
72         bool alignedReallocate(ref void[] b, size_t s);
73         /**
74         This method is defined only if both allocators define it. The call is
75         forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D
76         LargeAllocator) otherwise.
77         */
78         Ternary owns(void[] b);
79         /**
80         This function is defined only if both allocators define it, and forwards
81         appropriately depending on $(D b.length).
82         */
83         bool deallocate(void[] b);
84         /**
85         This function is defined only if both allocators define it, and calls
86         $(D deallocateAll) for them in turn.
87         */
88         bool deallocateAll();
89         /**
90         This function is defined only if both allocators define it, and returns
91         the conjunction of $(D empty) calls for the two.
92         */
93         Ternary empty();
94     }
95 
96     /**
97     Composite allocators involving nested instantiations of $(D Segregator) make
98     it difficult to access individual sub-allocators stored within. $(D
99     allocatorForSize) simplifies the task by supplying the allocator nested
100     inside a $(D Segregator) that is responsible for a specific size $(D s).
101 
102     Example:
103     ----
104     alias A = Segregator!(300,
105         Segregator!(200, A1, A2),
106         A3);
107     A a;
108     static assert(typeof(a.allocatorForSize!10) == A1);
109     static assert(typeof(a.allocatorForSize!250) == A2);
110     static assert(typeof(a.allocatorForSize!301) == A3);
111     ----
112     */
113     ref auto allocatorForSize(size_t s)()
114     {
115         static if (s <= threshold)
116             static if (is(SmallAllocator == Segregator!(Args), Args...))
117                 return _small.allocatorForSize!s;
118             else return _small;
119         else
120             static if (is(LargeAllocator == Segregator!(Args), Args...))
121                 return _large.allocatorForSize!s;
122             else return _large;
123     }
124 
125     enum uint alignment = min(SmallAllocator.alignment,
126         LargeAllocator.alignment);
127 
128     private template Impl()
129     {
130         size_t goodAllocSize(size_t s)
131         {
132             return s <= threshold
133                 ? _small.goodAllocSize(s)
134                 : _large.goodAllocSize(s);
135         }
136 
137         void[] allocate(size_t s)
138         {
139             return s <= threshold ? _small.allocate(s) : _large.allocate(s);
140         }
141 
142         static if (hasMember!(SmallAllocator, "alignedAllocate")
143                 && hasMember!(LargeAllocator, "alignedAllocate"))
144         void[] alignedAllocate(size_t s, uint a)
145         {
146             return s <= threshold
147                 ? _small.alignedAllocate(s, a)
148                 : _large.alignedAllocate(s, a);
149         }
150 
151         static if (hasMember!(SmallAllocator, "expand")
152                 || hasMember!(LargeAllocator, "expand"))
153         bool expand(ref void[] b, size_t delta)
154         {
155             if (!delta) return true;
156             if (b.length + delta <= threshold)
157             {
158                 // Old and new allocations handled by _small
159                 static if (hasMember!(SmallAllocator, "expand"))
160                     return _small.expand(b, delta);
161                 else
162                     return false;
163             }
164             if (b.length > threshold)
165             {
166                 // Old and new allocations handled by _large
167                 static if (hasMember!(LargeAllocator, "expand"))
168                     return _large.expand(b, delta);
169                 else
170                     return false;
171             }
172             // Oops, cross-allocator transgression
173             return false;
174         }
175 
176         static if (hasMember!(SmallAllocator, "reallocate")
177                 || hasMember!(LargeAllocator, "reallocate"))
178         bool reallocate(ref void[] b, size_t s)
179         {
180             static if (hasMember!(SmallAllocator, "reallocate"))
181                 if (b.length <= threshold && s <= threshold)
182                 {
183                     // Old and new allocations handled by _small
184                     return _small.reallocate(b, s);
185                 }
186             static if (hasMember!(LargeAllocator, "reallocate"))
187                 if (b.length > threshold && s > threshold)
188                 {
189                     // Old and new allocations handled by _large
190                     return _large.reallocate(b, s);
191                 }
192             // Cross-allocator transgression
193             return .reallocate(this, b, s);
194         }
195 
196         static if (hasMember!(SmallAllocator, "alignedReallocate")
197                 || hasMember!(LargeAllocator, "alignedReallocate"))
198         bool reallocate(ref void[] b, size_t s)
199         {
200             static if (hasMember!(SmallAllocator, "alignedReallocate"))
201                 if (b.length <= threshold && s <= threshold)
202                 {
203                     // Old and new allocations handled by _small
204                     return _small.alignedReallocate(b, s);
205                 }
206             static if (hasMember!(LargeAllocator, "alignedReallocate"))
207                 if (b.length > threshold && s > threshold)
208                 {
209                     // Old and new allocations handled by _large
210                     return _large.alignedReallocate(b, s);
211                 }
212             // Cross-allocator transgression
213             return .alignedReallocate(this, b, s);
214         }
215 
216         static if (hasMember!(SmallAllocator, "owns")
217                 && hasMember!(LargeAllocator, "owns"))
218         Ternary owns(void[] b)
219         {
220             return Ternary(b.length <= threshold
221                 ? _small.owns(b) : _large.owns(b));
222         }
223 
224         static if (hasMember!(SmallAllocator, "deallocate")
225                 && hasMember!(LargeAllocator, "deallocate"))
226         bool deallocate(void[] data)
227         {
228             return data.length <= threshold
229                 ? _small.deallocate(data)
230                 : _large.deallocate(data);
231         }
232 
233         static if (hasMember!(SmallAllocator, "deallocateAll")
234                 && hasMember!(LargeAllocator, "deallocateAll"))
235         bool deallocateAll()
236         {
237             // Use & insted of && to evaluate both
238             return _small.deallocateAll() & _large.deallocateAll();
239         }
240 
241         static if (hasMember!(SmallAllocator, "empty")
242                 && hasMember!(LargeAllocator, "empty"))
243         Ternary empty()
244         {
245             return _small.empty && _large.empty;
246         }
247 
248         static if (hasMember!(SmallAllocator, "resolveInternalPointer")
249                 && hasMember!(LargeAllocator, "resolveInternalPointer"))
250         Ternary resolveInternalPointer(const void* p, ref void[] result)
251         {
252             Ternary r = _small.resolveInternalPointer(p, result);
253             return r == Ternary.no ? _large.resolveInternalPointer(p, result) : r;
254         }
255     }
256 
257     private enum sharedMethods =
258         !stateSize!SmallAllocator
259         && !stateSize!LargeAllocator
260         && is(typeof(SmallAllocator.instance) == shared)
261         && is(typeof(LargeAllocator.instance) == shared);
262 
263     static if (sharedMethods)
264     {
265         static shared Segregator instance;
266         shared { mixin Impl!(); }
267     }
268     else
269     {
270         static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator)
271             static __gshared Segregator instance;
272         mixin Impl!();
273     }
274 }
275 
276 ///
277 @system unittest
278 {
279     import std.experimental.allocator.building_blocks.free_list : FreeList;
280     import std.experimental.allocator.gc_allocator : GCAllocator;
281     import std.experimental.allocator.mallocator : Mallocator;
282     alias A =
283         Segregator!(
284             1024 * 4,
285             Segregator!(
286                 128, FreeList!(Mallocator, 0, 128),
287                 GCAllocator),
288             Segregator!(
289                 1024 * 1024, Mallocator,
290                 GCAllocator)
291             );
292     A a;
293     auto b = a.allocate(200);
294     assert(b.length == 200);
295     a.deallocate(b);
296 }
297 
298 /**
299 A $(D Segregator) with more than three arguments expands to a composition of
300 elemental $(D Segregator)s, as illustrated by the following example:
301 
302 ----
303 alias A =
304     Segregator!(
305         n1, A1,
306         n2, A2,
307         n3, A3,
308         A4
309     );
310 ----
311 
312 With this definition, allocation requests for $(D n1) bytes or less are directed
313 to $(D A1); requests between $(D n1 + 1) and $(D n2) bytes (inclusive) are
314 directed to $(D A2); requests between $(D n2 + 1) and $(D n3) bytes (inclusive)
315 are directed to $(D A3); and requests for more than $(D n3) bytes are directed
316 to $(D A4). If some particular range should not be handled, $(D NullAllocator)
317 may be used appropriately.
318 
319 */
320 template Segregator(Args...)
321 if (Args.length > 3)
322 {
323     // Binary search
324     private enum cutPoint = ((Args.length - 2) / 4) * 2;
325     static if (cutPoint >= 2)
326     {
327         alias Segregator = .Segregator!(
328             Args[cutPoint],
329             .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]),
330             .Segregator!(Args[cutPoint + 2 .. $])
331         );
332     }
333     else
334     {
335         // Favor small sizes
336         alias Segregator = .Segregator!(
337             Args[0],
338             Args[1],
339             .Segregator!(Args[2 .. $])
340         );
341     }
342 }
343 
344 ///
345 @system unittest
346 {
347     import std.experimental.allocator.building_blocks.free_list : FreeList;
348     import std.experimental.allocator.gc_allocator : GCAllocator;
349     import std.experimental.allocator.mallocator : Mallocator;
350     alias A =
351         Segregator!(
352             128, FreeList!(Mallocator, 0, 128),
353             1024 * 4, GCAllocator,
354             1024 * 1024, Mallocator,
355             GCAllocator
356         );
357     A a;
358     auto b = a.allocate(201);
359     assert(b.length == 201);
360     a.deallocate(b);
361 }
362