1 /** 2 3 Various @nogc alternatives. This file includes parts of `std.process`, `std.random`, `std.uuid`. 4 5 Copyright: Copyright 2016-2024, Guillaume Piolat. 6 Copyright: Copyright 2013, Lars Tandle Kyllingstad (std.process). 7 Copyright: Copyright 2013, Steven Schveighoffer (std.process). 8 Copyright: Copyright 2013, Vladimir Panteleev (std.process). 9 10 License: www.boost.org/LICENSE_1_0.txt 11 */ 12 module dplug.core.nogc; 13 14 import core.stdc.stdarg; 15 import core.stdc.string: strdup, memcpy, strlen; 16 import core.stdc.stdlib: malloc, free, getenv; 17 import core.memory: GC; 18 import core.exception: onOutOfMemoryErrorNoGC; 19 20 import std.conv: emplace; 21 import std.traits; 22 23 import dplug.core.vec: Vec; 24 25 // This module provides many utilities to deal with @nogc nothrow, in a situation with the runtime 26 // disabled. 27 28 // 29 // Faking @nogc 30 // 31 32 version(Windows) 33 { 34 import core.sys.windows.winbase; 35 } 36 37 version = useTimSort; 38 39 auto assumeNoGC(T) (T t) 40 { 41 static if (isFunctionPointer!T || isDelegate!T) 42 { 43 enum attrs = functionAttributes!T | FunctionAttribute.nogc; 44 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 45 } 46 else 47 static assert(false); 48 } 49 50 auto assumeNothrowNoGC(T) (T t) 51 { 52 static if (isFunctionPointer!T || isDelegate!T) 53 { 54 enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_; 55 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 56 } 57 else 58 static assert(false); 59 } 60 61 unittest 62 { 63 void funcThatDoesGC() 64 { 65 int a = 4; 66 int[] b = [a, a, a]; 67 } 68 69 void anotherFunction() nothrow @nogc 70 { 71 assumeNothrowNoGC( (){ funcThatDoesGC(); } )(); 72 } 73 74 void aThirdFunction() @nogc 75 { 76 assumeNoGC( () { funcThatDoesGC(); } )(); 77 } 78 } 79 80 81 // 82 // Optimistic .destroy, which is @nogc nothrow by breaking the type-system 83 // 84 85 // for classes 86 void destroyNoGC(T)(T x) nothrow @nogc if (is(T == class) || is(T == interface)) 87 { 88 assumeNothrowNoGC( 89 (T x) 90 { 91 return destroy(x); 92 })(x); 93 } 94 95 // for struct 96 void destroyNoGC(T)(ref T obj) nothrow @nogc if (is(T == struct)) 97 { 98 assumeNothrowNoGC( 99 (ref T x) 100 { 101 return destroy(x); 102 })(obj); 103 } 104 /* 105 void destroyNoGC(T : U[n], U, size_t n)(ref T obj) nothrow @nogc 106 { 107 assumeNothrowNoGC( 108 (T x) 109 { 110 return destroy(x); 111 })(obj); 112 }*/ 113 114 void destroyNoGC(T)(ref T obj) nothrow @nogc 115 if (!is(T == struct) && !is(T == class) && !is(T == interface)) 116 { 117 assumeNothrowNoGC( 118 (ref T x) 119 { 120 return destroy(x); 121 })(obj); 122 } 123 124 125 126 // 127 // Constructing and destroying without the GC. 128 // 129 130 /// Allocates and construct a struct or class object. 131 /// Returns: Newly allocated object. 132 auto mallocNew(T, Args...)(Args args) 133 { 134 static if (is(T == class)) 135 immutable size_t allocSize = __traits(classInstanceSize, T); 136 else 137 immutable size_t allocSize = T.sizeof; 138 139 void* rawMemory = malloc(allocSize); 140 if (!rawMemory) 141 onOutOfMemoryErrorNoGC(); 142 143 static if (is(T == class)) 144 { 145 T obj = emplace!T(rawMemory[0 .. allocSize], args); 146 } 147 else 148 { 149 T* obj = cast(T*)rawMemory; 150 emplace!T(obj, args); 151 } 152 153 return obj; 154 } 155 156 /// Destroys and frees a class object created with $(D mallocEmplace). 157 void destroyFree(T)(T p) if (is(T == class)) 158 { 159 if (p !is null) 160 { 161 destroyNoGC(p); 162 free(cast(void*)p); 163 } 164 } 165 166 /// Destroys and frees an interface object created with $(D mallocEmplace). 167 void destroyFree(T)(T p) if (is(T == interface)) 168 { 169 if (p !is null) 170 { 171 void* here = cast(void*)(cast(Object)p); 172 destroyNoGC(p); 173 free(cast(void*)here); 174 } 175 } 176 177 /// Destroys and frees a non-class object created with $(D mallocEmplace). 178 void destroyFree(T)(T* p) if (!is(T == class)) 179 { 180 if (p !is null) 181 { 182 destroyNoGC(p); 183 free(cast(void*)p); 184 } 185 } 186 187 188 unittest 189 { 190 class A 191 { 192 int _i; 193 this(int i) 194 { 195 _i = i; 196 } 197 } 198 199 struct B 200 { 201 int i; 202 } 203 204 void testMallocEmplace() 205 { 206 A a = mallocNew!A(4); 207 destroyFree(a); 208 209 B* b = mallocNew!B(5); 210 destroyFree(b); 211 } 212 213 testMallocEmplace(); 214 } 215 216 version( D_InlineAsm_X86 ) 217 { 218 version = AsmX86; 219 } 220 else version( D_InlineAsm_X86_64 ) 221 { 222 version = AsmX86; 223 } 224 225 /// Allocates a slice with `malloc`. 226 T[] mallocSlice(T)(size_t count) nothrow @nogc 227 { 228 T[] slice = mallocSliceNoInit!T(count); 229 static if (is(T == struct)) 230 { 231 // we must avoid calling struct destructors with uninitialized memory 232 for(size_t i = 0; i < count; ++i) 233 { 234 T uninitialized; 235 memcpy(&slice[i], &uninitialized, T.sizeof); // memcpy OK 236 } 237 } 238 else 239 slice[0..count] = T.init; 240 return slice; 241 } 242 243 /// Allocates a slice with `malloc`, but does not initialize the content. 244 T[] mallocSliceNoInit(T)(size_t count) nothrow @nogc 245 { 246 T* p = cast(T*) malloc(count * T.sizeof); 247 return p[0..count]; 248 } 249 250 /// Frees a slice allocated with `mallocSlice`. 251 void freeSlice(T)(const(T)[] slice) nothrow @nogc 252 { 253 free(cast(void*)(slice.ptr)); // const cast here 254 } 255 256 /// Duplicates a slice with `malloc`. Equivalent to `.dup` 257 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`. 258 /// FUTURE: all the semantics are not well-defined with null slices, 259 /// but it would be tricky to think about it and define it. 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 string with `malloc`, and add a terminal zero. 268 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`. 269 char[] mallocDupZ(const(char)[] slice) nothrow @nogc 270 { 271 char[] copy = mallocSliceNoInit!char(slice.length + 1); 272 memcpy(copy.ptr, slice.ptr, slice.length * char.sizeof); 273 copy[$-1] = '\0'; 274 return copy[0..$-1]; 275 } 276 unittest 277 { 278 char[] s = mallocDupZ("Hello"); 279 assert(*assumeZeroTerminated(s) == 'H'); 280 freeSlice(s); 281 } 282 283 /// Duplicates a slice with `malloc`. Equivalent to `.idup` 284 /// Has to be cleaned-up with `free(slice.ptr)` or `freeSlice(slice)`. 285 immutable(T)[] mallocIDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct)) 286 { 287 return cast(immutable(T)[]) (mallocDup!T(slice)); 288 } 289 290 /// Duplicates a zero-terminated string with `malloc`, return a `char[]` with zero-terminated byte. 291 /// Has to be cleaned-up with `free(s.ptr)`. 292 /// Note: The zero-terminating byte is preserved. This allow to have a string which also can be 293 /// converted to a C string with `.ptr`. However the zero byte is not included in slice length. 294 char[] stringDup(const(char)* cstr) nothrow @nogc 295 { 296 assert(cstr !is null); 297 size_t len = strlen(cstr); 298 char* copy = strdup(cstr); 299 return copy[0..len]; 300 } 301 302 /// Duplicates a zero-terminated string with `malloc`, return a `string`. with zero-terminated 303 /// byte. Has to be cleaned-up with `free(s.ptr)`. 304 /// 305 /// Note: The zero-terminating byte is preserved. This allow to have a string which also can be 306 /// converted to a C string with `.ptr`. However the zero byte is not included in slice length. 307 string stringIDup(const(char)* cstr) nothrow @nogc 308 { 309 return cast(string) stringDup(cstr); 310 } 311 312 unittest 313 { 314 int[] slice = mallocSlice!int(4); 315 assert(slice[3] == int.init); 316 freeSlice(slice); 317 318 slice = mallocSliceNoInit!int(4); 319 freeSlice(slice); 320 321 slice = mallocSliceNoInit!int(0); 322 assert(slice == []); 323 freeSlice(slice); 324 } 325 326 /// Semantic function to check that a D string implicitely conveys a 327 /// termination byte after the slice. 328 /// (typically those comes from string literals or `stringDup`/`stringIDup`) 329 const(char)* assumeZeroTerminated(const(char)[] input) nothrow @nogc 330 { 331 if (input.ptr is null) 332 return null; 333 334 // Check that the null character is there 335 assert(input.ptr[input.length] == '\0'); 336 return input.ptr; 337 } 338 339 // 340 // STABLE IN-PLACE SORT (implementation is at bottom of file) 341 // 342 // Here is how to use it: 343 unittest 344 { 345 { 346 int[2][] testData = [[110, 0], [5, 0], [10, 0], [3, 0], [110, 1], [5, 1], [10, 1], [3, 1]]; 347 version(useTimSort) 348 { 349 Vec!(int[2]) tempBuf; 350 timSort!(int[2])(testData, tempBuf, (a, b) => (a[0] - b[0])); 351 } 352 assert(testData == [[3, 0], [3, 1], [5, 0], [5, 1], [10, 0], [10, 1], [110, 0], [110, 1]]); 353 } 354 } 355 356 357 // 358 // STABLE MERGE SORT 359 // 360 361 /// A bit faster than a dynamic cast. 362 /// This is to avoid TypeInfo look-up. 363 T unsafeObjectCast(T)(Object obj) 364 { 365 return cast(T)(cast(void*)(obj)); 366 } 367 368 /// Outputs a debug string in either: 369 /// - stdout on POSIX-like (visible in the command-line) 370 /// - the Output Windows on Windows (visible withing Visual Studio or with dbgview.exe) 371 /// Warning: no end-of-line added! 372 void debugLog(const(char)* message) nothrow @nogc 373 { 374 version(Windows) 375 { 376 OutputDebugStringA(message); 377 } 378 else 379 { 380 import core.stdc.stdio; 381 printf("%s\n", message); 382 } 383 } 384 385 ///ditto 386 extern (C) void debugLogf(const(char)* fmt, ...) nothrow @nogc 387 { 388 // This is a complete hack to be able to build in Ubuntu Focal, which distributes D front-ends 389 // based upon DMDFE 2.090. In these compilers, va_start is not marked @nogc. 390 static if (__VERSION__ > 2090) 391 { 392 import core.stdc.stdio; 393 394 char[256] buffer; 395 va_list args; 396 va_start (args, fmt); 397 vsnprintf (buffer.ptr, 256, fmt, args); 398 va_end (args); 399 400 version(Windows) 401 { 402 OutputDebugStringA(buffer.ptr); 403 } 404 else 405 { 406 printf("%s\n", buffer.ptr); 407 } 408 } 409 } 410 411 /// Inserts a breakpoint instruction. useful to trigger the debugger. 412 void debugBreak() nothrow @nogc 413 { 414 version( AsmX86 ) 415 { 416 asm nothrow @nogc 417 { 418 int 3; 419 } 420 } 421 else version( GNU ) 422 { 423 // __builtin_trap() is not the same thing unfortunately 424 asm 425 { 426 "int $0x03" : : : ; 427 } 428 } 429 else version(LDC) 430 { 431 import ldc.intrinsics; 432 llvm_debugtrap(); 433 } 434 else 435 { 436 static assert(false, "No debugBreak() for this compiler"); 437 } 438 } 439 440 441 // Copy source into dest. 442 // dest must contain room for maxChars characters 443 // A zero-byte character is then appended. 444 void stringNCopy(char* dest, size_t maxChars, const(char)[] source) nothrow @nogc 445 { 446 if (maxChars == 0) 447 return; 448 449 size_t max = maxChars < source.length ? maxChars - 1 : source.length; 450 for (int i = 0; i < max; ++i) 451 dest[i] = source[i]; 452 dest[max] = '\0'; 453 } 454 455 456 // 457 // Low-cost C string conversions 458 // 459 alias CString = CStringImpl!char; 460 alias CString16 = CStringImpl!wchar; 461 462 /// Zero-terminated C string, to replace toStringz and toUTF16z 463 struct CStringImpl(CharType) if (is(CharType: char) || is(CharType: wchar)) 464 { 465 public: 466 nothrow: 467 @nogc: 468 469 const(CharType)* storage() return 470 { 471 return _storage; 472 } 473 474 this(const(CharType)[] s) 475 { 476 // Always copy. We can't assume anything about the input. 477 size_t len = s.length; 478 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 479 buffer[0..len] = s[0..len]; 480 buffer[len] = '\0'; 481 _storage = buffer; 482 _wasAllocated = true; 483 } 484 485 ~this() 486 { 487 if (_wasAllocated) 488 free(cast(void*)_storage); 489 } 490 491 @disable this(this); 492 493 private: 494 const(CharType)* _storage = null; 495 bool _wasAllocated = false; 496 } 497 498 // 499 // Launch browser, replaces std.process.browse 500 // 501 502 void browseNoGC(const(char)[] url) nothrow @nogc 503 { 504 CString curl = CString(url); 505 version(Windows) 506 { 507 import core.sys.windows.winuser; 508 import core.sys.windows.shellapi; 509 ShellExecuteA(null, "open".ptr, curl.storage, null, null, 510 SW_SHOWNORMAL); 511 } 512 513 version(OSX) 514 { 515 import core.sys.posix.unistd; 516 const(char)*[5] args; 517 const(char)* browser = getenv("BROWSER"); 518 if (browser) 519 { 520 521 browser = strdup(browser); 522 args[0] = browser; 523 args[1] = curl.storage; 524 args[2] = null; 525 } 526 else 527 { 528 args[0] = "open".ptr; 529 args[1] = curl.storage; 530 args[2] = null; 531 } 532 533 auto childpid = core.sys.posix.unistd.fork(); 534 if (childpid == 0) 535 { 536 core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr); 537 return; 538 } 539 if (browser) 540 free(cast(void*)browser); 541 } 542 version(linux) 543 { 544 import core.sys.posix.stdlib; 545 import core.stdc.stdio; 546 char[256] cmdline; 547 snprintf(cmdline.ptr, 256, "%s %s", "xdg-open".ptr, curl.storage); 548 system(cmdline.ptr); 549 } 550 } 551 552 // 553 // @nogc sorting. 554 // 555 556 /// Must return -1 if a < b 557 /// 0 if a == b 558 /// 1 if a > b 559 alias nogcComparisonFunction(T) = int delegate(in T a, in T b) nothrow @nogc; 560 561 562 version(useTimSort) 563 { 564 565 public void timSort(T)(T[] dst, 566 ref Vec!T storeBuf, // content unimportant, this will be use a temp storage. 567 // it should be "grow-only" 568 nogcComparisonFunction!T comparison) nothrow @nogc 569 { 570 const size_t size = dst.length; 571 572 /* don't bother sorting an array of size 1 */ 573 if (size <= 1) { 574 return; 575 } 576 577 if (size < 64) 578 { 579 // uh... all out test cases are here 580 tim_sort_binary_inversion_sort!T(dst.ptr, size, comparison); 581 return; 582 } 583 584 // Why would it be used only there??? 585 enum TIM_SORT_STACK_SIZE = 64; 586 tim_sort_run_t[TIM_SORT_STACK_SIZE] run_stack; 587 size_t stack_curr = 0; 588 size_t curr = 0; 589 590 /* compute the minimum run length */ 591 size_t minrun = tim_sort_compute_minrun(size); 592 593 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, 594 &curr, comparison)) 595 { 596 return; 597 } 598 599 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, 600 &curr, comparison)) 601 { 602 return; 603 } 604 605 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, 606 &curr, comparison)) 607 { 608 return; 609 } 610 611 while (1) { 612 if (!tim_sort_check_invariant(run_stack.ptr, cast(int)stack_curr)) { 613 stack_curr = tim_sort_collapse!T(dst.ptr, run_stack.ptr, cast(int)stack_curr, 614 storeBuf, size, comparison); 615 continue; 616 } 617 618 if (!tim_sort_push_next!T(dst.ptr, size, storeBuf, minrun, run_stack.ptr, &stack_curr, 619 &curr, comparison)) { 620 return; 621 } 622 } 623 } 624 625 private: 626 627 628 /* adapted from Hacker's Delight */ 629 static int clzll(ulong x) pure nothrow @nogc 630 { 631 if (x == 0) 632 return 64; 633 634 // Note: not worth optimizing further with `63 - bsr(x)` 635 // It's simply called once. 636 int n = 0; 637 638 if (x <= 0x00000000FFFFFFFFL) 639 { 640 n = n + 32; 641 x = x << 32; 642 } 643 644 if (x <= 0x0000FFFFFFFFFFFFL) 645 { 646 n = n + 16; 647 x = x << 16; 648 } 649 650 if (x <= 0x00FFFFFFFFFFFFFFL) 651 { 652 n = n + 8; 653 x = x << 8; 654 } 655 656 if (x <= 0x0FFFFFFFFFFFFFFFL) 657 { 658 n = n + 4; 659 x = x << 4; 660 } 661 662 if (x <= 0x3FFFFFFFFFFFFFFFL) 663 { 664 n = n + 2; 665 x = x << 2; 666 } 667 668 if (x <= 0x7FFFFFFFFFFFFFFFL) 669 { 670 n = n + 1; 671 } 672 return n; 673 } 674 unittest 675 { 676 assert(clzll(0) == 64); 677 assert(clzll(1) == 63); 678 assert(clzll(-1) == 0); 679 } 680 681 static int tim_sort_compute_minrun(const ulong size) pure nothrow @nogc 682 { 683 int top_bit = 64 - clzll(size); 684 int maxbit = top_bit > 6 ? top_bit : 6; 685 int shift = maxbit - 6; 686 int minrun = cast(int)(size >> shift); 687 ulong mask = ((cast(ulong)1) << shift) - 1; 688 if (mask & size) 689 { 690 return minrun + 1; 691 } 692 return minrun; 693 } 694 695 696 struct tim_sort_run_t 697 { 698 size_t start; 699 size_t length; 700 } 701 702 /* Function used to do a binary search for binary insertion sort */ 703 size_t tim_sort_binary_inversion_find(T)(T *dst, const T x, const size_t size, 704 nogcComparisonFunction!T comparison) nothrow @nogc 705 { 706 size_t l, c, r; 707 T cx; 708 l = 0; 709 r = size - 1; 710 c = r >> 1; 711 712 /* check for out of bounds at the beginning. */ 713 if (comparison(x, dst[0]) < 0) 714 { 715 return 0; 716 } else if (comparison(x, dst[r]) > 0) 717 { 718 return r; 719 } 720 721 cx = dst[c]; 722 723 while (1) 724 { 725 const int val = comparison(x, cx); 726 727 if (val < 0) 728 { 729 if (c - l <= 1) 730 { 731 return c; 732 } 733 734 r = c; 735 } else 736 { /* allow = for stability. The binary search favors the right. */ 737 if (r - c <= 1) 738 { 739 return c + 1; 740 } 741 l = c; 742 } 743 744 c = l + ((r - l) >> 1); 745 cx = dst[c]; 746 } 747 } 748 749 // Binary insertion sort, but knowing that the first "start" entries are sorted. 750 static void tim_sort_binary_inversion_sort_start(T)(T *dst, 751 const size_t start, 752 const size_t size, 753 nogcComparisonFunction!T comparison) nothrow @nogc 754 { 755 size_t i; 756 757 for (i = start; i < size; i++) { 758 size_t j; 759 T x; 760 size_t location; 761 762 /* If this entry is already correct, just move along */ 763 if (comparison(dst[i - 1], dst[i]) <= 0) { 764 continue; 765 } 766 767 /* Else we need to find the right place, shift everything over, and squeeze in */ 768 x = dst[i]; 769 location = tim_sort_binary_inversion_find!T(dst, x, i, comparison); 770 771 for (j = i - 1; j >= location; j--) { 772 dst[j + 1] = dst[j]; 773 774 if (j == 0) { /* check edge case because j is unsigned */ 775 break; 776 } 777 } 778 779 dst[location] = x; 780 } 781 } 782 783 /* Binary insertion sort */ 784 static void tim_sort_binary_inversion_sort(T)(T *dst, 785 const size_t size, 786 nogcComparisonFunction!T comparison) 787 { 788 /* don't bother sorting an array of size <= 1 */ 789 if (size <= 1) { 790 return; 791 } 792 tim_sort_binary_inversion_sort_start!T(dst, 1, size, comparison); 793 } 794 795 /* timsort implementation, based on timsort.txt */ 796 797 static void tim_sort_reverse_elements(T)(T *dst, size_t start, size_t end) 798 { 799 while (1) { 800 if (start >= end) { 801 return; 802 } 803 804 T temp = dst[start]; // swap 805 dst[start] = dst[end]; 806 dst[end] = temp; 807 808 start++; 809 end--; 810 } 811 } 812 813 static size_t tim_sort_count_run(T)(T *dst, const size_t start, const size_t size, nogcComparisonFunction!T comparison) 814 { 815 size_t curr; 816 817 if (size - start == 1) { 818 return 1; 819 } 820 821 if (start >= size - 2) { 822 if (comparison(dst[size - 2], dst[size - 1]) > 0) 823 { 824 // swap 825 T temp = dst[size - 2]; 826 dst[size - 2] = dst[size - 1]; 827 dst[size - 1] = temp; 828 } 829 830 return 2; 831 } 832 833 curr = start + 2; 834 835 if (comparison(dst[start], dst[start + 1]) <= 0) { 836 /* increasing run */ 837 while (1) { 838 if (curr == size - 1) { 839 break; 840 } 841 842 if (comparison(dst[curr - 1], dst[curr]) > 0) { 843 break; 844 } 845 846 curr++; 847 } 848 849 return curr - start; 850 } else { 851 /* decreasing run */ 852 while (1) { 853 if (curr == size - 1) { 854 break; 855 } 856 857 if (comparison(dst[curr - 1], dst[curr]) <= 0) { 858 break; 859 } 860 861 curr++; 862 } 863 864 /* reverse in-place */ 865 tim_sort_reverse_elements!T(dst, start, curr - 1); 866 return curr - start; 867 } 868 } 869 870 static int tim_sort_check_invariant(tim_sort_run_t *stack, const int stack_curr) nothrow @nogc 871 { 872 size_t A, B, C; 873 874 if (stack_curr < 2) { 875 return 1; 876 } 877 878 if (stack_curr == 2) { 879 const size_t A1 = stack[stack_curr - 2].length; 880 const size_t B1 = stack[stack_curr - 1].length; 881 882 if (A1 <= B1) { 883 return 0; 884 } 885 886 return 1; 887 } 888 889 A = stack[stack_curr - 3].length; 890 B = stack[stack_curr - 2].length; 891 C = stack[stack_curr - 1].length; 892 893 if ((A <= B + C) || (B <= C)) 894 { 895 return 0; 896 } 897 898 return 1; 899 } 900 901 static void tim_sort_merge(T)(T *dst, 902 const tim_sort_run_t *stack, 903 const int stack_curr, 904 ref Vec!T storeBuf, 905 nogcComparisonFunction!T comparison) 906 { 907 const size_t A = stack[stack_curr - 2].length; 908 const size_t B = stack[stack_curr - 1].length; 909 const size_t curr = stack[stack_curr - 2].start; 910 911 size_t i, j, k; 912 913 size_t minSize = (A < B) ? A : B; 914 915 storeBuf.resize( minSize ); 916 T* storage = storeBuf.ptr; 917 918 /* left merge */ 919 if (A < B) { 920 memcpy(storage, &dst[curr], A * T.sizeof); 921 i = 0; 922 j = curr + A; 923 924 for (k = curr; k < curr + A + B; k++) { 925 if ((i < A) && (j < curr + A + B)) { 926 if (comparison(storage[i], dst[j]) <= 0) { 927 dst[k] = storage[i++]; 928 } else { 929 dst[k] = dst[j++]; 930 } 931 } else if (i < A) { 932 dst[k] = storage[i++]; 933 } else { 934 break; 935 } 936 } 937 } else { 938 /* right merge */ 939 memcpy(storage, &dst[curr + A], B * T.sizeof); 940 i = B; 941 j = curr + A; 942 k = curr + A + B; 943 944 while (k > curr) { 945 k--; 946 if ((i > 0) && (j > curr)) { 947 if (comparison(dst[j - 1], storage[i - 1]) > 0) { 948 dst[k] = dst[--j]; 949 } else { 950 dst[k] = storage[--i]; 951 } 952 } else if (i > 0) { 953 dst[k] = storage[--i]; 954 } else { 955 break; 956 } 957 } 958 } 959 } 960 961 static int tim_sort_collapse(T)(T *dst, 962 tim_sort_run_t *stack, 963 int stack_curr, 964 ref Vec!T storeBuf, 965 const size_t size, 966 nogcComparisonFunction!T comparison) nothrow @nogc 967 { 968 while (1) 969 { 970 size_t A, B, C, D; 971 int ABC, BCD, CD; 972 973 /* if the stack only has one thing on it, we are done with the collapse */ 974 if (stack_curr <= 1) { 975 break; 976 } 977 978 /* if this is the last merge, just do it */ 979 if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { 980 tim_sort_merge!T(dst, stack, stack_curr, storeBuf, comparison); 981 stack[0].length += stack[1].length; 982 stack_curr--; 983 break; 984 } 985 /* check if the invariant is off for a stack of 2 elements */ 986 else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { 987 tim_sort_merge!T(dst, stack, stack_curr, storeBuf, comparison); 988 stack[0].length += stack[1].length; 989 stack_curr--; 990 break; 991 } else if (stack_curr == 2) { 992 break; 993 } 994 995 B = stack[stack_curr - 3].length; 996 C = stack[stack_curr - 2].length; 997 D = stack[stack_curr - 1].length; 998 999 if (stack_curr >= 4) { 1000 A = stack[stack_curr - 4].length; 1001 ABC = (A <= B + C); 1002 } else { 1003 ABC = 0; 1004 } 1005 1006 BCD = (B <= C + D) || ABC; 1007 CD = (C <= D); 1008 1009 /* Both invariants are good */ 1010 if (!BCD && !CD) { 1011 break; 1012 } 1013 1014 /* left merge */ 1015 if (BCD && !CD) { 1016 tim_sort_merge!T(dst, stack, stack_curr - 1, storeBuf, comparison); 1017 stack[stack_curr - 3].length += stack[stack_curr - 2].length; 1018 stack[stack_curr - 2] = stack[stack_curr - 1]; 1019 stack_curr--; 1020 } else { 1021 /* right merge */ 1022 tim_sort_merge!T(dst, stack, stack_curr, storeBuf, comparison); 1023 stack[stack_curr - 2].length += stack[stack_curr - 1].length; 1024 stack_curr--; 1025 } 1026 } 1027 1028 return stack_curr; 1029 } 1030 1031 static int tim_sort_push_next(T)(T *dst, 1032 const size_t size, 1033 ref Vec!T storeBuf, 1034 const size_t minrun, 1035 tim_sort_run_t *run_stack, 1036 size_t *stack_curr, 1037 size_t *curr, 1038 nogcComparisonFunction!T comparison) 1039 { 1040 size_t len = tim_sort_count_run!T(dst, *curr, size, comparison); 1041 size_t run = minrun; 1042 1043 if (run > size - *curr) { 1044 run = size - *curr; 1045 } 1046 1047 if (run > len) { 1048 tim_sort_binary_inversion_sort_start!T(&dst[*curr], len, run, comparison); 1049 len = run; 1050 } 1051 1052 run_stack[*stack_curr].start = *curr; 1053 run_stack[*stack_curr].length = len; 1054 (*stack_curr)++; 1055 *curr += len; 1056 1057 if (*curr == size) { 1058 /* finish up */ 1059 while (*stack_curr > 1) { 1060 tim_sort_merge!T(dst, run_stack, cast(int) *stack_curr, storeBuf, comparison); 1061 run_stack[*stack_curr - 2].length += run_stack[*stack_curr - 1].length; 1062 (*stack_curr)--; 1063 } 1064 1065 return 0; 1066 } 1067 1068 return 1; 1069 } 1070 } 1071 1072 1073 /** 1074 $(H1 @nogc Simple Base64 parsing) 1075 1076 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 1077 Authors: Harrison Ford 1078 Copyright: 2021 Harrison Ford, Symmetry Investments, 2023 Guillaume Piolat 1079 */ 1080 // this is from mir.base64 but a bit stripped down. 1081 1082 private 1083 { 1084 1085 // NOTE: I do not know if this would work on big-endian systems. 1086 // Needs further testing to figure out if it *does* work on them. 1087 1088 // Technique borrowed from: 1089 // http://0x80.pl/notesen/2016-01-12-sse-base64-encoding.html#branchless-code-for-lookup-table 1090 ubyte lookup_encoding(ubyte i, char plusChar = '+', char slashChar = '/') 1091 pure nothrow @nogc @safe 1092 { 1093 assert(i < 64); 1094 1095 ubyte shift; 1096 1097 if (i < 26) 1098 { 1099 // range A-Z 1100 shift = 'A'; 1101 } 1102 else if (i >= 26 && i < 52) 1103 { 1104 // range a-z 1105 shift = 'a' - 26; 1106 } 1107 else if (i >= 52 && i < 62) 1108 { 1109 // range 0-9 1110 shift = cast(ubyte)('0' - 52); 1111 } 1112 else if (i == 62) 1113 { 1114 // character plus 1115 shift = cast(ubyte)(plusChar - 62); 1116 } 1117 else if (i == 63) 1118 { 1119 // character slash 1120 shift = cast(ubyte)(slashChar - 63); 1121 } 1122 1123 return cast(char)(i + shift); 1124 } 1125 1126 // Do the inverse of above (convert an ASCII value into the Base64 character set) 1127 ubyte lookup_decoding(ubyte i, char plusChar, char slashChar, bool* err) 1128 pure nothrow @nogc @safe 1129 { 1130 *err = false; 1131 // Branching bad, but this isn't performance sensitive 1132 if (i <= 'Z' && i >= 'A') { 1133 return cast(ubyte)(i - 'A'); 1134 } 1135 else if (i <= 'z' && i >= 'a') { 1136 return cast(ubyte)(i - 'a' + 26); 1137 } 1138 else if (i <= '9' && i >= '0') { 1139 return cast(ubyte)(i - '0' + 52); 1140 } 1141 else if (i == plusChar) { 1142 return 62; 1143 } 1144 else if (i == slashChar) { 1145 return 63; 1146 } 1147 // Just return 0 for padding, 1148 // as it typically means nothing. 1149 else if (i == '=') { 1150 return 0; 1151 } 1152 else 1153 { 1154 *err = true; 1155 return 0; 1156 } 1157 } 1158 } 1159 1160 /// Decode a Base64 encoded value, returning a buffer to be freed with free(). 1161 /// `null` in case of error or zero size. 1162 ubyte[] decodeBase64(scope const(ubyte)[] data, char plusChar = '+', char slashChar = '/') 1163 nothrow @nogc @system 1164 { 1165 Vec!ubyte outBuffer; 1166 bool err; 1167 decodeBase64(data, outBuffer, plusChar, slashChar, &err); 1168 if (err) 1169 return null; 1170 return outBuffer.releaseData; 1171 } 1172 ///ditto 1173 ubyte[] decodeBase64(scope const(char)[] data, char plusChar = '+', char slashChar = '/') 1174 nothrow @nogc @system 1175 { 1176 return decodeBase64(cast(const(ubyte)[])data, plusChar, slashChar); 1177 } 1178 1179 /// Decode a Base64 encoded value, appending the result onto a `Vec!ubyte`. 1180 /// Reusing the same `Vec!ubyte` allows you to avoid reallocations. 1181 /// Note: `err` must point to a `bool` and cannot be null. Ugly signature sorry. 1182 void decodeBase64(scope const(ubyte)[] data, 1183 ref Vec!ubyte outBuffer, 1184 char plusChar, // typically: '+' 1185 char slashChar, // typically: '/' 1186 bool* err) nothrow @nogc @safe 1187 { 1188 outBuffer.clearContents(); 1189 *err = false; 1190 // We expect data should be well-formed (with padding), 1191 // so we should throw if it is not well-formed. 1192 if (data.length % 4 != 0) 1193 { 1194 *err = true; 1195 return; 1196 } 1197 1198 ubyte[3] decodedByteGroup; 1199 ubyte sz = 0; 1200 1201 for (size_t i = 0; i < data.length; i += 4) 1202 { 1203 scope const(ubyte)[] group = data[i .. (i + 4)]; 1204 1205 ubyte[4] decodedBytes; 1206 decodedBytes[0] = lookup_decoding(group[0], plusChar, slashChar, err); if (*err) return; 1207 decodedBytes[1] = lookup_decoding(group[1], plusChar, slashChar, err); if (*err) return; 1208 1209 uint transformed_group = (decodedBytes[0] << 26) | (decodedBytes[1] << 20); 1210 1211 // According to RFC4648 Section 3.3, we don't have to accept extra padding characters, 1212 // and we can safely throw (and stay within spec). 1213 // x=== is also invalid, so we can just throw on that here. 1214 if (group[0] == '=' || group[1] == '=') 1215 { 1216 *err = true; 1217 return; 1218 } 1219 1220 // xx=(=)? 1221 if (group[2] == '=') 1222 { 1223 // If we are not at the end of a string, according to RFC4648, 1224 // we can safely treat a padding character as "non-alphabet data", 1225 // and as such, we should throw. See RFC4648 Section 3.3 for more information 1226 if ((i / 4) != ((data.length / 4) - 1)) 1227 { 1228 *err = true; 1229 return; 1230 } 1231 1232 if (group[3] == '=') 1233 { 1234 // xx== 1235 sz = 1; 1236 } 1237 // xx=x (invalid) 1238 // Padding should not be in the middle of a chunk 1239 else 1240 { 1241 *err = true; 1242 return; 1243 } 1244 } 1245 // xxx= 1246 else if (group[3] == '=') 1247 { 1248 // If we are not at the end of a string, according to RFC4648, 1249 // we can safely treat a padding character as "non-alphabet data", 1250 // and as such, we should throw. See RFC4648 Section 3.3 for more information 1251 if ((i / 4) != ((data.length / 4) - 1)) 1252 { 1253 *err = true; 1254 return; 1255 } 1256 1257 decodedBytes[2] = lookup_decoding(group[2], plusChar, slashChar, err); if (*err) return; 1258 transformed_group |= (decodedBytes[2] << 14); 1259 sz = 2; 1260 } 1261 // xxxx 1262 else 1263 { 1264 decodedBytes[2] = lookup_decoding(group[2], plusChar, slashChar, err); if (*err) return; 1265 decodedBytes[3] = lookup_decoding(group[3], plusChar, slashChar, err); if (*err) return; 1266 transformed_group |= ((decodedBytes[2] << 14) | (decodedBytes[3] << 8)); 1267 sz = 3; 1268 } 1269 1270 decodedByteGroup[0] = (transformed_group >> 24) & 0xff; 1271 decodedByteGroup[1] = (transformed_group >> 16) & 0xff; 1272 decodedByteGroup[2] = (transformed_group >> 8) & 0xff; 1273 1274 // Only emit the transformed bytes that we got data for. 1275 outBuffer.pushBack(decodedByteGroup[0 .. sz]); 1276 } 1277 } 1278 1279 /// Test decoding of data which has a length which can be 1280 /// cleanly decoded. 1281 @trusted unittest 1282 { 1283 // Note: the decoded strings are leaked in this test. 1284 assert("QUJD".decodeBase64 == "ABC"); 1285 assert("QQ==".decodeBase64 == "A"); 1286 assert("YSBiIGMgZCBlIGYgZyBoIGkgaiBrIGwgbSBuIG8gcCBxIHIgcyB0IHUgdiB3IHggeSB6".decodeBase64 1287 == "a b c d e f g h i j k l m n o p q r s t u v w x y z"); 1288 assert("LCAuIDsgLyBbICcgXSBcID0gLSAwIDkgOCA3IDYgNSA0IDMgMiAxIGAgfiAhIEAgIyAkICUgXiAmICogKCApIF8gKyB8IDogPCA+ID8=" 1289 .decodeBase64 == ", . ; / [ ' ] \\ = - 0 9 8 7 6 5 4 3 2 1 ` ~ ! @ # $ % ^ & * ( ) _ + | : < > ?"); 1290 assert("AAA=".decodeBase64 == "\x00\x00"); 1291 assert("AAAABBCC".decodeBase64 == "\x00\x00\x00\x04\x10\x82"); 1292 assert("AA==".decodeBase64 == "\x00"); 1293 assert("AA/=".decodeBase64 == "\x00\x0f"); 1294 } 1295 1296 /// Test decoding invalid data 1297 @trusted unittest 1298 { 1299 void testFail(const(char)[] input) @trusted 1300 { 1301 ubyte[] decoded = input.decodeBase64; 1302 assert(decoded is null); 1303 free(decoded.ptr); 1304 } 1305 1306 testFail("===A"); 1307 testFail("A="); 1308 testFail("AA="); 1309 testFail("A=AA"); 1310 testFail("AA=A"); 1311 testFail("AA=A===="); 1312 testFail("=AAA"); 1313 testFail("AAA=QUJD"); 1314 // This fails because we don't allow extra padding (than what is necessary) 1315 testFail("AA======"); 1316 // This fails because we don't allow padding before the end of the string (otherwise we'd 1317 // have a side-channel) 1318 testFail("QU==QUJD"); 1319 testFail("QU======QUJD"); 1320 // Invalid data that's out of the alphabet 1321 testFail("!@##@@!@"); 1322 } 1323 1324 1325 /// Encode a ubyte array as Base64, returning the encoded value, which shall be destroyed with 1326 /// `free`. 1327 ubyte[] encodeBase64(scope const(ubyte)[] buf, char plusChar = '+', char slashChar = '/') 1328 nothrow @nogc @system 1329 { 1330 Vec!ubyte outBuf; 1331 encodeBase64(buf, outBuf, plusChar, slashChar); 1332 return outBuf.releaseData; 1333 } 1334 1335 /// Encode a ubyte array as Base64, placing the result onto an `Vec!ubyte`. 1336 void encodeBase64(scope const(ubyte)[] input, 1337 scope ref Vec!ubyte outBuf, 1338 char plusChar = '+', 1339 char slashChar = '/') nothrow @nogc @trusted 1340 { 1341 outBuf.clearContents(); 1342 1343 // Slice our input array so that n % 3 == 0 (we have a multiple of 3) 1344 // If we have less then 3, then this is effectively a no-op (will result in a 0-length slice) 1345 ubyte[4] encodedByteGroup; 1346 const(ubyte)[] window = input[0 .. input.length - (input.length % 3)]; 1347 assert((window.length % 3) == 0); 1348 1349 for (size_t n = 0; n < window.length; n += 3) 1350 { 1351 uint group = (window[n] << 24) | (window[n+1] << 16) | (window[n+2] << 8); 1352 const(ubyte) a = (group >> 26) & 0x3f; 1353 const(ubyte) b = (group >> 20) & 0x3f; 1354 const(ubyte) c = (group >> 14) & 0x3f; 1355 const(ubyte) d = (group >> 8) & 0x3f; 1356 encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); 1357 encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); 1358 encodedByteGroup[2] = c.lookup_encoding(plusChar, slashChar); 1359 encodedByteGroup[3] = d.lookup_encoding(plusChar, slashChar); 1360 outBuf.pushBack(encodedByteGroup[]); 1361 } 1362 1363 // If it's a clean multiple of 3, then it requires no padding. 1364 // If not, then we need to add padding. 1365 if (input.length % 3 != 0) 1366 { 1367 window = input[window.length .. input.length]; 1368 1369 uint group = (window[0] << 24); 1370 1371 if (window.length == 1) { 1372 const(ubyte) a = (group >> 26) & 0x3f; 1373 const(ubyte) b = (group >> 20) & 0x3f; 1374 encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); 1375 encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); 1376 encodedByteGroup[2] = '='; 1377 encodedByteGroup[3] = '='; 1378 } 1379 else 1380 { 1381 // Just in case 1382 assert(window.length == 2); 1383 1384 group |= (window[1] << 16); 1385 const(ubyte) a = (group >> 26) & 0x3f; 1386 const(ubyte) b = (group >> 20) & 0x3f; 1387 const(ubyte) c = (group >> 14) & 0x3f; 1388 encodedByteGroup[0] = a.lookup_encoding(plusChar, slashChar); 1389 encodedByteGroup[1] = b.lookup_encoding(plusChar, slashChar); 1390 encodedByteGroup[2] = c.lookup_encoding(plusChar, slashChar); 1391 encodedByteGroup[3] = '='; 1392 } 1393 1394 outBuf.pushBack(encodedByteGroup[]); 1395 } 1396 } 1397 1398 @trusted unittest 1399 { 1400 // Note: encoded data leaked there. 1401 // 3 bytes 1402 { 1403 enum data = cast(immutable(ubyte)[])"ABC"; 1404 assert(data.encodeBase64 == "QUJD"); 1405 } 1406 1407 // 6 bytes 1408 { 1409 enum data = cast(immutable(ubyte)[])"ABCDEF"; 1410 assert(data.encodeBase64 == "QUJDREVG"); 1411 } 1412 1413 // 9 bytes 1414 { 1415 enum data = cast(immutable(ubyte)[])"ABCDEFGHI"; 1416 assert(data.encodeBase64 == "QUJDREVGR0hJ"); 1417 } 1418 1419 // 12 bytes 1420 { 1421 enum data = cast(immutable(ubyte)[])"ABCDEFGHIJKL"; 1422 assert(data.encodeBase64 == "QUJDREVGR0hJSktM"); 1423 } 1424 } 1425 1426 /// Test encoding of data which has a length which CANNOT be cleanly encoded. 1427 /// This typically means that there's padding. 1428 @trusted unittest 1429 { 1430 // Note: encoded data leaked there. 1431 // 1 byte 1432 { 1433 enum data = cast(immutable(ubyte)[])"A"; 1434 assert(data.encodeBase64 == "QQ=="); 1435 } 1436 // 2 bytes 1437 { 1438 enum data = cast(immutable(ubyte)[])"AB"; 1439 assert(data.encodeBase64 == "QUI="); 1440 } 1441 // 2 bytes 1442 { 1443 enum data = [0xFF, 0xFF]; 1444 assert(data.encodeBase64 == "//8="); 1445 } 1446 // 4 bytes 1447 { 1448 enum data = [0xDE, 0xAD, 0xBA, 0xBE]; 1449 assert(data.encodeBase64 == "3q26vg=="); 1450 } 1451 // 37 bytes 1452 { 1453 enum data = cast(immutable(ubyte)[])"A Very Very Very Very Large Test Blob"; 1454 assert(data.encodeBase64 == "QSBWZXJ5IFZlcnkgVmVyeSBWZXJ5IExhcmdlIFRlc3QgQmxvYg=="); 1455 } 1456 } 1457 1458 /// Test nogc encoding 1459 @trusted unittest 1460 { 1461 { 1462 1463 enum data = cast(immutable(ubyte)[])"A Very Very Very Very Large Test Blob"; 1464 Vec!ubyte outBuf; 1465 data.encodeBase64(outBuf); 1466 assert(outBuf[] == "QSBWZXJ5IFZlcnkgVmVyeSBWZXJ5IExhcmdlIFRlc3QgQmxvYg=="); 1467 } 1468 1469 { 1470 enum data = cast(immutable(ubyte)[])"abc123!?$*&()'-=@~"; 1471 Vec!ubyte outBuf; 1472 data.encodeBase64(outBuf); 1473 assert(outBuf[] == "YWJjMTIzIT8kKiYoKSctPUB+"); 1474 } 1475 } 1476 1477 /// Make sure we can decode what we encode. 1478 @trusted unittest 1479 { 1480 // Test an example string 1481 { 1482 enum data = cast(immutable(ubyte)[])"abc123!?$*&()'-=@~"; 1483 assert(data.encodeBase64.decodeBase64 == data); 1484 } 1485 // Test an example from Ion data 1486 { 1487 enum data = cast(immutable(ubyte)[])"a b c d e f g h i j k l m n o p q r s t u v w x y z"; 1488 assert(data.encodeBase64.decodeBase64 == data); 1489 } 1490 }