1 module mergearray.impl.allocator;
2 
3 /++
4 Namespace for a static, thread-local region allocator wrapping the GC.
5 +/
6 struct GCAllocator {
7 private:
8     static bool in_region = false;
9     
10 public:
11     /++
12     Returns: Whether or not the allocator's region is active for this thread.
13     +/
14     @property
15     static bool inRegion() { return in_region; }
16     /++
17     Have the calling thread enter the region.
18     
19     This allocator does not require scoping allocations within the region, but
20     the functionality is provided to be consistent with other allocators.
21     +/
22     static void enter(size_t) { in_region = true; }
23     /++
24     Have the calling thread exit the region.
25     
26     WARNING: This does not deallocate allocated memory, nor are allocations
27     guaranteed to survive until this is called.
28     +/
29     static void exit() { in_region = false; }
30     /++
31     Allocates the size bytes from the GC and returns a slice to it.
32     Params:
33     size = Number of bytes to allocate.
34     Returns: new void[](size)
35     +/
36     static void[] alloc(size_t size) in { assert(inRegion()); } body {
37         return new void[](size);
38     }
39     /++
40     Allocates a T onto the GC and returns a pointer or class reference to it.
41     Params:
42     args = Arguments to the constructor of T.
43     Returns: new T(args)
44     +/
45     static auto alloc(T, Args...)(Args args) in { assert(inRegion()); } body {
46         return new T(args);
47     }
48 }
49 
50 /++
51 Namespace for a static, thread-local region allocator wrapping an
52 std.experimental.allocator.IAllocator.
53 +/
54 struct StdAllocator {
55 private:
56     import std.experimental.allocator : IAllocator, processAllocator, make;
57     
58     static bool in_region = false;
59     
60 public:
61     /++
62     Thread-local reference to the allocator backing the region, defaulting to
63     std.experimental.allocator.processAllocator.
64     +/
65     static IAllocator my_allocator = null;
66     
67     static this() {
68         my_allocator = processAllocator;
69     }
70     
71     /++
72     Returns: Whether the calling thread is in the region or not.
73     +/
74     @property
75     static bool inRegion() { return in_region; }
76     /++
77     Have the calling thread enter the region without changing my_allocator.
78     +/
79     static void enter(size_t) {
80         in_region = true;
81     }
82     /++
83     Have the calling thread enter the region, and assign a new my_allocator.
84     Params:
85     ia = An allocator to assign to my_allocator.
86     +/
87     static void enter(IAllocator ia) {
88         in_region = true;
89         my_allocator = ia;
90     }
91     /++
92     Have the calling thread exit the region.
93     
94     WARNING: Does not mutate my_allocator. Deconstructors and deallocation must
95     be handled manually through my_allocator.
96     +/
97     static void exit() {
98         in_region = false;
99     }
100     /++
101     Allocates size bytes from my_allocator and returns a slice to it.
102     Params:
103     size = Number of bytes from my_allocator to allocate.
104     Returns: A slice to the size bytes from my_allocator.
105     +/
106     static auto alloc(size_t size) in { assert(inRegion()); } body {
107         import std.stdio;
108         debug(mergearray_allocator) writeln(size);
109         return my_allocator.allocate(size);
110     }
111     /++
112     Makes a T using my_allocator, and returns a pointer to it.
113     Params:
114     args = Arguments to the constructor of T.
115     Returns: A pointer or class reference to the T that my_allocator made.
116     +/
117     static auto alloc(T, Args...)(Args args) in { assert(inRegion()); } body {
118         import std.stdio;
119         debug(mergearray_allocator) writeln(T.stringof, " = ", T.sizeof);
120         return my_allocator.make!T(args);
121     }
122 }
123 
124 /++
125 Namespace for a static, thread-local, non-nested, unbounded-size allocator.
126 
127 Memory is allocated bump-the-pointer style within large GC-allocated chunks.
128 A new chunk is allocated when the current one is too full for the next request.
129 Deallocation of chunks is handled by the GC, and destructors are not called.
130 +/
131 struct ChunkGCAllocator {
132 private:
133     static void[] region;
134     static size_t curPos = 0;
135     static size_t allocSize = 0;//2 ^^ 27; // 128MB
136     static size_t prevAllocs = 0;
137     static bool in_region = false;
138     
139     debug(mergearray_allocator) import std.stdio;
140     
141     static void replace() {
142         debug(mergearray_allocator) {
143             writefln("%s replace: allocating %s bytes.", typeof(this).stringof, allocSize);
144         }
145         prevAllocs += region.length;
146         
147         region = new void[](allocSize);
148         
149         curPos = 0;
150     }
151     
152 public:
153     /++
154     Peek at the current head of the region.
155     This value can be used to check how many bytes have been allocated so far
156     in the current chunk.
157     +/
158     static size_t peek() { return curPos; }
159     /++
160     Returns: Whether the calling thread is in the region or not.
161     +/
162     static bool inRegion() { return in_region; }
163     /++
164     Have the calling thread enter the region with a chunks of size chunkSize.
165     Params:
166     chunkSize = The size of allocated chunks throughout the region.
167     +/
168     static void enter(size_t chunkSize) {
169         allocSize = chunkSize;
170         in_region = true;
171         replace();
172         debug(mergearray_allocator) {
173             writefln("%s enter: %s/%s bytes free.", typeof(this).stringof, region.length - curPos, region.length + prevAllocs);
174         }
175     }
176     /++
177     Have the calling thread exit the region, releasing any references to an
178     allocated chunk.
179     
180     WARNING: This does not deallocate allocated memory, nor are allocations
181     guaranteed to survive until this is called, nor are deconstructors called.
182     +/
183     static void exit() {
184         debug(mergearray_allocator) {
185             writefln("%s exit:  %s/%s bytes free.", typeof(this).stringof, region.length - curPos, region.length + prevAllocs);
186         }
187         in_region = false;
188         region = null;
189         prevAllocs = 0;
190     }
191     /++
192     Allocates the next size bytes from the region and returns a slice to it.
193     
194     If the current chunk is too full, then a new one is allocated and used.
195     Params:
196     size = Number of bytes from the region to allocate.
197     Returns: A slice to the next size unused bytes from the region.
198     +/
199     static void[] alloc(size_t size) in { assert(inRegion()); } body {
200         if (curPos + size > region.length) {
201             replace();
202         }
203         scope(success) curPos += size;
204         
205         debug(mergearray_allocator) writefln("%s alloc: %s bytes allocated.", typeof(this).stringof, size);
206         
207         return region[curPos .. curPos + size];
208     }
209     /++
210     Allocates at least T.sizeof bytes, emplaces a T there, and returns a pointer to it.
211     
212     The allocation IS automatically padded to ensure proper alignment.
213     Params:
214     args = Arguments to the constructor of T.
215     Returns: A pointer or class reference to the allocated and emplaced T.
216     +/
217     static auto alloc(T, Args...)(Args args) in { assert(inRegion()); } body {
218         size_t alignOffset = (T.alignof - curPos % T.alignof);
219         
220         if (curPos + T.sizeof + alignOffset > region.length) {
221             replace();
222             alignOffset = 0;
223         }
224         
225         immutable size = T.sizeof + alignOffset;
226         
227         scope(success) curPos += size;
228         
229         assert(curPos + size <= region.length);
230         void[] slice = region[curPos + alignOffset .. curPos + size];
231         
232         assert(slice.length == T.sizeof);
233         return emplace!T(slice, args);
234     }
235 }
236 
237 /++
238 Namespace for a static, thread-local, non-nested, bounded-size region allocator.
239 
240 Memory is allocated bump-the-pointer style within one buffer which must not
241 overflow. Exiting will free the buffer without calling destructors.
242 +/
243 struct TLRegionAllocator {
244 private:
245     import core.memory;
246     
247     static void[] buffer = null;
248     static size_t curPos = 0;
249     
250     debug(mergearray_allocator) import std.stdio;
251     
252 public:
253     /++
254     Returns: Whether the calling thread is in the region or not.
255     +/
256     @property
257     static bool inRegion() {
258         return buffer !is null;
259     }
260     /++
261     Peek at the current head of the region.
262     
263     This value can be used to check how many bytes have been allocated so far,
264     and to determine the alignment of the next allocation.
265     +/
266     static size_t peek() { return curPos; }
267     
268     /++
269     Have the calling thread enter the region with a given size maxBytes.
270     Params:
271     maxBytes = The number of bytes to initialize the region to.
272     +/
273     static void enter(size_t maxBytes) {
274         debug(mergearray_allocator) writefln("%s enter: %s bytes allocating...", typeof(this).stringof, maxBytes);
275         
276         buffer = GC.malloc(maxBytes)[0 .. maxBytes];
277         
278         assert(buffer.length == maxBytes);
279         
280         curPos = 0;
281         debug(mergearray_allocator) writefln("%s enter: %s bytes allocated.", typeof(this).stringof, buffer.length);
282     }
283     
284     /++
285     Have the calling thread exit the region, deallocating the memory.
286     WARNING: Destructors of allocated structs or objects are not called.
287     +/
288     static void exit() {
289         debug(mergearray_allocator) writefln("%s exit: %s/%s (%s%%) bytes used.", typeof(this).stringof, curPos, buffer.length, curPos * 100.0 / buffer.length);
290         
291         auto ptr = buffer.ptr;
292         buffer = null;
293         GC.free(ptr);
294     }
295     
296     /++
297     Allocates the next size bytes from the region and returns a slice to it.
298     Params:
299     size = Number of bytes from the region to allocate.
300     Returns: A slice to the next size unused bytes from the region.
301     +/
302     static void[] alloc(size_t size)
303     in {
304         assert(inRegion());
305         
306         version(assert) {
307             import std.string : format;
308             
309             assert(curPos + size <= buffer.length,
310                 "%s alloc: attempt to alloc %s but only %s/%s free"
311                 .format(typeof(this).stringof, size, buffer.length - curPos, buffer.length)
312             );
313         }
314     }
315     body {
316         scope(exit) curPos += size;
317         
318         debug(mergearray_allocator) writefln("%s alloc: %s bytes allocated.", typeof(this).stringof, size);
319         
320         return buffer[curPos .. curPos + size];
321     }
322     /++
323     Allocates T.sizeof bytes, emplaces a T there, and returns a pointer to it.
324     Warning: The allocation is NOT automatically padded to ensure proper alignment.
325     Params:
326     args = Arguments to the constructor of T.
327     Returns: A pointer or class reference to the allocated and emplaced T.
328     +/
329     static auto alloc(T, Args...)(Args args)
330     in {
331         assert(inRegion());
332         assert(curPos % T.alignof == 0);
333     }
334     body {
335         auto space = alloc(T.sizeof);
336         
337         import std.conv : emplace;
338         
339         return emplace!T(space, args);
340     }
341 }
342 
343 // unused/unpolished
344 static if (false)
345 struct TLStaticAllocator {
346 private:
347     static void[length] buffer = void;
348     static size_t curPos = size_t.max;
349     
350     debug(mergearray_allocator) import std.stdio;
351     
352 public:
353     enum size_t length = 2 ^^ 26;
354     
355     static bool inRegion() {
356         return curPos <= length;
357     }
358     
359     static size_t peek() { return curPos; }
360     
361     static void enter(size_t maxBytes = 0) in { assert(maxBytes <= length); } body {
362         curPos = 0;
363         
364         debug(mergearray_allocator) writefln("%s enter: %s bytes allocated.", typeof(this).stringof, length);
365     }
366     
367     static void exit() {
368         debug(mergearray_allocator) writefln("%s exit: %s/%s (%s%%) bytes used.", typeof(this).stringof, curPos, buffer.length, curPos * 100.0 / buffer.length);
369         curPos = size_t.max;
370     }
371     
372     static auto alloc(size_t size)
373     in {
374         assert(inRegion());
375         assert(curPos + size <= buffer.length);
376     }
377     body {
378         scope(exit) curPos += size;
379         
380         debug(mergearray_allocator) writefln("%s alloc: %s bytes allocated.", typeof(this).stringof, size);
381         
382         return buffer[curPos .. curPos + size];
383     }
384     
385     static auto alloc(T, Args...)(Args args)
386     in {
387         assert(inRegion());
388         assert(curPos + T.sizeof <= buffer.length);
389         assert(curPos % T.alignof == 0);
390     }
391     body {
392         scope(exit) curPos += T.sizeof;
393         
394         import std.conv;
395         
396         return emplace!T(buffer[curPos .. curPos + T.sizeof], args);
397     }
398 }
399