1 /** 2 * Dynamic Array 3 * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module libwasm.rt.array; 9 import libwasm.rt.memory; 10 import libwasm.rt.allocator; 11 12 @safe: 13 nothrow: 14 15 /* an optimized version of DynamicArray when T is an pointer 16 * 17 * to reduce template bloat all PointerArray!T* are backed by a 18 * DynamicArray!(void*) 19 */ 20 struct PointerArray(T, Allocator = ThreadMemAllocator) if (is(T : U*, U)) 21 { 22 private DynamicArray!(void*, Allocator) array; 23 24 alias array this; 25 26 /// Slice operator overload 27 pragma(inline, true) auto opSlice(this This)() @nogc 28 { 29 return opSlice!(This)(0, l); 30 } 31 32 /// ditto 33 pragma(inline, true) auto opSlice(this This)(size_t a, size_t b) @nogc @trusted 34 { 35 alias ET = ContainerElementType!(This, T); 36 return cast(ET[]) array.arr[a .. b]; 37 } 38 39 /// Index operator overload 40 pragma(inline, true) auto opIndex(this This)(size_t i) @nogc 41 { 42 return cast(T) array[i]; 43 } 44 45 void insertBack(T value) 46 { 47 array.insertBack(cast(void*) value); 48 } 49 50 void shrinkTo(int idx) 51 { 52 array.shrinkTo(idx); 53 } 54 55 alias insert = insertBack; 56 57 /// ditto 58 alias insertAnywhere = insertBack; 59 60 /// ditto 61 alias put = insertBack; 62 63 /// Index assignment support 64 void opIndexAssign(T value, size_t i) @nogc 65 { 66 array.arr[i] = cast(void*) value; 67 } 68 69 /// Slice assignment support 70 void opSliceAssign(T value) @nogc 71 { 72 array.arr[0 .. l] = cast(void*) value; 73 } 74 75 /// ditto 76 void opSliceAssign(T value, size_t i, size_t j) @nogc 77 { 78 array.arr[i .. j] = cast(void*) value; 79 } 80 81 auto ref T front() pure @property @trusted 82 { 83 return cast(T) array.front(); 84 } 85 86 /// Returns: the back element of the DynamicArray. 87 auto ref T back() pure @property @trusted 88 { 89 return cast(T) array.back(); 90 } 91 92 size_t length() const nothrow pure @property @safe @nogc 93 { 94 return array.l; 95 } 96 /// Ditto 97 alias opDollar = length; 98 99 void remove(const size_t i) 100 { 101 array.remove(i); 102 } 103 } 104 /** 105 * Array that is able to grow itself when items are appended to it. Uses 106 * malloc/free/realloc to manage its storage. 107 * 108 * Params: 109 * T = the array element type 110 * Allocator = the allocator to use. Defaults to `Mallocator`. 111 */ 112 113 struct DynamicArray(T, Allocator = ThreadMemAllocator) 114 { 115 static Allocator allocator; 116 117 this(this) @disable; 118 119 /** 120 Returns the size in bytes of the state that needs to be allocated to hold an 121 object of type $(D T). $(D stateSize!T) is zero for $(D struct)s that are not 122 nested and have no nonstatic member variables. 123 */ 124 template stateSize(T) 125 { 126 import std.traits; 127 128 static if (is(T == class) || is(T == interface)) 129 enum stateSize = __traits(classInstanceSize, T); 130 else static if (is(T == struct) || is(T == union)) 131 enum stateSize = Fields!T.length || isNested!T ? T.sizeof : 0; 132 else static if (is(T == void)) 133 enum size_t stateSize = 0; 134 else 135 enum stateSize = T.sizeof; 136 } 137 138 static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $]))) 139 { 140 /// Either `const(T)` or `T`. 141 alias AppendT = const(T); 142 143 /// Either `const(typeof(this))` or `typeof(this)`. 144 alias AppendTypeOfThis = const(typeof(this)); 145 } 146 else 147 { 148 alias AppendT = T; 149 alias AppendTypeOfThis = typeof(this); 150 } 151 152 @trusted ~this() 153 { 154 if (arr is null) 155 return; 156 157 static if ((is(T == struct) || is(T == union)) 158 && __traits(hasMember, T, "__xdtor")) 159 { 160 foreach (ref item; arr[0 .. l]) 161 { 162 item.__xdtor(); 163 } 164 } 165 allocator.deallocate(arr); 166 } 167 168 /// Slice operator overload 169 pragma(inline, true) 170 auto opSlice(this This)() @nogc 171 { 172 return opSlice!(This)(0, l); 173 } 174 175 /// ditto 176 pragma(inline, true) 177 auto opSlice(this This)(size_t a, size_t b) @nogc 178 { 179 alias ET = ContainerElementType!(This, T); 180 return cast(ET[]) arr[a .. b]; 181 } 182 183 /// Index operator overload 184 pragma(inline, true) 185 auto opIndex(this This)(size_t i) @nogc 186 { 187 return opSlice!(This)(i, i + 1)[0]; 188 } 189 190 /** 191 * Inserts the given value into the end of the array. 192 */ 193 @trusted void insertBack(T value) 194 { 195 196 if (arr.length == 0) 197 { 198 immutable size_t c = 4; 199 void[] a = allocator.allocate(c * T.sizeof); 200 arr = cast(typeof(arr)) a; 201 } 202 else if (l >= arr.length) 203 { 204 immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 205 void[] a = cast(void[]) arr; 206 void[] b = allocator.reallocate(a, c * T.sizeof); 207 arr = cast(typeof(arr)) b; 208 } 209 import std.traits : hasElaborateAssign, hasElaborateDestructor; 210 211 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 212 { 213 // If a destructor is run before blit or assignment involves 214 // more than just a blit, ensure that arr[l] is in a valid 215 // state before assigning to it. 216 import core.stdc.string : memcpy, memset; 217 218 const init = typeid(T).initializer(); 219 if (init.ptr is null) // null pointer means initialize to 0s 220 (() @trusted => memset(arr.ptr + l, 0, T.sizeof))(); 221 else 222 (() @trusted => memcpy(arr.ptr + l, init.ptr, T.sizeof))(); 223 } 224 emplace(arr[l++], value); 225 } 226 227 /// ditto 228 alias insert = insertBack; 229 230 /// ditto 231 alias insertAnywhere = insertBack; 232 233 /// ditto 234 alias put = insertBack; 235 236 /** 237 * ~= operator overload 238 */ 239 scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~") 240 { 241 insert(value); 242 return this; 243 } 244 245 /** 246 * ~= operator overload for an array of items 247 */ 248 scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs) 249 if (op == "~" && !is(T == AppendT[])) 250 { 251 // Disabling checkForOverlap when this function is called from opBinary!"~" 252 // is not just for efficiency, but to avoid circular function calls that 253 // would prevent inference of @nogc, etc. 254 static if (checkForOverlap) 255 if ((()@trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)()) 256 { 257 // Special case where rhs is a slice of this array. 258 this = this ~ rhs; 259 return this; 260 } 261 reserve(l + rhs.length); 262 import std.traits : hasElaborateAssign, hasElaborateDestructor; 263 264 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 265 { 266 foreach (ref value; rhs) 267 emplace(arr[l++], value); 268 } 269 else 270 { 271 arr[l .. l + rhs.length] = rhs[0 .. rhs.length]; 272 l += rhs.length; 273 } 274 return this; 275 } 276 277 /// ditto 278 scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs) 279 if (op == "~") 280 { 281 return this ~= rhs.arr[0 .. rhs.l]; 282 } 283 284 /** 285 * ~ operator overload 286 */ 287 typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~") 288 { 289 typeof(this) ret; 290 ret.reserve(l + other.l); 291 ret.opOpAssign!("~", false)(arr[0 .. l]); 292 ret.opOpAssign!("~", false)(other.arr[0 .. other.l]); 293 return ret; 294 } 295 296 /// ditto 297 typeof(this) opBinary(string op)(AppendT[] values) if (op == "~") 298 { 299 typeof(this) ret; 300 ret.reserve(l + values.length); 301 ret.opOpAssign!("~", false)(arr[0 .. l]); 302 ret.opOpAssign!("~", false)(values); 303 return ret; 304 } 305 306 /** 307 * Ensures sufficient capacity to accommodate `n` elements. 308 */ 309 @trusted void reserve(size_t n) 310 { 311 if (arr.length >= n) 312 return; 313 if (arr.ptr is null) 314 { 315 size_t c = 4; 316 if (c < n) 317 c = n; 318 void[] a = allocator.allocate(c * T.sizeof); 319 arr = cast(typeof(arr)) a; 320 } 321 else 322 { 323 size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 324 if (c < n) 325 c = n; 326 void[] a = cast(void[]) arr; 327 allocator.reallocate(a, c * T.sizeof); 328 arr = cast(typeof(arr)) a; 329 } 330 } 331 332 static if (is(typeof({ T value; }))) // default construction is allowed 333 { 334 /** 335 * Change the array length. 336 * When growing, initialize new elements to the default value. 337 */ 338 void resize(size_t n) 339 { 340 resize(n, T.init); 341 } 342 } 343 344 /** 345 * Change the array length. 346 * When growing, initialize new elements to the given value. 347 */ 348 void resize(size_t n, T value) 349 { 350 if (arr.length < n) 351 reserve(n); 352 353 if (l < n) // Growing? 354 { 355 import std.traits : hasElaborateAssign, hasElaborateDestructor; 356 357 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 358 { 359 foreach (i; l .. n) 360 emplace(arr[l], value); 361 } 362 else 363 arr[l .. n] = value; 364 } 365 else 366 { 367 static if ((is(T == struct) || is(T == union)) 368 && __traits(hasMember, T, "__xdtor")) 369 { 370 foreach (i; n .. l) 371 arr[i].__xdtor(); 372 } 373 } 374 375 l = n; 376 } 377 378 /** 379 * Remove the item at the given index from the array. 380 */ 381 void remove(const size_t i) 382 { 383 assert(i < this.l); 384 auto next = i + 1; 385 while (next < this.l) 386 { 387 arr[next - 1] = arr[next]; 388 ++next; 389 } 390 391 --l; 392 static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) 393 { 394 arr[l].__xdtor(); 395 } 396 } 397 398 /** 399 * Removes the last element from the array. 400 */ 401 void removeBack() 402 { 403 assert(l > 0); 404 --l; 405 static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) 406 { 407 arr[l].__xdtor(); 408 } 409 } 410 411 void shrinkTo(const size_t nl) 412 { 413 assert(l >= nl); 414 static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) 415 { 416 foreach_reverse (i; nl .. l) 417 arr[i].__xdtor(); 418 } 419 420 l = nl; 421 } 422 423 /// Index assignment support 424 void opIndexAssign(T value, size_t i) @nogc 425 { 426 arr[i] = value; 427 } 428 429 /// Slice assignment support 430 void opSliceAssign(T value) @nogc 431 { 432 arr[0 .. l] = value; 433 } 434 435 /// ditto 436 void opSliceAssign(T value, size_t i, size_t j) @nogc 437 { 438 arr[i .. j] = value; 439 } 440 441 /// Returns: the number of items in the array 442 size_t length() const nothrow pure @property @safe @nogc 443 { 444 return l; 445 } 446 447 /// Ditto 448 alias opDollar = length; 449 450 /// Returns: whether or not the DynamicArray is empty. 451 bool empty() const nothrow pure @property @safe @nogc 452 { 453 return l == 0; 454 } 455 456 /** 457 * Returns: a slice to the underlying array. 458 * 459 * As the memory of the array may be freed, access to this array is 460 * highly unsafe. 461 */ 462 auto ptr(this This)() @nogc @property 463 { 464 alias ET = ContainerElementType!(This, T); 465 return cast(ET) arr.ptr; 466 } 467 468 /// Returns: the front element of the DynamicArray. 469 auto ref T front() pure @property 470 { 471 return arr[0]; 472 } 473 474 /// Returns: the back element of the DynamicArray. 475 auto ref T back() pure @property 476 { 477 return arr[l - 1]; 478 } 479 480 private: 481 482 @trusted static void emplace(ref ContainerStorageType!T target, ref AppendT source) 483 { 484 (cast(void[])((&target)[0 .. 1]))[] = cast(void[])((&source)[0 .. 1]); 485 static if (__traits(hasMember, T, "__xpostblit")) 486 target.__xpostblit(); 487 } 488 489 enum bool useGC = false; 490 ContainerStorageType!(T)[] arr; 491 size_t l; 492 } 493 494 template removePred(alias pred) 495 { 496 auto removePred(T)(ref DynamicArray!T arr) 497 { 498 size_t target = 0; 499 auto len = arr.l < arr.arr.length ? arr.l : arr.arr.length; 500 foreach (i; 0 .. len) 501 { 502 if (pred(arr.arr[i])) 503 { 504 static if ((is(T == struct) || is(T == union)) && __traits(hasMember, T, "__xdtor")) 505 { 506 arr.arr[l].__xdtor(); 507 } 508 continue; 509 } 510 else 511 { 512 arr.arr[target] = arr.arr[i]; 513 target++; 514 } 515 } 516 arr.l = target; 517 } 518 } 519 520 template ContainerStorageType(T) 521 { 522 import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor, 523 hasElaborateAssign, isBasicType, isDynamicArray, isPointer, Unqual; 524 import std.typecons : Rebindable; 525 526 static if (is(T == const) || is(T == immutable)) 527 { 528 static if (isBasicType!T || isDynamicArray!T || isPointer!T) 529 alias ContainerStorageType = Unqual!T; 530 else static if (is(T == class) || is(T == interface)) 531 alias ContainerStorageType = Rebindable!T; 532 else static if (is(T == struct)) 533 { 534 alias U = Unqual!T; 535 static if (hasElaborateAssign!U || hasElaborateCopyConstructor!U || hasElaborateDestructor!U) 536 static assert(false, "Cannot store " ~ T.stringof ~ " because of postblit, opAssign, or ~this"); 537 else 538 alias ContainerStorageType = U; 539 } 540 else 541 static assert(false, "Don't know how to handle type " ~ T.stringof); 542 } 543 else 544 alias ContainerStorageType = T; 545 } 546 547 /// 548 unittest 549 { 550 static assert(is(ContainerStorageType!(int) == int)); 551 static assert(is(ContainerStorageType!(const int) == int)); 552 } 553 554 /// 555 unittest 556 { 557 import std.typecons : Rebindable; 558 559 static assert(is(ContainerStorageType!(Object) == Object)); 560 static assert(is(ContainerStorageType!(const(Object)) == Rebindable!(const(Object)))); 561 } 562 563 /// 564 unittest 565 { 566 struct A 567 { 568 int foo; 569 } 570 571 struct B 572 { 573 void opAssign(typeof(this)) 574 { 575 this.foo *= 2; 576 } 577 578 int foo; 579 } 580 581 // A can be stored easily because it is plain data 582 static assert(is(ContainerStorageType!(A) == A)); 583 static assert(is(ContainerStorageType!(const(A)) == A)); 584 585 // const(B) cannot be stored in the container because of its 586 // opAssign. Casting away const could lead to some very unexpected 587 // behavior. 588 static assert(!is(typeof(ContainerStorageType!(const(B))))); 589 // Mutable B is not a problem 590 static assert(is(ContainerStorageType!(B) == B)); 591 592 // Arrays can be stored because the entire pointer-length pair is moved as 593 // a unit. 594 static assert(is(ContainerStorageType!(const(int[])) == const(int)[])); 595 } 596 597 /// 598 unittest 599 { 600 static assert(is(ContainerStorageType!(const(int*)) == const(int)*)); 601 } 602 603 template ContainerElementType(ContainerType, ElementType) 604 { 605 import std.traits : isMutable, hasIndirections, PointerTarget, isPointer, Unqual; 606 607 template ET(bool isConst, T) 608 { 609 static if (isPointer!ElementType) 610 { 611 enum PointerIsConst = is(ElementType == const); 612 enum PointerIsImmutable = is(ElementType == immutable); 613 enum DataIsConst = is(PointerTarget!(ElementType) == const); 614 enum DataIsImmutable = is(PointerTarget!(ElementType) == immutable); 615 static if (isConst) 616 { 617 static if (PointerIsConst) 618 alias ET = ElementType; 619 else static if (PointerIsImmutable) 620 alias ET = ElementType; 621 else 622 alias ET = const(PointerTarget!(ElementType))*; 623 } 624 else 625 { 626 static assert(DataIsImmutable, "An immutable container cannot reference const or mutable data"); 627 static if (PointerIsConst) 628 alias ET = immutable(PointerTarget!ElementType)*; 629 else 630 alias ET = ElementType; 631 } 632 } 633 else 634 { 635 static if (isConst) 636 { 637 static if (is(ElementType == immutable)) 638 alias ET = ElementType; 639 else 640 alias ET = const(Unqual!ElementType); 641 } 642 else 643 alias ET = immutable(Unqual!ElementType); 644 } 645 } 646 647 static if (isMutable!ContainerType) 648 alias ContainerElementType = ElementType; 649 else 650 { 651 static if (hasIndirections!ElementType) 652 alias ContainerElementType = ET!(is(ContainerType == const), ElementType); 653 else 654 alias ContainerElementType = ElementType; 655 } 656 } 657 658 struct StringAppender(Allocator) 659 { 660 DynamicArray!(char, Allocator) arr = void; 661 this(ref Allocator allocator) 662 { 663 arr = DynamicArray!(char, Allocator)(allocator); 664 } 665 666 alias arr this; 667 void put(string s) 668 { 669 foreach (c; s) 670 arr.put(c); 671 } 672 673 void put(char c) 674 { 675 arr.put(c); 676 } 677 678 void put(char[] cs) 679 { 680 foreach (c; cs) 681 arr.put(c); 682 } 683 } 684 685 @trusted string text(Allocator, T...)(ref Allocator allocator, T t) 686 { 687 auto app = StringAppender!(Allocator)(allocator); 688 write(app, t); 689 auto end = app.length; 690 return cast(string) app[0 .. end]; 691 } 692 693 @trusted string text(T...)(T t) 694 { 695 StringAppender!() app; 696 write(app, t); 697 auto end = app.length; 698 return cast(string) app[0 .. end]; 699 } 700 701 char[] unsignedToTempString()(ulong value, return scope char[] buf, uint radix = 10) @safe 702 { 703 if (radix < 2) // not a valid radix, just return an empty string 704 return buf[$ .. $]; 705 706 size_t i = buf.length; 707 do 708 { 709 if (value < radix) 710 { 711 ubyte x = cast(ubyte) value; 712 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 713 break; 714 } 715 else 716 { 717 ubyte x = cast(ubyte)(value % radix); 718 value = value / radix; 719 buf[--i] = cast(char)((x < 10) ? x + '0' : x - 10 + 'a'); 720 } 721 } 722 while (value); 723 return buf[i .. $]; 724 } 725 726 private struct TempStringNoAlloc 727 { 728 // need to handle 65 bytes for radix of 2 with negative sign. 729 private char[65] _buf = 0; 730 private ubyte _len; 731 auto get() return 732 { 733 return _buf[$ - _len .. $]; 734 } 735 736 alias get this; 737 } 738 739 auto unsignedToTempString()(ulong value, uint radix = 10) @safe 740 { 741 TempStringNoAlloc result = void; 742 result._len = unsignedToTempString(value, result._buf, radix).length & 0xff; 743 return result; 744 } 745 746 char[] signedToTempString(long value, return scope char[] buf, uint radix = 10) @safe 747 { 748 bool neg = value < 0; 749 if (neg) 750 value = cast(ulong)-value; 751 auto r = unsignedToTempString(value, buf, radix); 752 if (neg) 753 { 754 // about to do a slice without a bounds check 755 auto trustedSlice(return char[] r) @trusted 756 { 757 assert(r.ptr > buf.ptr); 758 return (r.ptr - 1)[0 .. r.length + 1]; 759 } 760 761 r = trustedSlice(r); 762 r[0] = '-'; 763 } 764 return r; 765 } 766 767 auto signedToTempString(long value, uint radix = 10) @safe 768 { 769 bool neg = value < 0; 770 if (neg) 771 value = cast(ulong)-value; 772 auto r = unsignedToTempString(value, radix); 773 if (neg) 774 { 775 r._len++; 776 r.get()[0] = '-'; 777 } 778 return r; 779 } 780 781 import std.traits : isIntegral, isArray; 782 783 // TODO: std.range.put doesn't do scope on second args therefor compiler thinks buf escapes. resolve it and we can avoid the @trusted 784 @trusted 785 void toTextRange(T, W)(T value, auto ref W writer) if (isIntegral!T && isArray!T) 786 { 787 import core.internal.string : SignedStringBuf, 788 UnsignedStringBuf; 789 790 if (value < 0) 791 { 792 SignedStringBuf buf = void; 793 writer.put(signedToTempString(value, buf, 10)); 794 } 795 else 796 { 797 UnsignedStringBuf buf = void; 798 writer.put(unsignedToTempString(value, buf, 10)); 799 } 800 } 801 802 void write(Sink, S...)(auto ref Sink sink, S args) 803 { 804 import std.traits : isBoolean, isIntegral, isAggregateType, isSomeString, isSomeChar; 805 806 foreach (arg; args) 807 { 808 alias A = typeof(arg); 809 static if (isAggregateType!A || is(A == enum)) 810 { 811 sink.put(arg.toString()); 812 } 813 else static if (isSomeString!A) 814 { 815 sink.put(arg); 816 } 817 else static if (isIntegral!A) 818 { 819 toTextRange(arg, sink); 820 } 821 else static if (isBoolean!A) 822 { 823 sink.put(arg ? "true" : "false"); 824 } 825 else static if (isSomeChar!A) 826 { 827 sink.put(arg); 828 } 829 else 830 { 831 static assert(0); 832 } 833 } 834 }