1 /** 2 Utility and ancillary artifacts of `stdx.allocator`. This module 3 shouldn't be used directly; its functionality will be migrated into more 4 appropriate parts of `std`. 5 6 Authors: $(HTTP erdani.com, Andrei Alexandrescu), Timon Gehr (`Ternary`) 7 */ 8 module stdx.allocator.common; 9 import mir.utility; 10 import std.traits; 11 12 /** 13 Returns the size in bytes of the state that needs to be allocated to hold an 14 object of type $(D T). $(D stateSize!T) is zero for $(D struct)s that are not 15 nested and have no nonstatic member variables. 16 */ 17 template stateSize(T) 18 { 19 static if (is(T == class) || is(T == interface)) 20 enum stateSize = __traits(classInstanceSize, T); 21 else static if (is(T == struct) || is(T == union)) 22 enum stateSize = Fields!T.length || isNested!T ? T.sizeof : 0; 23 else static if (is(T == void)) 24 enum size_t stateSize = 0; 25 else 26 enum stateSize = T.sizeof; 27 } 28 29 @safe @nogc nothrow pure 30 unittest 31 { 32 static assert(stateSize!void == 0); 33 struct A {} 34 static assert(stateSize!A == 0); 35 struct B { int x; } 36 static assert(stateSize!B == 4); 37 interface I1 {} 38 //static assert(stateSize!I1 == 2 * size_t.sizeof); 39 class C1 {} 40 static assert(stateSize!C1 == 3 * size_t.sizeof); 41 class C2 { char c; } 42 static assert(stateSize!C2 == 4 * size_t.sizeof); 43 static class C3 { char c; } 44 static assert(stateSize!C3 == 2 * size_t.sizeof + char.sizeof); 45 } 46 47 /** 48 Returns `true` if the `Allocator` has the alignment known at compile time; 49 otherwise it returns `false`. 50 */ 51 template hasStaticallyKnownAlignment(Allocator) 52 { 53 enum hasStaticallyKnownAlignment = __traits(compiles, 54 {enum x = Allocator.alignment;}); 55 } 56 57 /** 58 $(D chooseAtRuntime) is a compile-time constant of type $(D size_t) that several 59 parameterized structures in this module recognize to mean deferral to runtime of 60 the exact value. For example, $(D BitmappedBlock!(Allocator, 4096)) (described in 61 detail below) defines a block allocator with block size of 4096 bytes, whereas 62 $(D BitmappedBlock!(Allocator, chooseAtRuntime)) defines a block allocator that has a 63 field storing the block size, initialized by the user. 64 */ 65 enum chooseAtRuntime = size_t.max - 1; 66 67 /** 68 $(D unbounded) is a compile-time constant of type $(D size_t) that several 69 parameterized structures in this module recognize to mean "infinite" bounds for 70 the parameter. For example, $(D Freelist) (described in detail below) accepts a 71 $(D maxNodes) parameter limiting the number of freelist items. If $(D unbounded) 72 is passed for $(D maxNodes), then there is no limit and no checking for the 73 number of nodes. 74 */ 75 enum unbounded = size_t.max; 76 77 /** 78 The alignment that is guaranteed to accommodate any D object allocation on the 79 current platform. 80 */ 81 enum uint platformAlignment = mir.utility.max(double.alignof, real.alignof); 82 83 /** 84 The default good size allocation is deduced as $(D n) rounded up to the 85 allocator's alignment. 86 */ 87 size_t goodAllocSize(A)(auto ref A a, size_t n) 88 { 89 return n.roundUpToMultipleOf(a.alignment); 90 } 91 92 /** 93 Returns s rounded up to a multiple of base. 94 */ 95 @safe @nogc nothrow pure 96 size_t roundUpToMultipleOf()(size_t s, uint base) 97 { 98 assert(base); 99 auto rem = s % base; 100 return rem ? s + base - rem : s; 101 } 102 103 @safe @nogc nothrow pure 104 unittest 105 { 106 assert(10.roundUpToMultipleOf(11) == 11); 107 assert(11.roundUpToMultipleOf(11) == 11); 108 assert(12.roundUpToMultipleOf(11) == 22); 109 assert(118.roundUpToMultipleOf(11) == 121); 110 } 111 112 /** 113 Returns `n` rounded up to a multiple of alignment, which must be a power of 2. 114 */ 115 @safe @nogc nothrow pure 116 size_t roundUpToAlignment()(size_t n, uint alignment) 117 { 118 import stdx.allocator.internal : isPowerOf2; 119 assert(alignment.isPowerOf2); 120 immutable uint slack = cast(uint) n & (alignment - 1); 121 const result = slack 122 ? n + alignment - slack 123 : n; 124 assert(result >= n); 125 return result; 126 } 127 128 @safe @nogc nothrow pure 129 unittest 130 { 131 assert(10.roundUpToAlignment(4) == 12); 132 assert(11.roundUpToAlignment(2) == 12); 133 assert(12.roundUpToAlignment(8) == 16); 134 assert(118.roundUpToAlignment(64) == 128); 135 } 136 137 /** 138 Returns `n` rounded down to a multiple of alignment, which must be a power of 2. 139 */ 140 @safe @nogc nothrow pure 141 size_t roundDownToAlignment()(size_t n, uint alignment) 142 { 143 import stdx.allocator.internal : isPowerOf2; 144 assert(alignment.isPowerOf2); 145 return n & ~size_t(alignment - 1); 146 } 147 148 @safe @nogc nothrow pure 149 unittest 150 { 151 assert(10.roundDownToAlignment(4) == 8); 152 assert(11.roundDownToAlignment(2) == 10); 153 assert(12.roundDownToAlignment(8) == 8); 154 assert(63.roundDownToAlignment(64) == 0); 155 } 156 157 /** 158 Advances the beginning of `b` to start at alignment `a`. The resulting buffer 159 may therefore be shorter. Returns the adjusted buffer, or null if obtaining a 160 non-empty buffer is impossible. 161 */ 162 @nogc nothrow pure 163 void[] roundUpToAlignment()(void[] b, uint a) 164 { 165 auto e = b.ptr + b.length; 166 auto p = cast(void*) roundUpToAlignment(cast(size_t) b.ptr, a); 167 if (e <= p) return null; 168 return p[0 .. e - p]; 169 } 170 171 @nogc nothrow pure 172 @system unittest 173 { 174 void[] empty; 175 assert(roundUpToAlignment(empty, 4) == null); 176 char[128] buf; 177 // At least one pointer inside buf is 128-aligned 178 assert(roundUpToAlignment(buf, 128) !is null); 179 } 180 181 /** 182 Like `a / b` but rounds the result up, not down. 183 */ 184 @safe @nogc nothrow pure 185 size_t divideRoundUp()(size_t a, size_t b) 186 { 187 assert(b); 188 return (a + b - 1) / b; 189 } 190 191 /** 192 Returns `s` rounded up to a multiple of `base`. 193 */ 194 @nogc nothrow pure 195 void[] roundStartToMultipleOf()(void[] s, uint base) 196 { 197 assert(base); 198 auto p = cast(void*) roundUpToMultipleOf( 199 cast(size_t) s.ptr, base); 200 auto end = s.ptr + s.length; 201 return p[0 .. end - p]; 202 } 203 204 nothrow pure 205 @system unittest 206 { 207 void[] p; 208 assert(roundStartToMultipleOf(p, 16) is null); 209 p = new ulong[10]; 210 assert(roundStartToMultipleOf(p, 16) is p); 211 } 212 213 /** 214 Returns $(D s) rounded up to the nearest power of 2. 215 */ 216 @safe @nogc nothrow pure 217 size_t roundUpToPowerOf2()(size_t s) 218 { 219 import std.meta : AliasSeq; 220 assert(s <= (size_t.max >> 1) + 1); 221 --s; 222 static if (size_t.sizeof == 4) 223 alias Shifts = AliasSeq!(1, 2, 4, 8, 16); 224 else 225 alias Shifts = AliasSeq!(1, 2, 4, 8, 16, 32); 226 foreach (i; Shifts) 227 { 228 s |= s >> i; 229 } 230 return s + 1; 231 } 232 233 @safe @nogc nothrow pure 234 unittest 235 { 236 assert(0.roundUpToPowerOf2 == 0); 237 assert(1.roundUpToPowerOf2 == 1); 238 assert(2.roundUpToPowerOf2 == 2); 239 assert(3.roundUpToPowerOf2 == 4); 240 assert(7.roundUpToPowerOf2 == 8); 241 assert(8.roundUpToPowerOf2 == 8); 242 assert(10.roundUpToPowerOf2 == 16); 243 assert(11.roundUpToPowerOf2 == 16); 244 assert(12.roundUpToPowerOf2 == 16); 245 assert(118.roundUpToPowerOf2 == 128); 246 assert((size_t.max >> 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1); 247 assert(((size_t.max >> 1) + 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1); 248 } 249 250 /** 251 Returns the number of trailing zeros of $(D x). 252 */ 253 @safe @nogc nothrow pure 254 uint trailingZeros()(ulong x) 255 { 256 uint result; 257 while (result < 64 && !(x & (1UL << result))) 258 { 259 ++result; 260 } 261 return result; 262 } 263 264 @safe @nogc nothrow pure 265 unittest 266 { 267 assert(trailingZeros(0) == 64); 268 assert(trailingZeros(1) == 0); 269 assert(trailingZeros(2) == 1); 270 assert(trailingZeros(3) == 0); 271 assert(trailingZeros(4) == 2); 272 } 273 274 /** 275 Returns `true` if `ptr` is aligned at `alignment`. 276 */ 277 @nogc nothrow pure 278 bool alignedAt(T)(T* ptr, uint alignment) 279 { 280 return cast(size_t) ptr % alignment == 0; 281 } 282 283 /** 284 Returns the effective alignment of `ptr`, i.e. the largest power of two that is 285 a divisor of `ptr`. 286 */ 287 @nogc nothrow pure 288 uint effectiveAlignment()(void* ptr) 289 { 290 return 1U << trailingZeros(cast(size_t) ptr); 291 } 292 293 @nogc nothrow pure 294 @system unittest 295 { 296 int x; 297 assert(effectiveAlignment(&x) >= int.alignof); 298 } 299 300 /** 301 Aligns a pointer down to a specified alignment. The resulting pointer is less 302 than or equal to the given pointer. 303 */ 304 @nogc nothrow pure 305 void* alignDownTo()(void* ptr, uint alignment) 306 { 307 import stdx.allocator.internal : isPowerOf2; 308 assert(alignment.isPowerOf2); 309 return cast(void*) (cast(size_t) ptr & ~(alignment - 1UL)); 310 } 311 312 /** 313 Aligns a pointer up to a specified alignment. The resulting pointer is greater 314 than or equal to the given pointer. 315 */ 316 @nogc nothrow pure 317 void* alignUpTo()(void* ptr, uint alignment) 318 { 319 import stdx.allocator.internal : isPowerOf2; 320 assert(alignment.isPowerOf2); 321 immutable uint slack = cast(size_t) ptr & (alignment - 1U); 322 return slack ? ptr + alignment - slack : ptr; 323 } 324 325 @safe @nogc nothrow pure 326 bool isGoodStaticAlignment()(uint x) 327 { 328 import stdx.allocator.internal : isPowerOf2; 329 return x.isPowerOf2; 330 } 331 332 @safe @nogc nothrow pure 333 bool isGoodDynamicAlignment()(uint x) 334 { 335 import stdx.allocator.internal : isPowerOf2; 336 return x.isPowerOf2 && x >= (void*).sizeof; 337 } 338 339 /** 340 The default $(D reallocate) function first attempts to use $(D expand). If $(D 341 Allocator.expand) is not defined or returns $(D false), $(D reallocate) 342 allocates a new block of memory of appropriate size and copies data from the old 343 block to the new block. Finally, if $(D Allocator) defines $(D deallocate), $(D 344 reallocate) uses it to free the old memory block. 345 346 $(D reallocate) does not attempt to use $(D Allocator.reallocate) even if 347 defined. This is deliberate so allocators may use it internally within their own 348 implementation of $(D reallocate). 349 350 */ 351 bool reallocate(Allocator)(auto ref Allocator a, ref void[] b, size_t s) 352 { 353 if (b.length == s) return true; 354 static if (__traits(hasMember, Allocator, "expand")) 355 { 356 if (b.length <= s && a.expand(b, s - b.length)) return true; 357 } 358 auto newB = a.allocate(s); 359 if (newB.length != s) return false; 360 if (newB.length <= b.length) newB[] = b[0 .. newB.length]; 361 else newB[0 .. b.length] = b[]; 362 static if (__traits(hasMember, Allocator, "deallocate")) 363 a.deallocate(b); 364 b = newB; 365 return true; 366 } 367 368 /** 369 370 The default $(D alignedReallocate) function first attempts to use $(D expand). 371 If $(D Allocator.expand) is not defined or returns $(D false), $(D 372 alignedReallocate) allocates a new block of memory of appropriate size and 373 copies data from the old block to the new block. Finally, if $(D Allocator) 374 defines $(D deallocate), $(D alignedReallocate) uses it to free the old memory 375 block. 376 377 $(D alignedReallocate) does not attempt to use $(D Allocator.reallocate) even if 378 defined. This is deliberate so allocators may use it internally within their own 379 implementation of $(D reallocate). 380 381 */ 382 bool alignedReallocate(Allocator)(auto ref Allocator alloc, 383 ref void[] b, size_t s, uint a) 384 { 385 static if (__traits(hasMember, Allocator, "expand")) 386 { 387 if (b.length <= s && b.ptr.alignedAt(a) 388 && alloc.expand(b, s - b.length)) return true; 389 } 390 else 391 { 392 if (b.length == s) return true; 393 } 394 auto newB = alloc.alignedAllocate(s, a); 395 if (newB.length <= b.length) newB[] = b[0 .. newB.length]; 396 else newB[0 .. b.length] = b[]; 397 static if (__traits(hasMember, Allocator, "deallocate")) 398 alloc.deallocate(b); 399 b = newB; 400 return true; 401 } 402 403 /** 404 Forwards each of the methods in `funs` (if defined) to `member`. 405 */ 406 enum forwardToMember = (string member, string[] funs...) 407 { 408 string result = " import std.traits : Parameters;\n"; 409 foreach (fun; funs) 410 { 411 result ~= " 412 static if (__traits(hasMember, typeof("~member~"), `"~fun~"`)) 413 { 414 static if (__traits(isTemplate, "~member~"."~fun~")) 415 auto ref "~fun~"(Parameters!(typeof("~member~"."~fun~"!())) args) 416 { 417 return "~member~"."~fun~"(args); 418 } 419 else 420 auto ref "~fun~"(Parameters!(typeof("~member~"."~fun~")) args) 421 { 422 return "~member~"."~fun~"(args); 423 } 424 }\n"; 425 } 426 return result; 427 }; 428 429 version(unittest) 430 { 431 import stdx.allocator : IAllocator, ISharedAllocator; 432 433 package void testAllocator(alias make)() 434 { 435 import std.conv : text; 436 import stdx.allocator.internal : isPowerOf2; 437 import std.stdio : writeln, stderr; 438 import stdx.allocator.internal : Ternary; 439 alias A = typeof(make()); 440 scope(failure) stderr.writeln("testAllocator failed for ", A.stringof); 441 442 auto a = make(); 443 444 // Test alignment 445 static assert(A.alignment.isPowerOf2); 446 447 // Test goodAllocSize 448 assert(a.goodAllocSize(1) >= A.alignment, 449 text(a.goodAllocSize(1), " < ", A.alignment)); 450 assert(a.goodAllocSize(11) >= 11.roundUpToMultipleOf(A.alignment)); 451 assert(a.goodAllocSize(111) >= 111.roundUpToMultipleOf(A.alignment)); 452 453 // Test allocate 454 assert(a.allocate(0) is null); 455 456 auto b1 = a.allocate(1); 457 assert(b1.length == 1); 458 auto b2 = a.allocate(2); 459 assert(b2.length == 2); 460 assert(b2.ptr + b2.length <= b1.ptr || b1.ptr + b1.length <= b2.ptr); 461 462 // Test alignedAllocate 463 static if (__traits(hasMember, A, "alignedAllocate")) 464 {{ 465 auto b3 = a.alignedAllocate(1, 256); 466 assert(b3.length <= 1); 467 assert(b3.ptr.alignedAt(256)); 468 assert(a.alignedReallocate(b3, 2, 512)); 469 assert(b3.ptr.alignedAt(512)); 470 static if (__traits(hasMember, A, "alignedDeallocate")) 471 { 472 a.alignedDeallocate(b3); 473 } 474 }} 475 else 476 { 477 static assert(!__traits(hasMember, A, "alignedDeallocate")); 478 // This seems to be a bug in the compiler: 479 //static assert(!__traits(hasMember, A, "alignedReallocate"), A.stringof); 480 } 481 482 static if (__traits(hasMember, A, "allocateAll")) 483 {{ 484 auto aa = make(); 485 if (aa.allocateAll().ptr) 486 { 487 // Can't get any more memory 488 assert(!aa.allocate(1).ptr); 489 } 490 auto ab = make(); 491 const b4 = ab.allocateAll(); 492 assert(b4.length); 493 // Can't get any more memory 494 assert(!ab.allocate(1).ptr); 495 }} 496 497 static if (__traits(hasMember, A, "expand")) 498 {{ 499 assert(a.expand(b1, 0)); 500 auto len = b1.length; 501 if (a.expand(b1, 102)) 502 { 503 assert(b1.length == len + 102, text(b1.length, " != ", len + 102)); 504 } 505 auto aa = make(); 506 void[] b5 = null; 507 assert(aa.expand(b5, 0)); 508 assert(b5 is null); 509 assert(!aa.expand(b5, 1)); 510 assert(b5.length == 0); 511 }} 512 513 void[] b6 = null; 514 assert(a.reallocate(b6, 0)); 515 assert(b6.length == 0); 516 assert(a.reallocate(b6, 1)); 517 assert(b6.length == 1, text(b6.length)); 518 assert(a.reallocate(b6, 2)); 519 assert(b6.length == 2); 520 521 // Test owns 522 static if (__traits(hasMember, A, "owns")) 523 {{ 524 assert(a.owns(null) == Ternary.no); 525 assert(a.owns(b1) == Ternary.yes); 526 assert(a.owns(b2) == Ternary.yes); 527 assert(a.owns(b6) == Ternary.yes); 528 }} 529 530 static if (__traits(hasMember, A, "resolveInternalPointer")) 531 {{ 532 void[] p; 533 assert(a.resolveInternalPointer(null, p) == Ternary.no); 534 Ternary r = a.resolveInternalPointer(b1.ptr, p); 535 assert(p.ptr is b1.ptr && p.length >= b1.length); 536 r = a.resolveInternalPointer(b1.ptr + b1.length / 2, p); 537 assert(p.ptr is b1.ptr && p.length >= b1.length); 538 r = a.resolveInternalPointer(b2.ptr, p); 539 assert(p.ptr is b2.ptr && p.length >= b2.length); 540 r = a.resolveInternalPointer(b2.ptr + b2.length / 2, p); 541 assert(p.ptr is b2.ptr && p.length >= b2.length); 542 r = a.resolveInternalPointer(b6.ptr, p); 543 assert(p.ptr is b6.ptr && p.length >= b6.length); 544 r = a.resolveInternalPointer(b6.ptr + b6.length / 2, p); 545 assert(p.ptr is b6.ptr && p.length >= b6.length); 546 static int[10] b7 = [ 1, 2, 3 ]; 547 assert(a.resolveInternalPointer(b7.ptr, p) == Ternary.no); 548 assert(a.resolveInternalPointer(b7.ptr + b7.length / 2, p) == Ternary.no); 549 assert(a.resolveInternalPointer(b7.ptr + b7.length, p) == Ternary.no); 550 int[3] b8 = [ 1, 2, 3 ]; 551 assert(a.resolveInternalPointer(b8.ptr, p) == Ternary.no); 552 assert(a.resolveInternalPointer(b8.ptr + b8.length / 2, p) == Ternary.no); 553 assert(a.resolveInternalPointer(b8.ptr + b8.length, p) == Ternary.no); 554 }} 555 } 556 557 package void testAllocatorObject(AllocInterface)(AllocInterface a) 558 if (is(AllocInterface : IAllocator) 559 || is (AllocInterface : shared ISharedAllocator)) 560 { 561 import std.conv : text; 562 import stdx.allocator.internal : isPowerOf2; 563 import std.stdio : writeln, stderr; 564 import stdx.allocator.internal : Ternary; 565 scope(failure) stderr.writeln("testAllocatorObject failed for ", 566 AllocInterface.stringof); 567 568 assert(a); 569 570 // Test alignment 571 assert(a.alignment.isPowerOf2); 572 573 // Test goodAllocSize 574 assert(a.goodAllocSize(1) >= a.alignment, 575 text(a.goodAllocSize(1), " < ", a.alignment)); 576 assert(a.goodAllocSize(11) >= 11.roundUpToMultipleOf(a.alignment)); 577 assert(a.goodAllocSize(111) >= 111.roundUpToMultipleOf(a.alignment)); 578 579 // Test empty 580 assert(a.empty != Ternary.no); 581 582 // Test allocate 583 assert(a.allocate(0) is null); 584 585 auto b1 = a.allocate(1); 586 assert(b1.length == 1); 587 auto b2 = a.allocate(2); 588 assert(b2.length == 2); 589 assert(b2.ptr + b2.length <= b1.ptr || b1.ptr + b1.length <= b2.ptr); 590 591 // Test alignedAllocate 592 { 593 // If not implemented it will return null, so those should pass 594 auto b3 = a.alignedAllocate(1, 256); 595 assert(b3.length <= 1); 596 assert(b3.ptr.alignedAt(256)); 597 if (a.alignedReallocate(b3, 1, 256)) 598 { 599 // If it is false, then the wrapped allocator did not implement 600 // this 601 assert(a.alignedReallocate(b3, 2, 512)); 602 assert(b3.ptr.alignedAt(512)); 603 } 604 } 605 606 // Test allocateAll 607 { 608 auto aa = a.allocateAll(); 609 if (aa.ptr) 610 { 611 // Can't get any more memory 612 assert(!a.allocate(1).ptr); 613 a.deallocate(aa); 614 } 615 const b4 = a.allocateAll(); 616 if (b4.ptr) 617 { 618 // Can't get any more memory 619 assert(!a.allocate(1).ptr); 620 } 621 } 622 623 // Test expand 624 { 625 assert(a.expand(b1, 0)); 626 auto len = b1.length; 627 if (a.expand(b1, 102)) 628 { 629 assert(b1.length == len + 102, text(b1.length, " != ", len + 102)); 630 } 631 } 632 633 void[] b6 = null; 634 assert(a.reallocate(b6, 0)); 635 assert(b6.length == 0); 636 assert(a.reallocate(b6, 1)); 637 assert(b6.length == 1, text(b6.length)); 638 assert(a.reallocate(b6, 2)); 639 assert(b6.length == 2); 640 641 // Test owns 642 { 643 if (a.owns(null) != Ternary.unknown) 644 { 645 assert(a.owns(null) == Ternary.no); 646 assert(a.owns(b1) == Ternary.yes); 647 assert(a.owns(b2) == Ternary.yes); 648 assert(a.owns(b6) == Ternary.yes); 649 } 650 } 651 652 // Test resolveInternalPointer 653 { 654 void[] p; 655 if (a.resolveInternalPointer(null, p) != Ternary.unknown) 656 { 657 assert(a.resolveInternalPointer(null, p) == Ternary.no); 658 Ternary r = a.resolveInternalPointer(b1.ptr, p); 659 assert(p.ptr is b1.ptr && p.length >= b1.length); 660 r = a.resolveInternalPointer(b1.ptr + b1.length / 2, p); 661 assert(p.ptr is b1.ptr && p.length >= b1.length); 662 r = a.resolveInternalPointer(b2.ptr, p); 663 assert(p.ptr is b2.ptr && p.length >= b2.length); 664 r = a.resolveInternalPointer(b2.ptr + b2.length / 2, p); 665 assert(p.ptr is b2.ptr && p.length >= b2.length); 666 r = a.resolveInternalPointer(b6.ptr, p); 667 assert(p.ptr is b6.ptr && p.length >= b6.length); 668 r = a.resolveInternalPointer(b6.ptr + b6.length / 2, p); 669 assert(p.ptr is b6.ptr && p.length >= b6.length); 670 static int[10] b7 = [ 1, 2, 3 ]; 671 assert(a.resolveInternalPointer(b7.ptr, p) == Ternary.no); 672 assert(a.resolveInternalPointer(b7.ptr + b7.length / 2, p) == Ternary.no); 673 assert(a.resolveInternalPointer(b7.ptr + b7.length, p) == Ternary.no); 674 int[3] b8 = [ 1, 2, 3 ]; 675 assert(a.resolveInternalPointer(b8.ptr, p) == Ternary.no); 676 assert(a.resolveInternalPointer(b8.ptr + b8.length / 2, p) == Ternary.no); 677 assert(a.resolveInternalPointer(b8.ptr + b8.length, p) == Ternary.no); 678 } 679 } 680 681 // Test deallocateAll 682 { 683 if (a.deallocateAll()) 684 { 685 if (a.empty != Ternary.unknown) 686 { 687 assert(a.empty == Ternary.yes); 688 } 689 } 690 } 691 } 692 }