1 /** 2 3 Various @nogc alternatives. This file includes parts of `std.process`, `std.random`, `std.uuid`. 4 5 Authors: 6 $(HTTP guillaumepiolat.fr, Guillaume Piolat) 7 $(LINK2 https://github.com/kyllingstad, Lars Tandle Kyllingstad), 8 $(LINK2 https://github.com/schveiguy, Steven Schveighoffer), 9 $(HTTP thecybershadow.net, Vladimir Panteleev) 10 11 Copyright: 12 Copyright (c) 2016, Guillaume Piolat. 13 Copyright (c) 2013, Lars Tandle Kyllingstad (std.process). 14 Copyright (c) 2013, Steven Schveighoffer (std.process). 15 Copyright (c) 2013, Vladimir Panteleev (std.process). 16 17 License: 18 $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 19 */ 20 module dplug.core.nogc; 21 22 import core.stdc.stdarg; 23 import core.stdc.string: strdup, memcpy, strlen; 24 import core.stdc.stdlib: malloc, free, getenv; 25 import core.memory: GC; 26 import core.exception: onOutOfMemoryErrorNoGC; 27 28 import std.conv: emplace; 29 import std.traits; 30 31 import dplug.core.vec: Vec; 32 33 // This module provides many utilities to deal with @nogc nothrow, in a situation with the runtime disabled. 34 35 // 36 // Faking @nogc 37 // 38 39 version = useTimSort; 40 41 auto assumeNoGC(T) (T t) 42 { 43 static if (isFunctionPointer!T || isDelegate!T) 44 { 45 enum attrs = functionAttributes!T | FunctionAttribute.nogc; 46 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 47 } 48 else 49 static assert(false); 50 } 51 52 auto assumeNothrowNoGC(T) (T t) 53 { 54 static if (isFunctionPointer!T || isDelegate!T) 55 { 56 enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_; 57 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 58 } 59 else 60 static assert(false); 61 } 62 63 unittest 64 { 65 void funcThatDoesGC() 66 { 67 int a = 4; 68 int[] b = [a, a, a]; 69 } 70 71 void anotherFunction() nothrow @nogc 72 { 73 assumeNothrowNoGC( (){ funcThatDoesGC(); } )(); 74 } 75 76 void aThirdFunction() @nogc 77 { 78 assumeNoGC( () { funcThatDoesGC(); } )(); 79 } 80 } 81 82 83 // 84 // Optimistic .destroy, which is @nogc nothrow by breaking the type-system 85 // 86 87 // for classes 88 void destroyNoGC(T)(T x) nothrow @nogc if (is(T == class) || is(T == interface)) 89 { 90 assumeNothrowNoGC( 91 (T x) 92 { 93 return destroy(x); 94 })(x); 95 } 96 97 // for struct 98 void destroyNoGC(T)(ref T obj) nothrow @nogc if (is(T == struct)) 99 { 100 assumeNothrowNoGC( 101 (ref T x) 102 { 103 return destroy(x); 104 })(obj); 105 } 106 /* 107 void destroyNoGC(T : U[n], U, size_t n)(ref T obj) nothrow @nogc 108 { 109 assumeNothrowNoGC( 110 (T x) 111 { 112 return destroy(x); 113 })(obj); 114 }*/ 115 116 void destroyNoGC(T)(ref T obj) nothrow @nogc 117 if (!is(T == struct) && !is(T == class) && !is(T == interface)) 118 { 119 assumeNothrowNoGC( 120 (ref T x) 121 { 122 return destroy(x); 123 })(obj); 124 } 125 126 127 128 // 129 // Constructing and destroying without the GC. 130 // 131 132 /// Allocates and construct a struct or class object. 133 /// Returns: Newly allocated object. 134 auto mallocNew(T, Args...)(Args args) 135 { 136 static if (is(T == class)) 137 immutable size_t allocSize = __traits(classInstanceSize, T); 138 else 139 immutable size_t allocSize = T.sizeof; 140 141 void* rawMemory = malloc(allocSize); 142 if (!rawMemory) 143 onOutOfMemoryErrorNoGC(); 144 145 static if (is(T == class)) 146 { 147 T obj = emplace!T(rawMemory[0 .. allocSize], args); 148 } 149 else 150 { 151 T* obj = cast(T*)rawMemory; 152 emplace!T(obj, args); 153 } 154 155 return obj; 156 } 157 158 /// Destroys and frees a class object created with $(D mallocEmplace). 159 void destroyFree(T)(T p) if (is(T == class)) 160 { 161 if (p !is null) 162 { 163 destroyNoGC(p); 164 free(cast(void*)p); 165 } 166 } 167 168 /// Destroys and frees an interface object created with $(D mallocEmplace). 169 void destroyFree(T)(T p) if (is(T == interface)) 170 { 171 if (p !is null) 172 { 173 void* here = cast(void*)(cast(Object)p); 174 destroyNoGC(p); 175 free(cast(void*)here); 176 } 177 } 178 179 /// Destroys and frees a non-class object created with $(D mallocEmplace). 180 void destroyFree(T)(T* p) if (!is(T == class)) 181 { 182 if (p !is null) 183 { 184 destroyNoGC(p); 185 free(cast(void*)p); 186 } 187 } 188 189 190 unittest 191 { 192 class A 193 { 194 int _i; 195 this(int i) 196 { 197 _i = i; 198 } 199 } 200 201 struct B 202 { 203 int i; 204 } 205 206 void testMallocEmplace() 207 { 208 A a = mallocNew!A(4); 209 destroyFree(a); 210 211 B* b = mallocNew!B(5); 212 destroyFree(b); 213 } 214 215 testMallocEmplace(); 216 } 217 218 version( D_InlineAsm_X86 ) 219 { 220 version = AsmX86; 221 } 222 else version( D_InlineAsm_X86_64 ) 223 { 224 version = AsmX86; 225 } 226 227 /// Allocates a slice with `malloc`. 228 T[] mallocSlice(T)(size_t count) nothrow @nogc 229 { 230 T[] slice = mallocSliceNoInit!T(count); 231 static if (is(T == struct)) 232 { 233 // we must avoid calling struct destructors with uninitialized memory 234 for(size_t i = 0; i < count; ++i) 235 { 236 T uninitialized; 237 memcpy(&slice[i], &uninitialized, T.sizeof); // memcpy OK 238 } 239 } 240 else 241 slice[0..count] = T.init; 242 return slice; 243 } 244 245 /// Allocates a slice with `malloc`, but does not initialize the content. 246 T[] mallocSliceNoInit(T)(size_t count) nothrow @nogc 247 { 248 T* p = cast(T*) malloc(count * T.sizeof); 249 return p[0..count]; 250 } 251 252 /// Frees a slice allocated with `mallocSlice`. 253 void freeSlice(T)(const(T)[] slice) nothrow @nogc 254 { 255 free(cast(void*)(slice.ptr)); // const cast here 256 } 257 258 /// Duplicates a slice with `malloc`. Equivalent to `.dup` 259 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`. 260 T[] mallocDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct)) 261 { 262 T[] copy = mallocSliceNoInit!T(slice.length); 263 memcpy(copy.ptr, slice.ptr, slice.length * T.sizeof); 264 return copy; 265 } 266 267 /// Duplicates a slice with `malloc`. Equivalent to `.idup` 268 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`. 269 immutable(T)[] mallocIDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct)) 270 { 271 return cast(immutable(T)[]) (mallocDup!T(slice)); 272 } 273 274 /// Duplicates a zero-terminated string with `malloc`, return a `char[]` with zero-terminated byte. 275 /// Has to be cleaned-up with `free(s.ptr)`. 276 /// Note: The zero-terminating byte is preserved. This allow to have a string which also can be converted 277 /// to a C string with `.ptr`. However the zero byte is not included in slice length. 278 char[] stringDup(const(char)* cstr) nothrow @nogc 279 { 280 assert(cstr !is null); 281 size_t len = strlen(cstr); 282 char* copy = strdup(cstr); 283 return copy[0..len]; 284 } 285 286 /// Duplicates a zero-terminated string with `malloc`, return a `string`. with zero-terminated byte. 287 /// Has to be cleaned-up with `free(s.ptr)`. 288 /// Note: The zero-terminating byte is preserved. This allow to have a string which also can be converted 289 /// to a C string with `.ptr`. However the zero byte is not included in slice length. 290 string stringIDup(const(char)* cstr) nothrow @nogc 291 { 292 return cast(string) stringDup(cstr); 293 } 294 295 unittest 296 { 297 int[] slice = mallocSlice!int(4); 298 assert(slice[3] == int.init); 299 freeSlice(slice); 300 301 slice = mallocSliceNoInit!int(4); 302 freeSlice(slice); 303 304 slice = mallocSliceNoInit!int(0); 305 assert(slice == []); 306 freeSlice(slice); 307 } 308 309 /// Semantic function to check that a D string implicitely conveys a 310 /// termination byte after the slice. 311 /// (typically those comes from string literals or `stringDup`/`stringIDup`) 312 const(char)* assumeZeroTerminated(const(char)[] input) nothrow @nogc 313 { 314 if (input.ptr is null) 315 return null; 316 317 // Check that the null character is there 318 assert(input.ptr[input.length] == '\0'); 319 return input.ptr; 320 } 321 322 // 323 // STABLE IN-PLACE SORT (implementation is at bottom of file) 324 // 325 // Here is how to use it: 326 unittest 327 { 328 { 329 int[2][] testData = [[110, 0], [5, 0], [10, 0], [3, 0], [110, 1], [5, 1], [10, 1], [3, 1]]; 330 version(useTimSort) 331 { 332 Vec!(int[2]) tempBuf; 333 timSort!(int[2])(testData, tempBuf, (a, b) => (a[0] - b[0])); 334 } 335 assert(testData == [[3, 0], [3, 1], [5, 0], [5, 1], [10, 0], [10, 1], [110, 0], [110, 1]]); 336 } 337 } 338 339 340 // 341 // STABLE MERGE SORT 342 // 343 344 /// A bit faster than a dynamic cast. 345 /// This is to avoid TypeInfo look-up. 346 T unsafeObjectCast(T)(Object obj) 347 { 348 return cast(T)(cast(void*)(obj)); 349 } 350 351 /// Outputs a debug string in either: 352 /// - stdout on POSIX-like (visible in the command-line) 353 /// - the Output Windows on Windows (visible withing Visual Studio or with dbgview.exe) 354 /// Warning: no end-of-line added! 355 void debugLog(const(char)* message) nothrow @nogc 356 { 357 version(Windows) 358 { 359 import core.sys.windows.windows; 360 OutputDebugStringA(message); 361 } 362 else 363 { 364 import core.stdc.stdio; 365 printf("%s\n", message); 366 } 367 } 368 369 ///ditto 370 extern (C) void debugLogf(const(char)* fmt, ...) nothrow @nogc 371 { 372 // This is a complete hack to be able to build in Ubuntu Focal, which distributes D front-ends based 373 // upon DMDFE 2.090. In these compilers, va_start is not marked @nogc. 374 static if (__VERSION__ > 2090) 375 { 376 import core.stdc.stdio; 377 378 char[256] buffer; 379 va_list args; 380 va_start (args, fmt); 381 vsnprintf (buffer.ptr, 256, fmt, args); 382 va_end (args); 383 384 version(Windows) 385 { 386 import core.sys.windows.windows; 387 OutputDebugStringA(buffer.ptr); 388 } 389 else 390 { 391 printf("%s\n", buffer.ptr); 392 } 393 } 394 } 395 396 /// Inserts a breakpoint instruction. useful to trigger the debugger. 397 void debugBreak() nothrow @nogc 398 { 399 version( AsmX86 ) 400 { 401 asm nothrow @nogc 402 { 403 int 3; 404 } 405 } 406 else version( GNU ) 407 { 408 // __builtin_trap() is not the same thing unfortunately 409 asm 410 { 411 "int $0x03" : : : ; 412 } 413 } 414 else version(LDC) 415 { 416 import ldc.intrinsics; 417 llvm_debugtrap(); 418 } 419 else 420 { 421 static assert(false, "No debugBreak() for this compiler"); 422 } 423 } 424 425 426 // Copy source into dest. 427 // dest must contain room for maxChars characters 428 // A zero-byte character is then appended. 429 void stringNCopy(char* dest, size_t maxChars, const(char)[] source) nothrow @nogc 430 { 431 if (maxChars == 0) 432 return; 433 434 size_t max = maxChars < source.length ? maxChars - 1 : source.length; 435 for (int i = 0; i < max; ++i) 436 dest[i] = source[i]; 437 dest[max] = '\0'; 438 } 439 440 441 // 442 // Low-cost C string conversions 443 // 444 alias CString = CStringImpl!char; 445 alias CString16 = CStringImpl!wchar; 446 447 /// Zero-terminated C string, to replace toStringz and toUTF16z 448 struct CStringImpl(CharType) if (is(CharType: char) || is(CharType: wchar)) 449 { 450 public: 451 nothrow: 452 @nogc: 453 454 const(CharType)* storage = null; 455 alias storage this; 456 457 458 this(const(CharType)[] s) 459 { 460 // Always copy. We can't assume anything about the input. 461 size_t len = s.length; 462 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 463 buffer[0..len] = s[0..len]; 464 buffer[len] = '\0'; 465 storage = buffer; 466 wasAllocated = true; 467 } 468 469 // The constructor taking immutable can safely assume that such memory 470 // has been allocated by the GC or malloc, or an allocator that align 471 // pointer on at least 4 bytes. 472 this(immutable(CharType)[] s) 473 { 474 // Same optimizations that for toStringz 475 if (s.length == 0) 476 { 477 enum emptyString = cast(CharType[])""; 478 storage = emptyString.ptr; 479 return; 480 } 481 482 /* Peek past end of s[], if it's 0, no conversion necessary. 483 * Note that the compiler will put a 0 past the end of static 484 * strings, and the storage allocator will put a 0 past the end 485 * of newly allocated char[]'s. 486 */ 487 const(CharType)* p = s.ptr + s.length; 488 // Is p dereferenceable? A simple test: if the p points to an 489 // address multiple of 4, then conservatively assume the pointer 490 // might be pointing to another block of memory, which might be 491 // unreadable. Otherwise, it's definitely pointing to valid 492 // memory. 493 if ((cast(size_t) p & 3) && *p == 0) 494 { 495 storage = s.ptr; 496 return; 497 } 498 499 size_t len = s.length; 500 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 501 buffer[0..len] = s[0..len]; 502 buffer[len] = '\0'; 503 storage = buffer; 504 wasAllocated = true; 505 } 506 507 ~this() 508 { 509 if (wasAllocated) 510 free(cast(void*)storage); 511 } 512 513 @disable this(this); 514 515 private: 516 bool wasAllocated = false; 517 } 518 519 520 // 521 // Launch browser, replaces std.process.browse 522 // 523 524 void browseNoGC(string url) nothrow @nogc 525 { 526 version(Windows) 527 { 528 import core.sys.windows.winuser; 529 import core.sys.windows.shellapi; 530 ShellExecuteA(null, CString("open").storage, CString(url).storage, null, null, SW_SHOWNORMAL); 531 } 532 533 version(OSX) 534 { 535 import core.sys.posix.unistd; 536 const(char)*[5] args; 537 538 auto curl = CString(url).storage; 539 const(char)* browser = getenv("BROWSER"); 540 if (browser) 541 { 542 browser = strdup(browser); 543 args[0] = browser; 544 args[1] = curl; 545 args[2] = null; 546 } 547 else 548 { 549 args[0] = "open".ptr; 550 args[1] = curl; 551 args[2] = null; 552 } 553 554 auto childpid = core.sys.posix.unistd.fork(); 555 if (childpid == 0) 556 { 557 core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr); 558 return; 559 } 560 if (browser) 561 free(cast(void*)browser); 562 } 563 version(linux) 564 { 565 import core.sys.posix.stdlib; 566 import core.stdc.stdio; 567 char[256] curl; 568 sprintf(curl.ptr, "%s %s", "xdg-open".ptr, CString(url).storage); 569 system(curl.ptr); 570 } 571 } 572 573 // 574 // @nogc sorting. 575 // 576 577 /// Must return -1 if a < b 578 /// 0 if a == b 579 /// 1 if a > b 580 alias nogcComparisonFunction(T) = int delegate(in T a, in T b) nothrow @nogc; 581 582 583 version(useTimSort) 584 { 585 586 public void timSort(T)(T[] dst, 587 ref Vec!T storeBuf, // content unimportant, this will be use a temp storage. 588 // it should be "grow-only" 589 nogcComparisonFunction!T comparison) nothrow @nogc 590 { 591 const size_t size = dst.length; 592 593 /* don't bother sorting an array of size 1 */ 594 if (size <= 1) { 595 return; 596 } 597 598 if (size < 64) 599 { 600 // uh... all out test cases are here 601 tim_sort_binary_inversion_sort!T(dst.ptr, size, comparison); 602 return; 603 } 604 605 // Why would it be used only there??? 606 enum TIM_SORT_STACK_SIZE = 64; 607 tim_sort_run_t[TIM_SORT_STACK_SIZE] run_stack; 608 size_t stack_curr = 0; 609 size_t curr = 0; 610 611 /* compute the minimum run length */ 612 size_t minrun = tim_sort_compute_minrun(size); 613 614 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, &curr, comparison)) { 615 return; 616 } 617 618 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, &curr, comparison)) { 619 return; 620 } 621 622 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, &curr, comparison)) { 623 return; 624 } 625 626 while (1) { 627 if (!tim_sort_check_invariant(run_stack.ptr, cast(int)stack_curr)) { 628 stack_curr = tim_sort_collapse!T(dst.ptr, run_stack.ptr, cast(int)stack_curr, storeBuf, size, comparison); 629 continue; 630 } 631 632 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, &curr, comparison)) { 633 return; 634 } 635 } 636 } 637 638 private: 639 640 641 /* adapted from Hacker's Delight */ 642 static int clzll(ulong x) pure nothrow @nogc 643 { 644 if (x == 0) 645 return 64; 646 647 // Note: not worth optimizing further with `63 - bsr(x)` 648 // It's simply called once. 649 int n = 0; 650 651 if (x <= 0x00000000FFFFFFFFL) 652 { 653 n = n + 32; 654 x = x << 32; 655 } 656 657 if (x <= 0x0000FFFFFFFFFFFFL) 658 { 659 n = n + 16; 660 x = x << 16; 661 } 662 663 if (x <= 0x00FFFFFFFFFFFFFFL) 664 { 665 n = n + 8; 666 x = x << 8; 667 } 668 669 if (x <= 0x0FFFFFFFFFFFFFFFL) 670 { 671 n = n + 4; 672 x = x << 4; 673 } 674 675 if (x <= 0x3FFFFFFFFFFFFFFFL) 676 { 677 n = n + 2; 678 x = x << 2; 679 } 680 681 if (x <= 0x7FFFFFFFFFFFFFFFL) 682 { 683 n = n + 1; 684 } 685 return n; 686 } 687 unittest 688 { 689 assert(clzll(0) == 64); 690 assert(clzll(1) == 63); 691 assert(clzll(-1) == 0); 692 } 693 694 static int tim_sort_compute_minrun(const ulong size) pure nothrow @nogc 695 { 696 int top_bit = 64 - clzll(size); 697 int maxbit = top_bit > 6 ? top_bit : 6; 698 int shift = maxbit - 6; 699 int minrun = cast(int)(size >> shift); 700 ulong mask = ((cast(ulong)1) << shift) - 1; 701 if (mask & size) 702 { 703 return minrun + 1; 704 } 705 return minrun; 706 } 707 708 709 struct tim_sort_run_t 710 { 711 size_t start; 712 size_t length; 713 } 714 715 /* Function used to do a binary search for binary insertion sort */ 716 size_t tim_sort_binary_inversion_find(T)(T *dst, const T x, const size_t size, nogcComparisonFunction!T comparison) nothrow @nogc 717 { 718 size_t l, c, r; 719 T cx; 720 l = 0; 721 r = size - 1; 722 c = r >> 1; 723 724 /* check for out of bounds at the beginning. */ 725 if (comparison(x, dst[0]) < 0) 726 { 727 return 0; 728 } else if (comparison(x, dst[r]) > 0) 729 { 730 return r; 731 } 732 733 cx = dst[c]; 734 735 while (1) 736 { 737 const int val = comparison(x, cx); 738 739 if (val < 0) 740 { 741 if (c - l <= 1) 742 { 743 return c; 744 } 745 746 r = c; 747 } else 748 { /* allow = for stability. The binary search favors the right. */ 749 if (r - c <= 1) 750 { 751 return c + 1; 752 } 753 l = c; 754 } 755 756 c = l + ((r - l) >> 1); 757 cx = dst[c]; 758 } 759 } 760 761 /* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ 762 static void tim_sort_binary_inversion_sort_start(T)(T *dst, 763 const size_t start, 764 const size_t size, 765 nogcComparisonFunction!T comparison) nothrow @nogc 766 { 767 size_t i; 768 769 for (i = start; i < size; i++) { 770 size_t j; 771 T x; 772 size_t location; 773 774 /* If this entry is already correct, just move along */ 775 if (comparison(dst[i - 1], dst[i]) <= 0) { 776 continue; 777 } 778 779 /* Else we need to find the right place, shift everything over, and squeeze in */ 780 x = dst[i]; 781 location = tim_sort_binary_inversion_find!T(dst, x, i, comparison); 782 783 for (j = i - 1; j >= location; j--) { 784 dst[j + 1] = dst[j]; 785 786 if (j == 0) { /* check edge case because j is unsigned */ 787 break; 788 } 789 } 790 791 dst[location] = x; 792 } 793 } 794 795 /* Binary insertion sort */ 796 static void tim_sort_binary_inversion_sort(T)(T *dst, 797 const size_t size, 798 nogcComparisonFunction!T comparison) 799 { 800 /* don't bother sorting an array of size <= 1 */ 801 if (size <= 1) { 802 return; 803 } 804 tim_sort_binary_inversion_sort_start!T(dst, 1, size, comparison); 805 } 806 807 /* timsort implementation, based on timsort.txt */ 808 809 static void tim_sort_reverse_elements(T)(T *dst, size_t start, size_t end) 810 { 811 while (1) { 812 if (start >= end) { 813 return; 814 } 815 816 T temp = dst[start]; // swap 817 dst[start] = dst[end]; 818 dst[end] = temp; 819 820 start++; 821 end--; 822 } 823 } 824 825 static size_t tim_sort_count_run(T)(T *dst, const size_t start, const size_t size, nogcComparisonFunction!T comparison) 826 { 827 size_t curr; 828 829 if (size - start == 1) { 830 return 1; 831 } 832 833 if (start >= size - 2) { 834 if (comparison(dst[size - 2], dst[size - 1]) > 0) 835 { 836 // swap 837 T temp = dst[size - 2]; 838 dst[size - 2] = dst[size - 1]; 839 dst[size - 1] = temp; 840 } 841 842 return 2; 843 } 844 845 curr = start + 2; 846 847 if (comparison(dst[start], dst[start + 1]) <= 0) { 848 /* increasing run */ 849 while (1) { 850 if (curr == size - 1) { 851 break; 852 } 853 854 if (comparison(dst[curr - 1], dst[curr]) > 0) { 855 break; 856 } 857 858 curr++; 859 } 860 861 return curr - start; 862 } else { 863 /* decreasing run */ 864 while (1) { 865 if (curr == size - 1) { 866 break; 867 } 868 869 if (comparison(dst[curr - 1], dst[curr]) <= 0) { 870 break; 871 } 872 873 curr++; 874 } 875 876 /* reverse in-place */ 877 tim_sort_reverse_elements!T(dst, start, curr - 1); 878 return curr - start; 879 } 880 } 881 882 static int tim_sort_check_invariant(tim_sort_run_t *stack, const int stack_curr) nothrow @nogc 883 { 884 size_t A, B, C; 885 886 if (stack_curr < 2) { 887 return 1; 888 } 889 890 if (stack_curr == 2) { 891 const size_t A1 = stack[stack_curr - 2].length; 892 const size_t B1 = stack[stack_curr - 1].length; 893 894 if (A1 <= B1) { 895 return 0; 896 } 897 898 return 1; 899 } 900 901 A = stack[stack_curr - 3].length; 902 B = stack[stack_curr - 2].length; 903 C = stack[stack_curr - 1].length; 904 905 if ((A <= B + C) || (B <= C)) 906 { 907 return 0; 908 } 909 910 return 1; 911 } 912 913 static void tim_sort_merge(T)(T *dst, 914 const tim_sort_run_t *stack, 915 const int stack_curr, 916 ref Vec!T storeBuf, 917 nogcComparisonFunction!T comparison) 918 { 919 const size_t A = stack[stack_curr - 2].length; 920 const size_t B = stack[stack_curr - 1].length; 921 const size_t curr = stack[stack_curr - 2].start; 922 923 size_t i, j, k; 924 925 size_t minSize = (A < B) ? A : B; 926 927 storeBuf.resize( minSize ); 928 T* storage = storeBuf.ptr; 929 930 /* left merge */ 931 if (A < B) { 932 memcpy(storage, &dst[curr], A * T.sizeof); 933 i = 0; 934 j = curr + A; 935 936 for (k = curr; k < curr + A + B; k++) { 937 if ((i < A) && (j < curr + A + B)) { 938 if (comparison(storage[i], dst[j]) <= 0) { 939 dst[k] = storage[i++]; 940 } else { 941 dst[k] = dst[j++]; 942 } 943 } else if (i < A) { 944 dst[k] = storage[i++]; 945 } else { 946 break; 947 } 948 } 949 } else { 950 /* right merge */ 951 memcpy(storage, &dst[curr + A], B * T.sizeof); 952 i = B; 953 j = curr + A; 954 k = curr + A + B; 955 956 while (k > curr) { 957 k--; 958 if ((i > 0) && (j > curr)) { 959 if (comparison(dst[j - 1], storage[i - 1]) > 0) { 960 dst[k] = dst[--j]; 961 } else { 962 dst[k] = storage[--i]; 963 } 964 } else if (i > 0) { 965 dst[k] = storage[--i]; 966 } else { 967 break; 968 } 969 } 970 } 971 } 972 973 static int tim_sort_collapse(T)(T *dst, 974 tim_sort_run_t *stack, 975 int stack_curr, 976 ref Vec!T storeBuf, 977 const size_t size, 978 nogcComparisonFunction!T comparison) nothrow @nogc 979 { 980 while (1) 981 { 982 size_t A, B, C, D; 983 int ABC, BCD, CD; 984 985 /* if the stack only has one thing on it, we are done with the collapse */ 986 if (stack_curr <= 1) { 987 break; 988 } 989 990 /* if this is the last merge, just do it */ 991 if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { 992 tim_sort_merge!T(dst, stack, stack_curr, storeBuf, comparison); 993 stack[0].length += stack[1].length; 994 stack_curr--; 995 break; 996 } 997 /* check if the invariant is off for a stack of 2 elements */ 998 else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { 999 tim_sort_merge!T(dst, stack, stack_curr, storeBuf, comparison); 1000 stack[0].length += stack[1].length; 1001 stack_curr--; 1002 break; 1003 } else if (stack_curr == 2) { 1004 break; 1005 } 1006 1007 B = stack[stack_curr - 3].length; 1008 C = stack[stack_curr - 2].length; 1009 D = stack[stack_curr - 1].length; 1010 1011 if (stack_curr >= 4) { 1012 A = stack[stack_curr - 4].length; 1013 ABC = (A <= B + C); 1014 } else { 1015 ABC = 0; 1016 } 1017 1018 BCD = (B <= C + D) || ABC; 1019 CD = (C <= D); 1020 1021 /* Both invariants are good */ 1022 if (!BCD && !CD) { 1023 break; 1024 } 1025 1026 /* left merge */ 1027 if (BCD && !CD) { 1028 tim_sort_merge!T(dst, stack, stack_curr - 1, storeBuf, comparison); 1029 stack[stack_curr - 3].length += stack[stack_curr - 2].length; 1030 stack[stack_curr - 2] = stack[stack_curr - 1]; 1031 stack_curr--; 1032 } else { 1033 /* right merge */ 1034 tim_sort_merge!T(dst, stack, stack_curr, storeBuf, comparison); 1035 stack[stack_curr - 2].length += stack[stack_curr - 1].length; 1036 stack_curr--; 1037 } 1038 } 1039 1040 return stack_curr; 1041 } 1042 1043 static int tim_sort_push_next(T)(T *dst, 1044 const size_t size, 1045 ref Vec!T storeBuf, 1046 const size_t minrun, 1047 tim_sort_run_t *run_stack, 1048 size_t *stack_curr, 1049 size_t *curr, 1050 nogcComparisonFunction!T comparison) 1051 { 1052 size_t len = tim_sort_count_run!T(dst, *curr, size, comparison); 1053 size_t run = minrun; 1054 1055 if (run > size - *curr) { 1056 run = size - *curr; 1057 } 1058 1059 if (run > len) { 1060 tim_sort_binary_inversion_sort_start!T(&dst[*curr], len, run, comparison); 1061 len = run; 1062 } 1063 1064 run_stack[*stack_curr].start = *curr; 1065 run_stack[*stack_curr].length = len; 1066 (*stack_curr)++; 1067 *curr += len; 1068 1069 if (*curr == size) { 1070 /* finish up */ 1071 while (*stack_curr > 1) { 1072 tim_sort_merge!T(dst, run_stack, cast(int) *stack_curr, storeBuf, comparison); 1073 run_stack[*stack_curr - 2].length += run_stack[*stack_curr - 1].length; 1074 (*stack_curr)--; 1075 } 1076 1077 return 0; 1078 } 1079 1080 return 1; 1081 } 1082 }