1 module memutils.scoped; 2 3 import core.thread : Fiber; 4 import memutils.constants; 5 import memutils.allocators; 6 import memutils.pool; 7 import memutils.utils; 8 import memutils.vector; 9 import memutils.refcounted; 10 import memutils.unique; 11 import memutils.hashmap; 12 import memutils.freelist; 13 import memutils.memory; 14 import memutils.helpers; 15 import std.traits : hasElaborateDestructor, isArray; 16 import std.algorithm : min; 17 import std.exception; 18 import core.exception; 19 import core.stdc.string : memcpy; 20 21 alias ScopedPool = RefCounted!ScopedPoolImpl; 22 23 final class ScopedPoolImpl { 24 // TODO: Use a name for debugging? 25 26 int id; 27 /// Initializes a scoped pool with max_mem 28 /// max_mem doesn't do anything at the moment 29 this(size_t max_mem = 0) { 30 PoolStack.push(); 31 id = PoolStack.top.id; 32 //logDebug("ScopedPool.this id: ", id); 33 } 34 35 ~this() { 36 //logDebug("ScopedPool.~this id: ", id, " PoolStack.top.id: ", PoolStack.top.id); 37 debug if(id != PoolStack.top.id) { 38 //logDebug("Unfreezing..."); 39 unfreeze(); 40 } 41 PoolStack.pop(); 42 } 43 44 /// Use only if ScopedPool is the highest on stack. 45 void freeze() { 46 enforce(id == PoolStack.top.id); 47 enforce(PoolStack.freeze(1) == 1, "Failed to freeze pool"); 48 } 49 50 void unfreeze() { 51 enforce(PoolStack.unfreeze(1) == 1, "Failed to unfreeze pool"); 52 enforce(id == PoolStack.top.id); 53 } 54 } 55 56 T alloc(T, ARGS...)(auto ref ARGS args) 57 if (is(T == class) || is(T == interface) || __traits(isAbstractClass, T)) 58 { 59 T ret; 60 61 if (!PoolStack.empty) { 62 ret = ObjectAllocator!(T, PoolStack).alloc(args); 63 64 // Add destructor to pool 65 static if (hasElaborateDestructor!T || __traits(hasMember, T, "__xdtor") ) 66 PoolStack.top().onDestroy(&ret.__xdtor); 67 } 68 else { 69 ret = new T(args); 70 } 71 72 return ret; 73 } 74 75 T* alloc(T, ARGS...)(auto ref ARGS args) 76 if (!(is(T == class) || is(T == interface) || __traits(isAbstractClass, T))) 77 { 78 T* ret; 79 80 if (!PoolStack.empty) { 81 ret = ObjectAllocator!(T, PoolStack).alloc(args); 82 83 // Add destructor to pool 84 static if (hasElaborateDestructor!T || __traits(hasMember, T, "__xdtor") ) 85 PoolStack.top.onDestroy(&ret.__xdtor); 86 87 } 88 else { 89 ret = new T(args); 90 } 91 92 return ret; 93 } 94 95 /// arrays 96 auto alloc(T)(size_t n) 97 if (isArray!T) 98 { 99 import std.range : ElementType; 100 101 T ret; 102 if (!PoolStack.empty) { 103 ret = allocArray!(ElementType!T, PoolStack)(n); 104 registerPoolArray(ret); 105 } 106 else { 107 ret = new T(n); 108 } 109 return ret; 110 } 111 112 auto realloc(T)(ref T arr, size_t n) 113 if (isArray!T) 114 { 115 import std.range : ElementType; 116 T ret; 117 if (!PoolStack.empty) { 118 scope(exit) arr = null; 119 ret = reallocArray!(ElementType!T, PoolStack)(arr, n); 120 reregisterPoolArray(arr, ret); 121 } 122 else { 123 ret.length = n; 124 ret[0 .. arr.length] = arr[]; 125 arr = null; 126 } 127 return ret; 128 } 129 130 auto copy(T)(auto ref T arr) 131 if (isArray!T) 132 { 133 import std.range : ElementType; 134 135 alias ElType = UnConst!(typeof(arr[0])); 136 137 ElType[] ret; 138 if (!PoolStack.empty) { 139 ret = allocArray!(ElType, PoolStack)(arr.length); 140 memcpy(cast(void*)ret.ptr, cast(void*)arr.ptr, arr.length * ElType.sizeof); 141 } else { 142 ret.length = arr.length; 143 ret[] = arr[]; 144 } 145 146 return cast(T)ret; 147 } 148 149 struct PoolStack { 150 static: 151 @property bool empty() { return m_tstack.empty && m_fstack.empty; } 152 153 /// returns the most recent unfrozen pool, null if none available 154 @property ManagedPool top() { 155 assert(!m_fstack.empty || !m_tstack.empty, "No Pool found on stack"); 156 assert((Fiber.getThis() && !m_fstack.empty) || !m_tstack.empty, "PoolStack.top() called with empty PoolStack"); 157 if (Fiber.getThis() && !m_fstack.empty) { 158 return m_fstack.top; 159 } 160 return m_tstack.top; 161 } 162 163 /// creates a new pool as the fiber stack top or the thread stack top 164 void push() { 165 //logTrace("Pushing PoolStack"); 166 if (Fiber.getThis()) 167 return m_fstack.push(); 168 m_tstack.push(); 169 //logTrace("Pushed ThreadStack"); 170 } 171 172 /// destroy the most recent pool and free all its resources, calling destructors 173 /// if you're in a fiber, search for stack top in the fiber stack and destroy it. 174 /// otherwise, search in the thread stack and destroy it. 175 void pop() { 176 //logTrace("Pop PoolStack"); 177 if (Fiber.getThis() && (!m_fstack.empty || !m_ffreezer.empty)) 178 { 179 //logTrace("Pop FiberStack"); 180 assert(!m_fstack.empty, "pop() called on empty FiberPoolStack"); 181 return m_fstack.pop(); 182 } 183 assert(!m_tstack.empty, "pop() called on empty ThreadPoolStack"); 184 return m_tstack.pop(); 185 //logTrace("Destroyign ", ret.back.id); 186 187 } 188 189 void disable() { 190 freeze(m_tstack.length + m_fstack.length); 191 } 192 193 void enable() { 194 unfreeze(m_ffreezer.length + m_tfreezer.length); 195 } 196 197 // returns number of pools frozen 198 size_t freeze(size_t n = 1) { 199 auto minsz = min(m_fstack.length, n); 200 201 if (minsz > 0) { 202 auto frozen = m_fstack.freeze(minsz); 203 m_ffreezer.push(frozen); 204 } 205 206 if (minsz < n) { 207 auto tsz = min(m_tstack.length, n - minsz); 208 if (tsz > 0) { 209 auto frozen = m_tstack.freeze(tsz); 210 m_tfreezer.push(frozen); 211 } 212 return tsz + minsz; 213 } 214 return minsz; 215 } 216 217 // returns number of pools unfrozen 218 size_t unfreeze(size_t n = 1) { 219 auto minsz = min(m_ffreezer.length, n); 220 221 if (minsz > 0) m_fstack.unfreeze(m_ffreezer.pop(minsz)); 222 223 if (minsz < n) { 224 auto tsz = min(m_tfreezer.length, n - minsz); 225 if (tsz > 0) m_tstack.unfreeze(m_tfreezer.pop(tsz)); 226 return tsz + minsz; 227 } 228 return minsz; 229 } 230 231 ~this() { 232 destroy(m_fstack); 233 destroy(m_tstack); 234 } 235 236 private static: 237 // active 238 ThreadPoolStack m_tstack; 239 FiberPoolStack m_fstack; 240 241 // frozen 242 ThreadPoolFreezer m_tfreezer; 243 FiberPoolFreezer m_ffreezer; 244 245 } 246 247 package: 248 249 alias Pool = PoolAllocator!(AutoFreeListAllocator!(MallocAllocator)); 250 alias ManagedPool = RefCounted!(Pool); 251 252 /// User utility for allocating on lower level pools 253 struct ThreadPoolFreezer 254 { 255 @disable this(this); 256 @property size_t length() const { return m_pools.length; } 257 @property bool empty() const { return length == 0; } 258 259 void push(Array!(ManagedPool, Malloc) pools) 260 { 261 //logTrace("Push Thread Freezer of ", m_pools.length); 262 // insert sorted 263 foreach(ref item; pools[]) { 264 bool found; 265 foreach (size_t i, ref el; m_pools[]) { 266 if (item.id < el.id) { 267 m_pools.insertBefore(i, item); 268 found = true; 269 break; 270 } 271 } 272 if (!found) m_pools ~= item; 273 } 274 //logTrace("Pushed Thread Freezer now ", m_pools.length); 275 } 276 277 Array!(ManagedPool, Malloc) pop(size_t n) { 278 assert(!empty); 279 //logTrace("Pop Thread Freezer of ", m_pools.length, " id ", m_pools.back.id); 280 // already sorted 281 auto pools = Array!(ManagedPool, Malloc)( m_pools[$-n .. $] ); 282 283 284 m_pools.length = (m_pools.length - 1); 285 //logTrace("Popped Thread Freezer returning ", pools.length, " expecting ", n); 286 //logTrace("Returning ID ", pools.back.id); 287 return pools; 288 } 289 290 package: 291 Vector!(ManagedPool, Malloc) m_pools; 292 } 293 294 /// User utility for allocating on lower level pools 295 struct FiberPoolFreezer 296 { 297 @disable this(this); 298 @property size_t fibers() const { return m_pools.length; } 299 300 @property size_t length() const { 301 Fiber f = Fiber.getThis(); 302 if (auto ptr = (f in m_pools)) { 303 return (*ptr).length; 304 } 305 return 0; 306 } 307 308 @property bool empty() const { 309 return length == 0; 310 } 311 312 void push(Array!(ManagedPool, Malloc) pools) 313 { 314 logDebug("Push Fiber Freezer of ", length); 315 Fiber f = Fiber.getThis(); 316 assert(f !is null); 317 if (auto ptr = (f in m_pools)) { 318 auto arr = *ptr; 319 320 // insert sorted 321 foreach(ref item; pools[]) { 322 bool found; 323 foreach (size_t i, ref el; arr[]) { 324 if (item.id < el.id) { 325 arr.insertBefore(i, item); 326 found = true; 327 break; 328 } 329 } 330 if (!found) arr ~= item; 331 } 332 //logTrace("Pushed Fiber Freezer of ", length); 333 return; 334 } 335 //else 336 m_pools[f] = pools.cloneToRef; 337 //logTrace("Pushed Fiber Freezer of ", length); 338 } 339 340 Array!(ManagedPool, Malloc) pop(size_t n) { 341 logDebug("Pop Fiber Freezer of ", length); 342 assert(!empty); 343 344 Fiber f = Fiber.getThis(); 345 auto arr = m_pools[f]; 346 347 if (arr.empty) { 348 m_pools.remove(f); 349 return Array!(ManagedPool, Malloc)(); 350 } 351 352 // already sorted 353 auto pools = Array!(ManagedPool, Malloc)( arr[$-n .. $] ); 354 arr.length = (arr.length - n); 355 //logTrace("Popped Fiber Freezer of ", length); 356 return pools; 357 } 358 359 private: 360 361 HashMap!(Fiber, Array!(ManagedPool, Malloc), Malloc) m_pools; 362 } 363 struct ThreadPoolStack 364 { 365 @disable this(this); 366 @property size_t length() const { return m_pools.length; } 367 @property bool empty() const { return length == 0; } 368 size_t opDollar() const { return length; } 369 @property bool hasTop() { return length > 0 && cnt-1 == top.id; } 370 371 372 ManagedPool opIndex(size_t n) { 373 //logTrace("OpIndex[", n, "] in Thread Pool of ", length, " top: ", cnt, " id: ", m_pools[n].id); 374 return m_pools[n]; 375 } 376 377 @property ManagedPool top() 378 { 379 //logTrace("Front Thread Pool of ", length); 380 if (empty) { 381 //logTrace("Empty"); 382 return ManagedPool(); 383 } 384 return m_pools.back; 385 } 386 387 void pop() 388 { 389 assert(!empty); 390 //logTrace("Pop Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 391 auto pool = m_pools.back; 392 assert(pool.id == cnt-1); 393 --cnt; 394 m_pools.removeBack(); 395 //if (!empty) logTrace("Popped Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 396 } 397 398 void push() { 399 //if (!m_pools.empty) logTrace("Push Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 400 //else logTrace("Push Thread Pool of ", length, " top: ", cnt); 401 ManagedPool pool = ManagedPool(); 402 pool.id = cnt++; 403 m_pools.pushBack(pool); 404 //logTrace("Pushed Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 405 } 406 407 Array!(ManagedPool, Malloc) freeze(size_t n) { 408 assert(!empty); 409 //if (!m_pools.empty) logTrace("Freeze ", n, " in Thread Pool of ", length, " top: ", cnt); 410 //else logTrace("Freeze ", n, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 411 assert(n <= length); 412 auto ret = Array!(ManagedPool, Malloc)(n); 413 ret[] = m_pools[$-n .. $]; 414 m_pools.removeBack(n); 415 //logTrace("Returning ", ret.length); 416 //if (!empty) logTrace("Freezeed ", n, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 417 return ret; 418 } 419 420 void unfreeze(Array!(ManagedPool, Malloc) pools) { 421 //logTrace("Unfreeze ", pools.length, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 422 // insert sorted 423 foreach(ref item; pools[]) { 424 bool found; 425 foreach (size_t i, ref el; m_pools[]) { 426 if (item.id < el.id) { 427 m_pools.insertBefore(i, item); 428 found = true; 429 break; 430 } 431 } 432 if (!found) m_pools ~= item; 433 } 434 //logTrace("Unfreezed ", pools.length, " in Thread Pool of ", length, " top: ", cnt, " back id: ", m_pools.back.id); 435 } 436 437 package: 438 int cnt; 439 Vector!(ManagedPool, Malloc) m_pools; 440 } 441 442 struct FiberPoolStack 443 { 444 @disable this(this); 445 @property size_t fibers() const { return m_pools.length; } 446 447 @property size_t length() const { 448 Fiber f = Fiber.getThis(); 449 if (auto ptr = (f in m_pools)) { 450 return (*ptr).length; 451 } 452 return 0; 453 } 454 455 @property bool hasTop() { return length > 0 && cnt-1 == top.id; } 456 457 @property bool empty() const { 458 return length == 0; 459 } 460 461 size_t opDollar() const { return length; } 462 463 ManagedPool opIndex(size_t n) { 464 assert(!empty); 465 Fiber f = Fiber.getThis(); 466 //logTrace("OpIndex[", n, "] in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f][n].id); 467 return m_pools[f][n]; 468 469 } 470 471 @property ManagedPool top() 472 { 473 assert(!empty); 474 Fiber f = Fiber.getThis(); 475 if (auto ptr = (f in m_pools)) { 476 //logTrace("top in Fiber Pool of ", length, " top: ", cnt, " len: ", (*ptr).back().id); 477 return (*ptr).back(); 478 } 479 return ManagedPool(); 480 481 } 482 483 // returns next item ie. top() 484 void pop() { 485 assert(!empty); 486 487 Fiber f = Fiber.getThis(); 488 logDebug("pop in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id); 489 auto arr = m_pools[f]; 490 assert(arr.back.id == cnt-1); 491 arr.removeBack(); 492 cnt--; 493 if (arr.empty) 494 m_pools.remove(f); 495 //if (!empty) logTrace("popped in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id); 496 } 497 498 void push() 499 { 500 ManagedPool pool = ManagedPool(); 501 pool.id = cnt++; 502 Fiber f = Fiber.getThis(); 503 if (!empty) logDebug("Push in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools.get(f).back.id); 504 assert(f !is null); 505 if (auto ptr = (f in m_pools)) { 506 *ptr ~= pool; 507 //logTrace("Pushed in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id); 508 return; 509 } 510 //else 511 m_pools[f] = Array!(ManagedPool, Malloc)(); 512 m_pools[f] ~= pool; 513 //logDebug("Pushed in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id); 514 } 515 516 // returns the frozen items 517 Array!(ManagedPool, Malloc) freeze(size_t n) { 518 assert(n <= length); 519 Fiber f = Fiber.getThis(); 520 logDebug("Freeze in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id); 521 auto arr = m_pools[f]; 522 logDebug("Got array"); 523 auto ret = Array!(ManagedPool, Malloc)(n); 524 ret[] = arr[$-n .. $]; 525 arr.removeBack(n); 526 logDebug("Frozen in Fiber Pool of ", length, " top: ", cnt); 527 return ret; 528 } 529 530 531 void unfreeze(Array!(ManagedPool, Malloc) items) 532 { 533 Fiber f = Fiber.getThis(); 534 assert(f !is null); 535 logDebug("Unfreeze in Fiber Pool of ", length, " top: ", cnt); 536 if (auto ptr = (f in m_pools)) { 537 auto arr = *ptr; 538 // insert sorted 539 foreach(ref item; items[]) { 540 bool found; 541 foreach (size_t i, ref el; arr[]) { 542 if (item.id < el.id) { 543 arr.insertBefore(i, item); 544 found = true; 545 break; 546 } 547 } 548 if (!found) arr ~= item; 549 } 550 logDebug("Unfrozen in Fiber Pool of ", length, " top: ", cnt, " id: ", m_pools[f].back.id); 551 return; 552 } 553 assert(false); 554 } 555 package: 556 int cnt; 557 HashMap!(Fiber, Array!(ManagedPool, Malloc), Malloc) m_pools; 558 } 559 560 561 private void registerPoolArray(T)(ref T arr) { 562 import std.range : ElementType; 563 // Add destructors to fiber pool 564 static if (is(T == struct) && (hasElaborateDestructor!(ElementType!T) || __traits(hasMember, ElementType!T, "__xdtor") )) { 565 foreach (ref el; arr) 566 PoolStack.top.onDestroy(&el.__xdtor); 567 } 568 } 569 570 private void reregisterPoolArray(T)(ref T arr, ref T arr2) { 571 import std.range : ElementType; 572 // Add destructors to fiber pool 573 static if (is(T == struct) && (hasElaborateDestructor!(ElementType!T) || __traits(hasMember, ElementType!T, "__xdtor") )) { 574 if (arr.ptr is arr2.ptr && arr2.length > arr.length) { 575 foreach (ref el; arr2[arr.length - 1 .. $]) 576 PoolStack.top.onDestroy(&el.__xdtor); 577 } 578 else { 579 PoolStack.top.removeArrayDtors(&arr.back.__xdtor, arr.length); 580 registerPoolArray(arr2); 581 } 582 } 583 }