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 }