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