1 ///
2 module stdx.allocator.building_blocks.bucketizer;
3 
4 /**
5 
6 A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in
7 the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]),
8 $(D [min + 2 * step, min + 3 * step - 1]), $(D ...), $(D [max - step + 1, max]).
9 
10 $(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to
11 them appropriately. The size of the array is $(D (max + 1 - min) / step), which
12 must be an exact division.
13 
14 Allocations for sizes smaller than $(D min) or larger than $(D max) are illegal
15 for $(D Bucketizer). To handle them separately, $(D Segregator) may be of use.
16 
17 */
18 struct Bucketizer(Allocator, size_t min, size_t max, size_t step)
19 {
20     import common = stdx.allocator.common : roundUpToMultipleOf;
21     import stdx.allocator.internal : Ternary;
22 
23     static assert((max - (min - 1)) % step == 0,
24         "Invalid limits when instantiating " ~ Bucketizer.stringof);
25 
26     // state
27     /**
28     The array of allocators is publicly available for e.g. initialization and
29     inspection.
30     */
31     Allocator[(max + 1 - min) / step] buckets;
32 
33     private Allocator* allocatorFor(size_t n)
34     {
35         const i = (n - min) / step;
36         return i < buckets.length ? buckets.ptr + i : null;
37     }
38 
39     /**
40     The alignment offered is the same as $(D Allocator.alignment).
41     */
42     enum uint alignment = Allocator.alignment;
43 
44     /**
45     Rounds up to the maximum size of the bucket in which $(D bytes) falls.
46     */
47     size_t goodAllocSize(size_t bytes) const
48     {
49         // round up bytes such that bytes - min + 1 is a multiple of step
50         assert(bytes >= min);
51         const min_1 = min - 1;
52         return min_1 + roundUpToMultipleOf(bytes - min_1, step);
53     }
54 
55     /**
56     Directs the call to either one of the $(D buckets) allocators.
57     */
58     void[] allocate(size_t bytes)
59     {
60         if (!bytes) return null;
61         if (auto a = allocatorFor(bytes))
62         {
63             const actual = goodAllocSize(bytes);
64             auto result = a.allocate(actual);
65             return result.ptr ? result.ptr[0 .. bytes] : null;
66         }
67         return null;
68     }
69 
70     /**
71     Directs the call to either one of the $(D buckets) allocators. Defined only
72     if `Allocator` defines `alignedAllocate`.
73     */
74     static if (__traits(hasMember, Allocator, "alignedAllocate"))
75     void[] alignedAllocate(size_t bytes, uint a)
76     {
77         if (!bytes) return null;
78         if (auto a = allocatorFor(b.length))
79         {
80             const actual = goodAllocSize(bytes);
81             auto result = a.alignedAllocate(actual);
82             return result.ptr ? result.ptr[0 .. bytes] : null;
83         }
84         return null;
85     }
86 
87     /**
88     This method allows expansion within the respective bucket range. It succeeds
89     if both $(D b.length) and $(D b.length + delta) fall in a range of the form
90     $(D [min + k * step, min + (k + 1) * step - 1]).
91     */
92     bool expand(ref void[] b, size_t delta)
93     {
94         if (!b.ptr) return delta == 0;
95         assert(b.length >= min && b.length <= max);
96         const available = goodAllocSize(b.length);
97         const desired = b.length + delta;
98         if (available < desired) return false;
99         b = b.ptr[0 .. desired];
100         return true;
101     }
102 
103     /**
104     This method allows reallocation within the respective bucket range. If both
105     $(D b.length) and $(D size) fall in a range of the form $(D [min + k *
106     step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise,
107     reallocation with moving is attempted.
108     */
109     bool reallocate(ref void[] b, size_t size)
110     {
111         if (size == 0)
112         {
113             deallocate(b);
114             b = null;
115             return true;
116         }
117         if (size >= b.length)
118         {
119             return expand(b, size - b.length);
120         }
121         assert(b.length >= min && b.length <= max);
122         if (goodAllocSize(size) == goodAllocSize(b.length))
123         {
124             b = b.ptr[0 .. size];
125             return true;
126         }
127         // Move cross buckets
128         return common.reallocate(this, b, size);
129     }
130 
131     /**
132     Similar to `reallocate`, with alignment. Defined only if `Allocator`
133     defines `alignedReallocate`.
134     */
135     static if (__traits(hasMember, Allocator, "alignedReallocate"))
136     bool alignedReallocate(ref void[] b, size_t size, uint a)
137     {
138         if (size == 0)
139         {
140             deallocate(b);
141             b = null;
142             return true;
143         }
144         if (size >= b.length)
145         {
146             return expand(b, size - b.length);
147         }
148         assert(b.length >= min && b.length <= max);
149         if (goodAllocSize(size) == goodAllocSize(b.length))
150         {
151             b = b.ptr[0 .. size];
152             return true;
153         }
154         // Move cross buckets
155         return .alignedReallocate(this, b, size, a);
156     }
157 
158     /**
159     Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it.
160     */
161     static if (__traits(hasMember, Allocator, "owns"))
162     Ternary owns(void[] b)
163     {
164         if (!b.ptr) return Ternary.no;
165         if (auto a = allocatorFor(b.length))
166         {
167             const actual = goodAllocSize(b.length);
168             return a.owns(b.ptr[0 .. actual]);
169         }
170         return Ternary.no;
171     }
172 
173     /**
174     This method is only defined if $(D Allocator) defines $(D deallocate).
175     */
176     static if (__traits(hasMember, Allocator, "deallocate"))
177     bool deallocate(void[] b)
178     {
179         if (!b.ptr) return true;
180         if (auto a = allocatorFor(b.length))
181         {
182             a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]);
183         }
184         return true;
185     }
186 
187     /**
188     This method is only defined if all allocators involved define $(D
189     deallocateAll), and calls it for each bucket in turn. Returns `true` if all
190     allocators could deallocate all.
191     */
192     static if (__traits(hasMember, Allocator, "deallocateAll"))
193     bool deallocateAll()
194     {
195         bool result = true;
196         foreach (ref a; buckets)
197         {
198             if (!a.deallocateAll()) result = false;
199         }
200         return result;
201     }
202 
203     /**
204     This method is only defined if all allocators involved define $(D
205     resolveInternalPointer), and tries it for each bucket in turn.
206     */
207     static if (__traits(hasMember, Allocator, "resolveInternalPointer"))
208     Ternary resolveInternalPointer(const void* p, ref void[] result)
209     {
210         foreach (ref a; buckets)
211         {
212             Ternary r = a.resolveInternalPointer(p, result);
213             if (r == Ternary.yes) return r;
214         }
215         return Ternary.no;
216     }
217 }
218 
219 ///
220 @system unittest
221 {
222     import mir.utility : max;
223     import stdx.allocator.building_blocks.allocator_list : AllocatorList;
224     import stdx.allocator.building_blocks.free_list : FreeList;
225     import stdx.allocator.building_blocks.region : Region;
226     import stdx.allocator.common : unbounded;
227     import stdx.allocator.mallocator : Mallocator;
228     import stdx.allocator.internal : Ternary;
229     Bucketizer!(
230         FreeList!(
231             AllocatorList!(
232                 (size_t n) => Region!Mallocator(max(n, 1024u * 1024))),
233             0, unbounded),
234         65, 512, 64) a;
235     auto b = a.allocate(400);
236     assert(b.length == 400);
237     assert(a.owns(b) == Ternary.yes);
238     void[] p;
239     a.deallocate(b);
240 }