1 ///
2 module stdx.allocator.building_blocks.fallback_allocator;
3 
4 import stdx.allocator.common;
5 
6 /**
7 $(D FallbackAllocator) is the allocator equivalent of an "or" operator in
8 algebra. An allocation request is first attempted with the $(D Primary)
9 allocator. If that returns $(D null), the request is forwarded to the $(D
10 Fallback) allocator. All other requests are dispatched appropriately to one of
11 the two allocators.
12 
13 In order to work, $(D FallbackAllocator) requires that $(D Primary) defines the
14 $(D owns) method. This is needed in order to decide which allocator was
15 responsible for a given allocation.
16 
17 $(D FallbackAllocator) is useful for fast, special-purpose allocators backed up
18 by general-purpose allocators. The example below features a stack region backed
19 up by the $(D GCAllocator).
20 */
21 struct FallbackAllocator(Primary, Fallback)
22 {
23     import mir.utility : min;
24     import stdx.allocator.internal : Ternary;
25 
26     @system unittest
27     {
28         testAllocator!(() => FallbackAllocator());
29     }
30 
31     /// The primary allocator.
32     static if (stateSize!Primary) Primary primary;
33     else alias primary = Primary.instance;
34 
35     /// The fallback allocator.
36     static if (stateSize!Fallback) Fallback fallback;
37     else alias fallback = Fallback.instance;
38 
39     /**
40     If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator)
41     defines a static instance called `instance`.
42     */
43     static if (!stateSize!Primary && !stateSize!Fallback)
44     {
45         enum FallbackAllocator instance = FallbackAllocator();
46     }
47 
48     /**
49     The alignment offered is the minimum of the two allocators' alignment.
50     */
51     enum uint alignment = min(Primary.alignment, Fallback.alignment);
52 
53     /**
54     Allocates memory trying the primary allocator first. If it returns $(D
55     null), the fallback allocator is tried.
56     */
57     void[] allocate(size_t s)
58     {
59         auto result = primary.allocate(s);
60         return result.length == s ? result : fallback.allocate(s);
61     }
62 
63     /**
64     $(D FallbackAllocator) offers $(D alignedAllocate) iff at least one of the
65     allocators also offers it. It attempts to allocate using either or both.
66     */
67     static if (__traits(hasMember, Primary, "alignedAllocate")
68         || __traits(hasMember, Fallback, "alignedAllocate"))
69     void[] alignedAllocate(size_t s, uint a)
70     {
71         static if (__traits(hasMember, Primary, "alignedAllocate"))
72         {{
73             auto result = primary.alignedAllocate(s, a);
74             if (result.length == s) return result;
75         }}
76         static if (__traits(hasMember, Fallback, "alignedAllocate"))
77         {{
78             auto result = fallback.alignedAllocate(s, a);
79             if (result.length == s) return result;
80         }}
81         return null;
82     }
83 
84     /**
85 
86     $(D expand) is defined if and only if at least one of the allocators
87     defines $(D expand). It works as follows. If $(D primary.owns(b)), then the
88     request is forwarded to $(D primary.expand) if it is defined, or fails
89     (returning $(D false)) otherwise. If $(D primary) does not own $(D b), then
90     the request is forwarded to $(D fallback.expand) if it is defined, or fails
91     (returning $(D false)) otherwise.
92 
93     */
94     static if (__traits(hasMember, Primary, "owns")
95         && (__traits(hasMember, Primary, "expand") || __traits(hasMember, Fallback, "expand")))
96     bool expand(ref void[] b, size_t delta)
97     {
98         if (!delta) return true;
99         if (!b.ptr) return false;
100         if (primary.owns(b) == Ternary.yes)
101         {
102             static if (__traits(hasMember, Primary, "expand"))
103                 return primary.expand(b, delta);
104             else
105                 return false;
106         }
107         static if (__traits(hasMember, Fallback, "expand"))
108             return fallback.expand(b, delta);
109         else
110             return false;
111     }
112 
113     /**
114 
115     $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D
116     primary.reallocate(b, newSize)) is attempted. If it fails, an attempt is
117     made to move the allocation from $(D primary) to $(D fallback).
118 
119     If $(D primary) does not own $(D b), then $(D fallback.reallocate(b,
120     newSize)) is attempted. If that fails, an attempt is made to move the
121     allocation from $(D fallback) to $(D primary).
122 
123     */
124     static if (__traits(hasMember, Primary, "owns"))
125     bool reallocate(ref void[] b, size_t newSize)
126     {
127         bool crossAllocatorMove(From, To)(auto ref From from, auto ref To to)
128         {
129             auto b1 = to.allocate(newSize);
130             if (b1.length != newSize) return false;
131             if (b.length < newSize) b1[0 .. b.length] = b[];
132             else b1[] = b[0 .. newSize];
133             static if (__traits(hasMember, From, "deallocate"))
134                 from.deallocate(b);
135             b = b1;
136             return true;
137         }
138 
139         if (b is null || primary.owns(b) == Ternary.yes)
140         {
141             return primary.reallocate(b, newSize)
142                 // Move from primary to fallback
143                 || crossAllocatorMove(primary, fallback);
144         }
145         return fallback.reallocate(b, newSize)
146             // Interesting. Move from fallback to primary.
147             || crossAllocatorMove(fallback, primary);
148     }
149 
150     static if (__traits(hasMember, Primary, "owns")
151         && (__traits(hasMember, Primary, "alignedAllocate")
152             || __traits(hasMember, Fallback, "alignedAllocate")))
153     bool alignedReallocate(ref void[] b, size_t newSize, uint a)
154     {
155         bool crossAllocatorMove(From, To)(auto ref From from, auto ref To to)
156         {
157             static if (!__traits(hasMember, To, "alignedAllocate"))
158             {
159                 return false;
160             }
161             else
162             {
163                 auto b1 = to.alignedAllocate(newSize, a);
164                 if (b1.length != newSize) return false;
165                 if (b.length < newSize) b1[0 .. b.length] = b[];
166                 else b1[] = b[0 .. newSize];
167                 static if (__traits(hasMember, From, "deallocate"))
168                     from.deallocate(b);
169                 b = b1;
170                 return true;
171             }
172         }
173 
174         static if (__traits(hasMember, Primary, "alignedAllocate"))
175         {
176             if (b is null || primary.owns(b) == Ternary.yes)
177             {
178                 return primary.alignedReallocate(b, newSize, a)
179                     || crossAllocatorMove(primary, fallback);
180             }
181         }
182         static if (__traits(hasMember, Fallback, "alignedAllocate"))
183         {
184             return fallback.alignedReallocate(b, newSize, a)
185                 || crossAllocatorMove(fallback, primary);
186         }
187         else
188         {
189             return false;
190         }
191     }
192 
193     /**
194     $(D owns) is defined if and only if both allocators define $(D owns).
195     Returns $(D primary.owns(b) | fallback.owns(b)).
196     */
197     static if (__traits(hasMember, Primary, "owns") && __traits(hasMember, Fallback, "owns"))
198     Ternary owns(void[] b)
199     {
200         return primary.owns(b) | fallback.owns(b);
201     }
202 
203     /**
204     $(D resolveInternalPointer) is defined if and only if both allocators
205     define it.
206     */
207     static if (__traits(hasMember, Primary, "resolveInternalPointer")
208         && __traits(hasMember, Fallback, "resolveInternalPointer"))
209     Ternary resolveInternalPointer(const void* p, ref void[] result)
210     {
211         Ternary r = primary.resolveInternalPointer(p, result);
212         return r == Ternary.no ? fallback.resolveInternalPointer(p, result) : r;
213     }
214 
215     /**
216     $(D deallocate) is defined if and only if at least one of the allocators
217     define    $(D deallocate). It works as follows. If $(D primary.owns(b)),
218     then the request is forwarded to $(D primary.deallocate) if it is defined,
219     or is a no-op otherwise. If $(D primary) does not own $(D b), then the
220     request is forwarded to $(D fallback.deallocate) if it is defined, or is a
221     no-op otherwise.
222     */
223     static if (__traits(hasMember, Primary, "owns") &&
224         (__traits(hasMember, Primary, "deallocate")
225             || __traits(hasMember, Fallback, "deallocate")))
226     bool deallocate(void[] b)
227     {
228         if (primary.owns(b) == Ternary.yes)
229         {
230             static if (__traits(hasMember, Primary, "deallocate"))
231                 return primary.deallocate(b);
232             else
233                 return false;
234         }
235         else
236         {
237             static if (__traits(hasMember, Fallback, "deallocate"))
238                 return fallback.deallocate(b);
239             else
240                 return false;
241         }
242     }
243 
244     /**
245     $(D empty) is defined if both allocators also define it.
246 
247     Returns: $(D primary.empty & fallback.empty)
248     */
249     static if (__traits(hasMember, Primary, "empty") && __traits(hasMember, Fallback, "empty"))
250     Ternary empty()
251     {
252         return primary.empty & fallback.empty;
253     }
254 }
255 
256 @system unittest
257 {
258     import std.conv : text;
259     import stdx.allocator.building_blocks.region : InSituRegion;
260     import stdx.allocator.gc_allocator : GCAllocator;
261     import stdx.allocator.internal : Ternary;
262     FallbackAllocator!(InSituRegion!16_384, GCAllocator) a;
263     // This allocation uses the stack
264     auto b1 = a.allocate(1024);
265     assert(b1.length == 1024, text(b1.length));
266     assert(a.primary.owns(b1) == Ternary.yes);
267     // This large allocation will go to the Mallocator
268     auto b2 = a.allocate(1024 * 1024);
269     assert(a.primary.owns(b2) == Ternary.no);
270     a.deallocate(b1);
271     a.deallocate(b2);
272 }
273 
274 /**
275 Convenience function that uses type deduction to return the appropriate
276 $(D FallbackAllocator) instance. To initialize with allocators that don't have
277 state, use their $(D it) static member.
278 */
279 FallbackAllocator!(Primary, Fallback)
280 fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f)
281 {
282     import mir.functional: forward;
283 
284     alias R = FallbackAllocator!(Primary, Fallback);
285 
286     static if (stateSize!Primary)
287         static if (stateSize!Fallback)
288             return R(forward!p, forward!f);
289         else
290             return R(forward!p);
291     else
292         static if (stateSize!Fallback)
293             return R(forward!f);
294         else
295             return R();
296 }
297 
298 ///
299 @system unittest
300 {
301     import stdx.allocator.building_blocks.region : Region;
302     import stdx.allocator.gc_allocator : GCAllocator;
303     import stdx.allocator.internal : Ternary;
304     auto a = fallbackAllocator(Region!GCAllocator(1024), GCAllocator.instance);
305     auto b1 = a.allocate(1020);
306     assert(b1.length == 1020);
307     assert(a.primary.owns(b1) == Ternary.yes);
308     auto b2 = a.allocate(10);
309     assert(b2.length == 10);
310     assert(a.primary.owns(b2) == Ternary.no);
311 }