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, Auburn Sounds. 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.string: strdup, memcpy; 23 import core.stdc.stdlib: malloc, free, getenv; 24 import core.memory: GC; 25 import core.exception: onOutOfMemoryErrorNoGC; 26 27 import std.conv: emplace; 28 import std.traits; 29 import std.array: empty; 30 import std.exception: assumeUnique; 31 32 // This module provides many utilities to deal with @nogc 33 34 version = doNotUseRuntime; 35 36 // 37 // Faking @nogc 38 // 39 40 auto assumeNoGC(T) (T t) 41 { 42 static if (isFunctionPointer!T || isDelegate!T) 43 { 44 enum attrs = functionAttributes!T | FunctionAttribute.nogc; 45 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 46 } 47 else 48 static assert(false); 49 } 50 51 auto assumeNothrowNoGC(T) (T t) 52 { 53 static if (isFunctionPointer!T || isDelegate!T) 54 { 55 enum attrs = functionAttributes!T | FunctionAttribute.nogc | FunctionAttribute.nothrow_; 56 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 57 } 58 else 59 static assert(false); 60 } 61 62 unittest 63 { 64 void funcThatDoesGC() 65 { 66 int a = 4; 67 int[] b = [a, a, a]; 68 } 69 70 void anotherFunction() nothrow @nogc 71 { 72 assumeNothrowNoGC( (){ funcThatDoesGC(); } )(); 73 } 74 75 void aThirdFunction() @nogc 76 { 77 assumeNoGC( () { funcThatDoesGC(); } )(); 78 } 79 } 80 81 82 // 83 // Optimistic .destroy, which is @nogc nothrow by breaking the type-system 84 // 85 86 // for classes 87 void destroyNoGC(T)(T x) nothrow @nogc if (is(T == class) || is(T == interface)) 88 { 89 assumeNothrowNoGC( 90 (T x) 91 { 92 return destroy(x); 93 })(x); 94 } 95 96 // for struct 97 void destroyNoGC(T)(ref T obj) nothrow @nogc if (is(T == struct)) 98 { 99 assumeNothrowNoGC( 100 (ref T x) 101 { 102 return destroy(x); 103 })(obj); 104 } 105 /* 106 void destroyNoGC(T : U[n], U, size_t n)(ref T obj) nothrow @nogc 107 { 108 assumeNothrowNoGC( 109 (T x) 110 { 111 return destroy(x); 112 })(obj); 113 }*/ 114 115 void destroyNoGC(T)(ref T obj) nothrow @nogc 116 if (!is(T == struct) && !is(T == class) && !is(T == interface)) 117 { 118 assumeNothrowNoGC( 119 (ref T x) 120 { 121 return destroy(x); 122 })(obj); 123 } 124 125 126 127 // 128 // Constructing and destroying without the GC. 129 // 130 131 /// Allocates and construct a struct or class object. 132 /// Returns: Newly allocated object. 133 auto mallocEmplace(T, Args...)(Args args) 134 { 135 static if (is(T == class)) 136 immutable size_t allocSize = __traits(classInstanceSize, T); 137 else 138 immutable size_t allocSize = T.sizeof; 139 140 void* rawMemory = malloc(allocSize); 141 if (!rawMemory) 142 onOutOfMemoryErrorNoGC(); 143 144 version(doNotUseRuntime) 145 { 146 } 147 else 148 { 149 static if (hasIndirections!T) 150 GC.addRange(rawMemory, allocSize); 151 } 152 153 static if (is(T == class)) 154 { 155 T obj = emplace!T(rawMemory[0 .. allocSize], args); 156 } 157 else 158 { 159 T* obj = cast(T*)rawMemory; 160 emplace!T(obj, args); 161 } 162 163 return obj; 164 } 165 166 /// Destroys and frees a class object created with $(D mallocEmplace). 167 void destroyFree(T)(T p) if (is(T == class)) 168 { 169 if (p !is null) 170 { 171 destroyNoGC(p); 172 173 version(doNotUseRuntime) 174 { 175 } 176 else 177 { 178 static if (hasIndirections!T) 179 GC.removeRange(cast(void*)p); 180 } 181 182 free(cast(void*)p); 183 } 184 } 185 186 /// Destroys and frees an interface object created with $(D mallocEmplace). 187 void destroyFree(T)(T p) if (is(T == interface)) 188 { 189 if (p !is null) 190 { 191 void* here = cast(void*)(cast(Object)p); 192 destroyNoGC(p); 193 194 version(doNotUseRuntime) 195 { 196 } 197 else 198 { 199 static if (hasIndirections!T) 200 GC.removeRange(here); 201 } 202 203 free(cast(void*)here); 204 } 205 } 206 207 /// Destroys and frees a non-class object created with $(D mallocEmplace). 208 void destroyFree(T)(T* p) if (!is(T == class)) 209 { 210 if (p !is null) 211 { 212 destroyNoGC(p); 213 214 version(doNotUseRuntime) 215 { 216 } 217 else 218 { 219 static if (hasIndirections!T) 220 GC.removeRange(cast(void*)p); 221 } 222 223 free(cast(void*)p); 224 } 225 } 226 227 228 unittest 229 { 230 class A 231 { 232 int _i; 233 this(int i) 234 { 235 _i = i; 236 } 237 } 238 239 struct B 240 { 241 int i; 242 } 243 244 void testMallocEmplace() 245 { 246 A a = mallocEmplace!A(4); 247 destroyFree(a); 248 249 B* b = mallocEmplace!B(5); 250 destroyFree(b); 251 } 252 253 testMallocEmplace(); 254 } 255 256 version( D_InlineAsm_X86 ) 257 { 258 version = AsmX86; 259 } 260 else version( D_InlineAsm_X86_64 ) 261 { 262 version = AsmX86; 263 } 264 265 /// Allocates a slice with `malloc`. 266 /// This does not add GC roots so when using the runtime do not use such slice as traceable. 267 T[] mallocSlice(T)(size_t count) nothrow @nogc 268 { 269 T[] slice = mallocSliceNoInit!T(count); 270 static if (is(T == struct)) 271 { 272 // we must avoid calling struct destructors with uninitialized memory 273 for(size_t i = 0; i < count; ++i) 274 { 275 T uninitialized; 276 memcpy(&slice[i], &uninitialized, T.sizeof); 277 } 278 } 279 else 280 slice[0..count] = T.init; 281 return slice; 282 } 283 284 /// Allocates a slice with `malloc`, but does not initialize the content. 285 /// This does not add GC roots so when using the runtime do not use such slice as traceable. 286 T[] mallocSliceNoInit(T)(size_t count) nothrow @nogc 287 { 288 T* p = cast(T*) malloc(count * T.sizeof); 289 return p[0..count]; 290 } 291 292 /// Free a slice allocated with `mallocSlice`. 293 void freeSlice(T)(const(T)[] slice) nothrow @nogc 294 { 295 free(cast(void*)(slice.ptr)); // const cast here 296 } 297 298 // Duplicates a slice with malloc. Equivalent to .dup 299 T[] mallocDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct)) 300 { 301 T[] copy = mallocSliceNoInit!T(slice.length); 302 memcpy(copy.ptr, slice.ptr, slice.length * T.sizeof); 303 return copy; 304 } 305 306 // Duplicates a slice with malloc. Equivalent to .idup 307 immutable(T)[] mallocIDup(T)(const(T)[] slice) nothrow @nogc if (!is(T == struct)) 308 { 309 T[] copy = mallocSliceNoInit!T(slice.length); 310 memcpy(copy.ptr, slice.ptr, slice.length * T.sizeof); 311 return assumeUnique(copy); 312 } 313 314 unittest 315 { 316 int[] slice = mallocSlice!int(4); 317 freeSlice(slice); 318 assert(slice[3] == int.init); 319 320 slice = mallocSliceNoInit!int(4); 321 freeSlice(slice); 322 323 slice = mallocSliceNoInit!int(0); 324 assert(slice == []); 325 freeSlice(slice); 326 } 327 328 329 // 330 // GC-proof resources: for when the GC does exist. 331 // 332 333 /// Destructors called by the GC enjoy a variety of limitations and 334 /// relying on them is dangerous. 335 /// See_also: $(WEB p0nce.github.io/d-idioms/#The-trouble-with-class-destructors) 336 /// Example: 337 /// --- 338 /// class Resource 339 /// { 340 /// ~this() 341 /// { 342 /// if (!alreadyClosed) 343 /// { 344 /// if (isCalledByGC()) 345 /// assert(false, "Resource release relies on Garbage Collection"); 346 /// alreadyClosed = true; 347 /// releaseResource(); 348 /// } 349 /// } 350 /// } 351 /// --- 352 bool isCalledByGC() nothrow 353 { 354 import core.exception; 355 try 356 { 357 import core.memory; 358 cast(void) GC.malloc(1); // not ideal since it allocates 359 return false; 360 } 361 catch(InvalidMemoryOperationError e) 362 { 363 return true; 364 } 365 } 366 367 unittest 368 { 369 class A 370 { 371 ~this() 372 { 373 assert(!isCalledByGC()); 374 } 375 } 376 import std.typecons; 377 auto a = scoped!A(); 378 } 379 380 381 382 383 // 384 // @nogc sorting. 385 // 386 387 /// Must return -1 if a < b 388 /// 0 if a == b 389 /// 1 if a > b 390 alias nogcComparisonFunction(T) = int delegate(in T a, in T b) nothrow @nogc; 391 392 /// @nogc quicksort 393 /// From the excellent: http://codereview.stackexchange.com/a/77788 394 void quicksort(T)(T[] array, nogcComparisonFunction!T comparison) nothrow @nogc 395 { 396 if (array.length < 2) 397 return; 398 399 static void swapElem(ref T lhs, ref T rhs) 400 { 401 T tmp = lhs; 402 lhs = rhs; 403 rhs = tmp; 404 } 405 406 int partition(T* arr, int left, int right) nothrow @nogc 407 { 408 immutable int mid = left + (right - left) / 2; 409 T pivot = arr[mid]; 410 // move the mid point value to the front. 411 swapElem(arr[mid],arr[left]); 412 int i = left + 1; 413 int j = right; 414 while (i <= j) 415 { 416 while(i <= j && comparison(arr[i], pivot) <= 0 ) 417 i++; 418 419 while(i <= j && comparison(arr[j], pivot) > 0) 420 j--; 421 422 if (i < j) 423 swapElem(arr[i], arr[j]); 424 } 425 swapElem(arr[i - 1], arr[left]); 426 return i - 1; 427 } 428 429 void doQsort(T* array, int left, int right) nothrow @nogc 430 { 431 if (left >= right) 432 return; 433 434 int part = partition(array, left, right); 435 doQsort(array, left, part - 1); 436 doQsort(array, part + 1, right); 437 } 438 439 doQsort(array.ptr, 0, cast(int)(array.length) - 1); 440 } 441 442 unittest 443 { 444 int[] testData = [110, 5, 10, 3, 22, 100, 1, 23]; 445 quicksort!int(testData, (a, b) => (a - b)); 446 assert(testData == [1, 3, 5, 10, 22, 23, 100, 110]); 447 } 448 449 450 // 451 // STABLE MERGE SORT 452 // 453 454 // Stable merge sort, using a temporary array. 455 // Array A[] has the items to sort. 456 // Array B[] is a work array. 457 void mergeSort(T)(T[] inoutElements, T[] scratchBuffer, nogcComparisonFunction!T comparison) nothrow @nogc 458 { 459 // Left source half is A[ iBegin:iMiddle-1]. 460 // Right source half is A[iMiddle:iEnd-1 ]. 461 // Result is B[ iBegin:iEnd-1 ]. 462 void topDownMerge(T)(T* A, int iBegin, int iMiddle, int iEnd, T* B) nothrow @nogc 463 { 464 int i = iBegin; 465 int j = iMiddle; 466 467 // While there are elements in the left or right runs... 468 for (int k = iBegin; k < iEnd; k++) 469 { 470 // If left run head exists and is <= existing right run head. 471 if ( i < iMiddle && ( j >= iEnd || (comparison(A[i], A[j]) <= 0) ) ) 472 { 473 B[k] = A[i]; 474 i = i + 1; 475 } 476 else 477 { 478 B[k] = A[j]; 479 j = j + 1; 480 } 481 } 482 } 483 484 // Sort the given run of array A[] using array B[] as a source. 485 // iBegin is inclusive; iEnd is exclusive (A[iEnd] is not in the set). 486 void topDownSplitMerge(T)(T* B, int iBegin, int iEnd, T* A) nothrow @nogc 487 { 488 if(iEnd - iBegin < 2) // if run size == 1 489 return; // consider it sorted 490 // split the run longer than 1 item into halves 491 int iMiddle = (iEnd + iBegin) / 2; // iMiddle = mid point 492 // recursively sort both runs from array A[] into B[] 493 topDownSplitMerge!T(A, iBegin, iMiddle, B); // sort the left run 494 topDownSplitMerge!T(A, iMiddle, iEnd, B); // sort the right run 495 // merge the resulting runs from array B[] into A[] 496 topDownMerge!T(B, iBegin, iMiddle, iEnd, A); 497 } 498 499 assert(inoutElements.length == scratchBuffer.length); 500 int n = cast(int)inoutElements.length; 501 scratchBuffer[] = inoutElements[]; // copy data into temporary buffer 502 topDownSplitMerge(scratchBuffer.ptr, 0, n, inoutElements.ptr); 503 } 504 505 unittest 506 { 507 int[2][] scratch; 508 scratch.length = 8; 509 int[2][] testData = [[110, 0], [5, 0], [10, 0], [3, 0], [110, 1], [5, 1], [10, 1], [3, 1]]; 510 mergeSort!(int[2])(testData, scratch, (a, b) => (a[0] - b[0])); 511 assert(testData == [[3, 0], [3, 1], [5, 0], [5, 1], [10, 0], [10, 1], [110, 0], [110, 1]]); 512 } 513 514 515 516 // 517 // Unrelated things that hardly goes anywhere 518 // 519 520 /// Crash if the GC is running. 521 /// Useful in destructors to avoid reliance GC resource release. 522 /// However since this is not @nogc, this is not suitable in runtime-less D. 523 /// See_also: $(WEB p0nce.github.io/d-idioms/#GC-proof-resource-class) 524 void ensureNotInGC(string resourceName = null) nothrow 525 { 526 import core.exception; 527 try 528 { 529 import core.memory; 530 cast(void) GC.malloc(1); // not ideal since it allocates 531 return; 532 } 533 catch(InvalidMemoryOperationError e) 534 { 535 import core.stdc.stdio; 536 fprintf(stderr, "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", 537 resourceName ? resourceName.ptr : "a resource".ptr); 538 assert(false); // crash 539 } 540 } 541 542 543 544 /// To call for something that should never happen, but we still 545 /// want to make a "best effort" at runtime even if it can be meaningless. 546 /// MAYDO: change that name, it's not actually unrecoverable 547 /// MAYDO: stop using that function 548 void unrecoverableError() nothrow @nogc 549 { 550 debug 551 { 552 // Crash unconditionally 553 assert(false); 554 } 555 else 556 { 557 // There is a trade-off here, if we crash immediately we will be 558 // correctly identified by the user as the origin of the bug, which 559 // is always helpful. 560 // But crashing may in many-case also crash the host, which is not very friendly. 561 // Eg: a plugin not instancing vs host crashing. 562 // The reasoning is that the former is better from the user POV. 563 } 564 } 565 566 567 /// A bit faster than a dynamic cast. 568 /// This is to avoid TypeInfo look-up. 569 T unsafeObjectCast(T)(Object obj) 570 { 571 return cast(T)(cast(void*)(obj)); 572 } 573 574 575 /// Inserts a breakpoint instruction. useful to trigger the debugger. 576 void debugBreak() nothrow @nogc 577 { 578 version( AsmX86 ) 579 { 580 asm nothrow @nogc 581 { 582 int 3; 583 } 584 } 585 else version( GNU ) 586 { 587 // __builtin_trap() is not the same thing unfortunately 588 asm 589 { 590 "int $0x03" : : : ; 591 } 592 } 593 else 594 { 595 static assert(false, "No debugBreak() for this compiler"); 596 } 597 } 598 599 600 // Copy source into dest. 601 // dest must contain room for maxChars characters 602 // A zero-byte character is then appended. 603 void stringNCopy(char* dest, size_t maxChars, const(char)[] source) nothrow @nogc 604 { 605 if (maxChars == 0) 606 return; 607 608 size_t max = maxChars < source.length ? maxChars - 1 : source.length; 609 for (int i = 0; i < max; ++i) 610 dest[i] = source[i]; 611 dest[max] = '\0'; 612 } 613 614 615 // 616 // Low-cost C string conversions 617 // 618 alias CString = CStringImpl!char; 619 alias CString16 = CStringImpl!wchar; 620 621 /// Zero-terminated C string, to replace toStringz and toUTF16z 622 struct CStringImpl(CharType) if (is(CharType: char) || is(CharType: wchar)) 623 { 624 public: 625 nothrow: 626 @nogc: 627 628 const(CharType)* storage = null; 629 alias storage this; 630 631 632 this(const(CharType)[] s) 633 { 634 // Always copy. We can't assume anything about the input. 635 size_t len = s.length; 636 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 637 buffer[0..len] = s[0..len]; 638 buffer[len] = '\0'; 639 storage = buffer; 640 wasAllocated = true; 641 } 642 643 // The constructor taking immutable can safely assume that such memory 644 // has been allocated by the GC or malloc, or an allocator that align 645 // pointer on at least 4 bytes. 646 this(immutable(CharType)[] s) 647 { 648 // Same optimizations that for toStringz 649 if (s.empty) 650 { 651 enum emptyString = cast(CharType[])""; 652 storage = emptyString.ptr; 653 return; 654 } 655 656 /* Peek past end of s[], if it's 0, no conversion necessary. 657 * Note that the compiler will put a 0 past the end of static 658 * strings, and the storage allocator will put a 0 past the end 659 * of newly allocated char[]'s. 660 */ 661 const(CharType)* p = s.ptr + s.length; 662 // Is p dereferenceable? A simple test: if the p points to an 663 // address multiple of 4, then conservatively assume the pointer 664 // might be pointing to another block of memory, which might be 665 // unreadable. Otherwise, it's definitely pointing to valid 666 // memory. 667 if ((cast(size_t) p & 3) && *p == 0) 668 { 669 storage = s.ptr; 670 return; 671 } 672 673 size_t len = s.length; 674 CharType* buffer = cast(CharType*) malloc((len + 1) * CharType.sizeof); 675 buffer[0..len] = s[0..len]; 676 buffer[len] = '\0'; 677 storage = buffer; 678 wasAllocated = true; 679 } 680 681 ~this() 682 { 683 if (wasAllocated) 684 free(cast(void*)storage); 685 } 686 687 @disable this(this); 688 689 private: 690 bool wasAllocated = false; 691 } 692 693 694 // 695 // Launch browser, replaces std.process.browse 696 // 697 698 void browseNoGC(string url) nothrow @nogc 699 { 700 version(Windows) 701 { 702 import core.sys.windows.winuser; 703 import core.sys.windows.shellapi; 704 ShellExecuteA(null, CString("open").storage, CString(url).storage, null, null, SW_SHOWNORMAL); 705 } 706 707 version(OSX) 708 { 709 import core.sys.posix.unistd; 710 const(char)*[5] args; 711 712 auto curl = CString(url).storage; 713 const(char)* browser = getenv("BROWSER"); 714 if (browser) 715 { 716 browser = strdup(browser); 717 args[0] = browser; 718 args[1] = curl; 719 args[2] = null; 720 } 721 else 722 { 723 args[0] = "open".ptr; 724 args[1] = curl; 725 args[2] = null; 726 } 727 728 auto childpid = core.sys.posix.unistd.fork(); 729 if (childpid == 0) 730 { 731 core.sys.posix.unistd.execvp(args[0], cast(char**)args.ptr); 732 return; 733 } 734 if (browser) 735 free(cast(void*)browser); 736 } 737 } 738