1 /**
2 Stack allocator for temporary allocations.
3 
4 Copyright: Auburn Sounds 2019.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module dplug.core.stackallocator;
8 
9 import core.stdc.stdlib: malloc, free;
10 import dplug.core.vec;
11 
12 struct StackAllocator
13 {
14 private:
15     Vec!(ubyte*) bucketArray;
16     uint numUsedPages;
17     uint currentPageFreeBytes;
18     enum PAGE_SIZE = 1024 * 1024 * 1024; // 1MB
19 
20     struct State
21     {
22         uint savedNumUsedPages;
23         uint savedCurrentPageFreeBytes;
24     }
25 
26 public:
27 
28     @disable this(this); // non copyable
29 
30     ~this()
31     {
32         foreach(ubyte* bucket; bucketArray)
33             free(bucket);
34     }
35 
36     /// Save allocation state
37     State saveState()
38     {
39         return State(numUsedPages, currentPageFreeBytes);
40     }
41 
42     /// Pop allocation state
43     void restoreState(State state)
44     {
45         numUsedPages = state.savedNumUsedPages;
46         currentPageFreeBytes = state.savedCurrentPageFreeBytes;
47     }
48 
49     /// return pointer to len x T.sizeof bytes of uninitialized memory
50     T[] makeArray(T)(size_t len)
51     {
52         size_t allocSize = len * T.sizeof;
53         assert(allocSize <= PAGE_SIZE, "Requested size is bigger that page size");
54 
55         if (currentPageFreeBytes < allocSize)
56             setupNextPage;
57 
58         size_t nextByte = PAGE_SIZE - currentPageFreeBytes;
59         currentPageFreeBytes -= allocSize;
60 
61         ubyte* pagePtr = bucketArray[numUsedPages-1];
62         ubyte[] bytes = pagePtr[nextByte..nextByte+allocSize];
63 
64         return cast(T[])bytes;
65     }
66 
67     private void setupNextPage()
68     {
69         if (numUsedPages == bucketArray.length)
70         {
71             ubyte* newBucket = cast(ubyte*)malloc(PAGE_SIZE);
72             bucketArray.pushBack(newBucket);
73         }
74         // alloc from new page
75         ++numUsedPages;
76         currentPageFreeBytes = PAGE_SIZE;
77     }
78 }
79 
80 unittest
81 {
82     StackAllocator allocator;
83     auto saved = allocator.saveState;
84     uint[] arr = allocator.makeArray!uint(10);
85     arr[] = 42;
86     assert(arr.length == 10);
87     allocator.restoreState(saved);
88 
89     uint[] arr2 = allocator.makeArray!uint(10);
90     arr2[] = 48;
91 
92     assert(arr[0] == 48);
93 
94     // multiple allocations
95     uint[] arr3 = allocator.makeArray!uint(10);
96     arr3[] = 60;
97 
98     // doesn't overwrite arr2
99     assert(arr2[0] == 48);
100     assert(arr2[$-1] == 48);
101 
102     allocator.restoreState(saved);
103 
104     // test new page allocation
105     allocator.makeArray!uint(1);
106     allocator.makeArray!uint(StackAllocator.PAGE_SIZE / uint.sizeof);
107 
108     allocator.restoreState(saved);
109 
110     // test page reuse
111     allocator.makeArray!uint(StackAllocator.PAGE_SIZE / uint.sizeof);
112 }