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 }