1 // Written in the D programming language.
2 /**
3 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/bucketizer.d)
4 */
5 module std.experimental.allocator.building_blocks.bucketizer;
6 
7 /**
8 
9 A `Bucketizer` uses distinct allocators for handling allocations of sizes in
10 the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]),
11 $(D [min + 2 * step, min + 3 * step - 1]), `...`, $(D [max - step + 1, max]).
12 
13 `Bucketizer` holds a fixed-size array of allocators and dispatches calls to
14 them appropriately. The size of the array is $(D (max + 1 - min) / step), which
15 must be an exact division.
16 
17 Allocations for sizes smaller than `min` or larger than `max` are illegal
18 for `Bucketizer`. To handle them separately, `Segregator` may be of use.
19 
20 */
21 struct Bucketizer(Allocator, size_t min, size_t max, size_t step)
22 {
23     import common = std.experimental.allocator.common : roundUpToMultipleOf,
24            alignedAt;
25     import std.traits : hasMember;
26     import std.typecons : Ternary;
27 
28     static assert((max - (min - 1)) % step == 0,
29         "Invalid limits when instantiating " ~ Bucketizer.stringof);
30 
31     // state
32     /**
33     The array of allocators is publicly available for e.g. initialization and
34     inspection.
35     */
36     Allocator[(max + 1 - min) / step] buckets;
37 
38     pure nothrow @safe @nogc
39     private Allocator* allocatorFor(size_t n)
40     {
41         const i = (n - min) / step;
42         return i < buckets.length ? &buckets[i] : null;
43     }
44 
45     /**
46     The alignment offered is the same as `Allocator.alignment`.
47     */
48     enum uint alignment = Allocator.alignment;
49 
50     /**
51     Rounds up to the maximum size of the bucket in which `bytes` falls.
52     */
53     pure nothrow @safe @nogc
54     size_t goodAllocSize(size_t bytes) const
55     {
56         // round up bytes such that bytes - min + 1 is a multiple of step
57         assert(bytes >= min);
58         const min_1 = min - 1;
59         return min_1 + roundUpToMultipleOf(bytes - min_1, step);
60     }
61 
62     /**
63     Directs the call to either one of the `buckets` allocators.
64     */
65     void[] allocate(size_t bytes)
66     {
67         if (!bytes) return null;
68         if (auto a = allocatorFor(bytes))
69         {
70             const actual = goodAllocSize(bytes);
71             auto result = a.allocate(actual);
72             return result.ptr ? result.ptr[0 .. bytes] : null;
73         }
74         return null;
75     }
76 
77     static if (hasMember!(Allocator, "allocateZeroed"))
78     package(std) void[] allocateZeroed()(size_t bytes)
79     {
80         if (!bytes) return null;
81         if (auto a = allocatorFor(bytes))
82         {
83             const actual = goodAllocSize(bytes);
84             auto result = a.allocateZeroed(actual);
85             return result.ptr ? result.ptr[0 .. bytes] : null;
86         }
87         return null;
88     }
89 
90     /**
91     Allocates the requested `bytes` of memory with specified `alignment`.
92     Directs the call to either one of the `buckets` allocators. Defined only
93     if `Allocator` defines `alignedAllocate`.
94     */
95     static if (hasMember!(Allocator, "alignedAllocate"))
96     void[] alignedAllocate(size_t bytes, uint alignment)
97     {
98         if (!bytes) return null;
99         if (auto a = allocatorFor(bytes))
100         {
101             const actual = goodAllocSize(bytes);
102             auto result = a.alignedAllocate(actual, alignment);
103             return result !is null ? (() @trusted => (&result[0])[0 .. bytes])() : null;
104         }
105         return null;
106     }
107 
108     /**
109     This method allows expansion within the respective bucket range. It succeeds
110     if both `b.length` and $(D b.length + delta) fall in a range of the form
111     $(D [min + k * step, min + (k + 1) * step - 1]).
112     */
113     bool expand(ref void[] b, size_t delta)
114     {
115         if (!b || delta == 0) return delta == 0;
116         assert(b.length >= min && b.length <= max);
117         const available = goodAllocSize(b.length);
118         const desired = b.length + delta;
119         if (available < desired) return false;
120         b = (() @trusted => b.ptr[0 .. desired])();
121         return true;
122     }
123 
124     /**
125     This method allows reallocation within the respective bucket range. If both
126     `b.length` and `size` fall in a range of the form $(D [min + k *
127     step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise,
128     reallocation with moving is attempted.
129     */
130     bool reallocate(ref void[] b, size_t size)
131     {
132         if (size == 0)
133         {
134             deallocate(b);
135             b = null;
136             return true;
137         }
138         if (size >= b.length && expand(b, size - b.length))
139         {
140             return true;
141         }
142         assert(b.length >= min && b.length <= max);
143         if (goodAllocSize(size) == goodAllocSize(b.length))
144         {
145             b = b.ptr[0 .. size];
146             return true;
147         }
148         // Move cross buckets
149         return common.reallocate(this, b, size);
150     }
151 
152     /**
153     Similar to `reallocate`, with alignment. Defined only if `Allocator`
154     defines `alignedReallocate`.
155     */
156     static if (hasMember!(Allocator, "alignedReallocate"))
157     bool alignedReallocate(ref void[] b, size_t size, uint a)
158     {
159         if (size == 0)
160         {
161             deallocate(b);
162             b = null;
163             return true;
164         }
165         if (size >= b.length && b.ptr.alignedAt(a) && expand(b, size - b.length))
166         {
167             return true;
168         }
169         assert(b.length >= min && b.length <= max);
170         if (goodAllocSize(size) == goodAllocSize(b.length) && b.ptr.alignedAt(a))
171         {
172             b = b.ptr[0 .. size];
173             return true;
174         }
175         // Move cross buckets
176         return common.alignedReallocate(this, b, size, a);
177     }
178 
179     /**
180     Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it.
181     */
182     static if (hasMember!(Allocator, "owns"))
183     Ternary owns(void[] b)
184     {
185         if (!b.ptr) return Ternary.no;
186         if (auto a = allocatorFor(b.length))
187         {
188             const actual = goodAllocSize(b.length);
189             return a.owns(b.ptr[0 .. actual]);
190         }
191         return Ternary.no;
192     }
193 
194     /**
195     This method is only defined if `Allocator` defines `deallocate`.
196     */
197     static if (hasMember!(Allocator, "deallocate"))
198     bool deallocate(void[] b)
199     {
200         if (!b.ptr) return true;
201         if (auto a = allocatorFor(b.length))
202         {
203             a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]);
204         }
205         return true;
206     }
207 
208     /**
209     This method is only defined if all allocators involved define $(D
210     deallocateAll), and calls it for each bucket in turn. Returns `true` if all
211     allocators could deallocate all.
212     */
213     static if (hasMember!(Allocator, "deallocateAll"))
214     bool deallocateAll()
215     {
216         bool result = true;
217         foreach (ref a; buckets)
218         {
219             if (!a.deallocateAll()) result = false;
220         }
221         return result;
222     }
223 
224     /**
225     This method is only defined if all allocators involved define $(D
226     resolveInternalPointer), and tries it for each bucket in turn.
227     */
228     static if (hasMember!(Allocator, "resolveInternalPointer"))
229     Ternary resolveInternalPointer(const void* p, ref void[] result)
230     {
231         foreach (ref a; buckets)
232         {
233             Ternary r = a.resolveInternalPointer(p, result);
234             if (r == Ternary.yes) return r;
235         }
236         return Ternary.no;
237     }
238 }
239 
240 ///
241 @system unittest
242 {
243     import std.algorithm.comparison : max;
244     import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
245     import std.experimental.allocator.building_blocks.free_list : FreeList;
246     import std.experimental.allocator.building_blocks.region : Region;
247     import std.experimental.allocator.common : unbounded;
248     import std.experimental.allocator.mallocator : Mallocator;
249     import std.typecons : Ternary;
250     Bucketizer!(
251         FreeList!(
252             AllocatorList!(
253                 (size_t n) => Region!Mallocator(max(n, 1024 * 1024))),
254             0, unbounded),
255         65, 512, 64) a;
256     auto b = a.allocate(400);
257     assert(b.length == 400);
258     assert(a.owns(b) == Ternary.yes);
259     a.deallocate(b);
260 }
261 
262 @system unittest
263 {
264     import std.algorithm.comparison : max;
265     import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
266     import std.experimental.allocator.building_blocks.free_list : FreeList;
267     import std.experimental.allocator.building_blocks.region : Region;
268     import std.experimental.allocator.common : unbounded;
269     import std.experimental.allocator.mallocator : Mallocator;
270     import std.typecons : Ternary;
271 
272     Bucketizer!(
273         FreeList!(
274             AllocatorList!(
275                 (size_t n) => Region!Mallocator(max(n, 1024 * 1024)), Mallocator),
276             0, unbounded),
277         65, 512, 64) a;
278 
279     assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() == 128);
280 
281     auto b = a.allocate(100);
282     assert(b.length == 100);
283     // Make reallocate use extend
284     assert((() nothrow @nogc => a.reallocate(b, 101))());
285     assert(b.length == 101);
286     // Move cross buckets
287     assert((() nothrow @nogc => a.reallocate(b, 200))());
288     assert(b.length == 200);
289     // Free through realloc
290     assert((() nothrow @nogc => a.reallocate(b, 0))());
291     assert(b is null);
292     // Ensure deallocate inherits from parent allocators
293     assert((() nothrow @nogc => a.deallocate(b))());
294     assert((() nothrow @nogc => a.deallocateAll())());
295 }
296 
297 // Test alignedAllocate
298 @system unittest
299 {
300     import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock;
301     import std.experimental.allocator.gc_allocator : GCAllocator;
302 
303     Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 65, 512, 64) a;
304     foreach (ref bucket; a.buckets)
305     {
306         bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]);
307     }
308 
309     auto b = a.alignedAllocate(100, 16);
310     assert(b.length == 100);
311     assert(a.alignedAllocate(42, 16) is null);
312     assert(a.alignedAllocate(0, 16) is null);
313     assert((() pure nothrow @safe @nogc => a.expand(b, 0))());
314     assert(b.length == 100);
315     assert((() pure nothrow @safe @nogc => a.expand(b, 28))());
316     assert(b.length == 128);
317     assert((() pure nothrow @safe @nogc => !a.expand(b, 1))());
318 }
319 
320 @system unittest
321 {
322     import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock;
323     import std.experimental.allocator.gc_allocator : GCAllocator;
324 
325     Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 1, 512, 64) a;
326     foreach (ref bucket; a.buckets)
327     {
328         bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]);
329     }
330 
331     auto b = a.alignedAllocate(1, 4);
332     assert(b.length == 1);
333     // Make reallocate use extend
334     assert(a.alignedReallocate(b, 11, 4));
335     assert(b.length == 11);
336     // Make reallocate use use realloc because of alignment change
337     assert(a.alignedReallocate(b, 21, 16));
338     assert(b.length == 21);
339     // Make reallocate use extend
340     assert(a.alignedReallocate(b, 22, 16));
341     assert(b.length == 22);
342     // Move cross buckets
343     assert(a.alignedReallocate(b, 101, 16));
344     assert(b.length == 101);
345     // Free through realloc
346     assert(a.alignedReallocate(b, 0, 16));
347     assert(b is null);
348 }