1 module libd.memory.allocator.blockallocator;
2 
3 import libd.memory, libd.datastructures, libd.threading;
4 
5 @(OnMove.forbid)
6 shared struct BlockBucketAllocator(size_t size)
7 {
8     @disable this(this);
9     @disable this(scope const ref typeof(this)){}
10 
11     static immutable string Tag = "block_bucket_alloc";
12 
13     private
14     {
15         static struct Node
16         {
17             BlockAllocator!size alloc;
18             Node* next;
19         }
20 
21         Node* _head;
22         Node* _tail;
23         shared RegionAllocator _nodeAllocBase;
24         shared AllocatorWrapperOf!(typeof(_nodeAllocBase)) _nodeAlloc;
25         size_t _pageFactor;
26     }
27 
28     @nogc nothrow:
29 
30     this(size_t pageFactor)
31     {
32         this._nodeAllocBase = typeof(_nodeAllocBase)(pageFactor);
33         this._nodeAlloc = typeof(_nodeAlloc)(&this._nodeAllocBase);
34         this._pageFactor = pageFactor;
35         this.newNode();
36     }
37 
38     ~this()
39     {
40         while(this._head !is null)
41         {
42             this._head.alloc.__xdtor();
43             this._head = this._head.next;
44         }
45     }
46 
47     MaybeNullSlice!(T, Tag) alloc(T)(size_t amount)
48     {
49         if(amount > this._head.alloc._maxAlloc)
50             return typeof(return)(null);
51 
52         auto curr = this._head;
53         while(curr !is null)
54         {
55             auto alloc = curr.alloc.alloc!T(amount);
56             if(alloc !is null)
57                 return typeof(return)(alloc.slice);
58             curr = curr.next;
59         }
60 
61         curr = this.newNode();
62         if(curr is null)
63             return typeof(return)(null);
64         auto alloc = curr.alloc.alloc!T(amount);
65         if(alloc !is null)
66             return typeof(return)(alloc.slice);
67         
68         return typeof(return)(null);
69     }
70 
71     @safe
72     bool owns(void* ptr) pure
73     {
74         auto curr = this._head;
75         while(curr !is null)
76         {
77             if(curr.alloc.owns(ptr))
78                 return true;
79             curr = curr.next;
80         }
81 
82         return false;
83     }
84 
85     void free(T)(ref NotNullSlice!(T, Tag) slice)
86     {
87         this.free(slice.ptr);
88         slice.slice = null;
89     }
90 
91     void free(T)(ref NotNullPtr!(T, Tag) ptr)
92     {
93         this.free(ptr.ptr);
94         ptr.ptr = null;
95     }
96     
97     void free(T)(T* ptr)
98     {
99         auto curr = this._head;
100         while(curr !is null)
101         {
102             if(curr.alloc.owns(ptr))
103             {
104                 curr.alloc.free!T(ptr);
105                 return;
106             }
107             curr = curr.next;
108         }
109 
110         assert(false, "Pointer does not belong to this allocator.");
111     }
112 
113     MaybeNullSlice!(T, Tag) realloc(T)(ref NotNullSlice!(T, Tag) slice, size_t toAmount)
114     {
115         // Step one: see if the owner bucket can reallocate first.
116         auto curr = this._head;
117         while(curr !is null)
118         {
119             if(curr.alloc.owns(slice.ptr))
120             {
121                 auto copy = slice.notNull!"block_alloc";
122                 auto result = curr.alloc.realloc!T(copy, toAmount);
123                 if(result !is null)
124                 {
125                     slice = copy;
126                     return typeof(return)(result);
127                 }
128                 break;
129             }
130             curr = curr.next;
131         }
132 
133         // Otherwise try to reallocate using any other allocator.
134         auto newBlock = this.alloc!T(toAmount);
135         if(newBlock is null)
136             return typeof(return)();
137 
138         if(toAmount < slice.length)
139             memcpy(slice.ptr, newBlock.ptr, toAmount * T.sizeof);
140         else
141             memcpy(slice.ptr, newBlock.ptr, slice.length * T.sizeof);
142 
143         curr.alloc.free(slice.ptr);
144         return typeof(return)(newBlock);
145     }
146 
147     private shared(Node*) newNode()
148     {
149         auto node = this._nodeAlloc.make!Node;
150         if(node is null)
151             return null;
152         emplaceCtor(node.alloc, this._pageFactor);
153 
154         if(this._head is null)
155         {
156             this._head = cast(shared)node;
157             this._tail = cast(shared)node;
158         }
159         else
160         {
161             this._tail.next = cast(shared)node;
162             this._tail = cast(shared)node;
163         }
164 
165         return cast(shared)node.ptr;
166     }
167 }
168 
169 shared struct BlockAllocator(size_t size)
170 {
171     @disable this(this);
172 
173     private
174     {
175         PageAllocation _alloc;
176         Lockable!BitKeeper _bits;
177         size_t _dataStart;
178         size_t _maxAlloc;
179     }
180 
181     static immutable string Tag = "block_alloc";
182 
183     @nogc nothrow:
184 
185     this(size_t pageFactor)
186     {
187         const pagesPerSize          = ((size / memoryPageSize) + 1) * pageFactor;
188         const bytesPerPagesPerSize  = pagesPerSize * size * memoryPageSize;
189         const bitsNeededForKeeper   = bytesPerPagesPerSize / size;
190         const pagesForKeeperBits    = (bitsNeededForKeeper / memoryPageSize) + 1;
191         const pagesTotal            = pagesForKeeperBits + pagesPerSize;
192 
193         this._alloc = cast(shared)PageAllocator.allocInPages(pagesTotal, false);
194         this._dataStart = pagesForKeeperBits * memoryPageSize;
195         this._maxAlloc  = pagesPerSize * memoryPageSize;
196         this._bits.access((ref bits)
197         {
198             bits = BitKeeper(cast(ubyte[])this._alloc.memory[0..this._dataStart], bitsNeededForKeeper);
199         });
200     }
201 
202     ~this()
203     {
204         if(this._alloc.memory.ptr)
205             PageAllocator.free(this._alloc);
206     }
207 
208     MaybeNullSlice!(T, Tag) alloc(T)(size_t amount)
209     {
210         const blocksForAmount = ((amount + BitKeeperSlice.sizeof) / size) + 1;
211         SimpleResult!BitKeeperSlice slice;
212         this._bits.access((ref bits)
213         {
214             slice = bits.alloc(blocksForAmount);
215         });
216         if(!slice.isValid)
217             return typeof(return)(null);
218 
219         auto bitSlice = slice.value;
220         const start = this._dataStart + (bitSlice.bitIndex * size);
221         const end   = start + BitKeeperSlice.sizeof + amount;
222         if(end > this._alloc.memory.length)
223             return typeof(return)(null);
224         auto ptr = cast(ubyte[])this._alloc.memory[start..end];
225         *(cast(BitKeeperSlice*)ptr) = bitSlice;
226         return typeof(return)(cast(T[])(cast(T*)ptr[BitKeeperSlice.sizeof..$].ptr)[0..amount]);
227     }
228 
229     @safe
230     bool owns(void* ptr) pure
231     {
232         return ptr >= this._alloc.memory.ptr && ptr <= &this._alloc.memory[$-1];
233     }
234 
235     void free(T)(ref NotNullSlice!(T, Tag) slice)
236     {
237         this.free(slice.ptr);
238         slice.slice = null;
239     }
240 
241     void free(T)(ref NotNullPtr!(T, Tag) ptr)
242     {
243         this.free(ptr.ptr);
244         ptr.ptr = null;
245     }
246 
247     void free(T)(T* ptr)
248     {
249         // I might have hit a codegen bug doing this a more logical way,
250         // Instead of subtracting 24 it adds like 200
251         auto ptr1 = cast(ulong)ptr;
252         ptr1 -= BitKeeperSlice.sizeof;
253         const ptr2 = cast(BitKeeperSlice*)ptr1;
254         const slice = *ptr2;
255         this._bits.access((ref bits)
256         {
257             bits.free(slice);
258         });
259     }
260 
261     MaybeNullSlice!(T, Tag) realloc(T)(ref NotNullSlice!(T, Tag) slice, size_t toAmount)
262     {
263         if(slice.length == toAmount)
264             return slice.maybeNull!Tag;
265 
266         // Naive implementation. We could do quite a bit better than this.
267         auto newBlock = this.alloc!T(toAmount);
268         if(newBlock is null)
269             return typeof(return)(null);
270 
271         if(toAmount < slice.length)
272             memcpy(slice.ptr, newBlock.ptr, toAmount * T.sizeof);
273         else
274             memcpy(slice.ptr, newBlock.ptr, slice.length * T.sizeof);
275 
276         this.free(slice);
277         return newBlock;
278     }
279 }
280 
281 @("BlockAllocator - BasicAllocatorTests")
282 unittest 
283 { 
284     import libd.memory.allocator._test;
285     BlockAllocator!1 alloc = BlockAllocator!1(1);
286     basicAllocatorTests!(BlockAllocator!1, () => &alloc)(); 
287 }
288 
289 @("BlockBucketAllocator - BasicAllocatorTests")
290 unittest 
291 { 
292     import libd.memory.allocator._test;
293     BlockBucketAllocator!1 alloc = BlockBucketAllocator!1(1);
294     basicAllocatorTests!(BlockBucketAllocator!1, () => &alloc)(); 
295 }