1 /++ 2 $(H1 Thread-safe reference-counted arrays and iterators). 3 +/ 4 module mir.rc.array; 5 6 import mir.primitives: hasLength; 7 import mir.qualifier; 8 import mir.rc.context; 9 import mir.type_info; 10 import std.traits; 11 12 package static immutable allocationExcMsg = "mir_rcarray: out of memory error."; 13 14 version (D_Exceptions) 15 { 16 import core.exception: OutOfMemoryError; 17 package static immutable allocationError = new OutOfMemoryError(allocationExcMsg); 18 } 19 20 /++ 21 Thread safe reference counting array. 22 23 The implementation never adds roots into the GC. 24 +/ 25 struct mir_rcarray(T) 26 { 27 import mir.internal.utility: isComplex, realType; 28 /// 29 package T* _payload; 30 package ref mir_rc_context context() inout return scope pure nothrow @nogc @trusted @property 31 { 32 assert(_payload); 33 return (cast(mir_rc_context*)_payload)[-1]; 34 } 35 package void _reset() { _payload = null; } 36 37 package alias ThisTemplate = .mir_rcarray; 38 package alias _thisPtr = _payload; 39 40 /// 41 alias serdeKeysProxy = Unqual!T; 42 43 /// 44 void proxySwap(ref typeof(this) rhs) pure nothrow @nogc @safe 45 { 46 auto t = this._payload; 47 this._payload = rhs._payload; 48 rhs._payload = t; 49 } 50 51 /// 52 this(typeof(null)) 53 { 54 } 55 56 /// 57 mixin CommonRCImpl; 58 59 /// 60 pragma(inline, true) 61 bool opEquals(typeof(null)) @safe scope const pure nothrow @nogc 62 { 63 return !this; 64 } 65 66 /// ditto 67 bool opEquals(Y)(auto ref scope const ThisTemplate!Y rhs) @safe scope const pure nothrow @nogc 68 { 69 static if (isComplex!T) 70 return cast(const realType!T[]) opIndex() == cast(const realType!Y[]) rhs.opIndex(); 71 else 72 return opIndex() == rhs.opIndex(); 73 } 74 75 /// 76 int opCmp(Y)(auto ref scope const ThisTemplate!Y rhs) @trusted scope const pure nothrow @nogc 77 { 78 static if (isComplex!T) 79 return __cmp(cast(const realType!T[])opIndex(), cast(const realType!Y[])rhs.opIndex()); 80 else 81 return __cmp(opIndex(), rhs.opIndex()); 82 } 83 84 /// 85 size_t toHash() @trusted scope const pure nothrow @nogc 86 { 87 static if (isComplex!T) 88 return hashOf(cast(const realType!T[])opIndex()); 89 else 90 return hashOf(opIndex()); 91 } 92 93 /// 94 ~this() nothrow 95 { 96 static if (hasElaborateDestructor!T || hasDestructor!T) 97 { 98 if (false) // break @safe and pure attributes 99 { 100 Unqual!T* object; 101 (*object).__xdtor; 102 } 103 } 104 if (this) 105 { 106 (() @trusted { mir_rc_decrease_counter(context); })(); 107 } 108 } 109 110 /// 111 size_t length() @trusted scope pure nothrow @nogc const @property 112 { 113 return _payload !is null ? context.length : 0; 114 } 115 116 /// 117 inout(T)* ptr() @system scope inout 118 { 119 return _payload; 120 } 121 122 /// 123 ref opIndex(size_t i) @trusted scope inout 124 { 125 assert(_payload); 126 assert(i < context.length); 127 return _payload[i]; 128 } 129 130 /// 131 inout(T)[] opIndex() @trusted scope inout 132 { 133 return _payload !is null ? _payload[0 .. context.length] : null; 134 } 135 136 /// 137 size_t opDollar(size_t pos : 0)() @trusted scope pure nothrow @nogc const 138 { 139 return length; 140 } 141 142 /// 143 auto asSlice()() @property 144 { 145 import mir.ndslice.slice: mir_slice; 146 alias It = mir_rci!T; 147 return mir_slice!It([length], It(this)); 148 } 149 150 /// 151 auto asSlice()() const @property 152 { 153 import mir.ndslice.slice: mir_slice; 154 alias It = mir_rci!(const T); 155 return mir_slice!It([length], It(this.lightConst)); 156 } 157 158 /// 159 auto asSlice()() immutable @property 160 { 161 import mir.ndslice.slice: mir_slice; 162 alias It = mir_rci!(immutable T); 163 return mir_slice!It([length], It(this.lightImmutable)); 164 } 165 166 /// 167 auto moveToSlice()() @property 168 { 169 import core.lifetime: move; 170 import mir.ndslice.slice: mir_slice; 171 alias It = mir_rci!T; 172 return mir_slice!It([length], It(move(this))); 173 } 174 175 /++ 176 Params: 177 length = array length 178 initialize = Flag, don't initialize memory with default value if `false`. 179 deallocate = Flag, never deallocates memory if `false`. 180 +/ 181 this(size_t length, bool initialize = true, bool deallocate = true) @trusted @nogc 182 { 183 if (length == 0) 184 return; 185 Unqual!T[] ar; 186 () @trusted { 187 static if (is(T == class) || is(T == interface)) 188 auto ctx = mir_rc_create(mir_get_type_info!T, length, mir_get_payload_ptr!T, initialize, deallocate); 189 else 190 auto ctx = mir_rc_create(mir_get_type_info!T, length, mir_get_payload_ptr!T, initialize, deallocate); 191 if (!ctx) 192 { 193 version(D_Exceptions) 194 { import mir.exception : toMutable; throw allocationError.toMutable; } 195 else 196 assert(0, allocationExcMsg); 197 } 198 _payload = cast(T*)(ctx + 1); 199 ar = cast(Unqual!T[])_payload[0 .. length]; 200 } (); 201 if (initialize || hasElaborateAssign!(Unqual!T)) 202 { 203 import mir.conv: uninitializedFillDefault; 204 uninitializedFillDefault(ar); 205 } 206 } 207 208 static if (isImplicitlyConvertible!(const T, T)) 209 static if (isImplicitlyConvertible!(const Unqual!T, T)) 210 package alias V = const Unqual!T; 211 else 212 package alias V = const T; 213 else 214 package alias V = T; 215 216 static if (is(T == const) || is(T == immutable)) 217 this(return ref scope const typeof(this) rhs) @trusted pure nothrow @nogc 218 { 219 if (rhs) 220 { 221 this._payload = cast(typeof(this._payload))rhs._payload; 222 mir_rc_increase_counter(context); 223 } 224 } 225 226 static if (is(T == immutable)) 227 this(return ref scope const typeof(this) rhs) immutable @trusted pure nothrow @nogc 228 { 229 if (rhs) 230 { 231 this._payload = cast(typeof(this._payload))rhs._payload; 232 mir_rc_increase_counter(context); 233 } 234 } 235 236 static if (is(T == immutable)) 237 this(return ref scope const typeof(this) rhs) const @trusted pure nothrow @nogc 238 { 239 if (rhs) 240 { 241 this._payload = cast(typeof(this._payload))rhs._payload; 242 mir_rc_increase_counter(context); 243 } 244 } 245 246 this(return ref scope inout typeof(this) rhs) inout @trusted pure nothrow @nogc 247 { 248 if (rhs) 249 { 250 this._payload = rhs._payload; 251 mir_rc_increase_counter(context); 252 } 253 } 254 255 /// 256 ref opAssign(typeof(null)) scope return @trusted // pure nothrow @nogc 257 { 258 this = typeof(this).init; 259 } 260 261 /// 262 ref opAssign(scope return typeof(this) rhs) scope return @trusted // pure nothrow @nogc 263 { 264 this.proxySwap(rhs); 265 return this; 266 } 267 268 /// 269 ref opAssign(Q)(scope return ThisTemplate!Q rhs) scope return @trusted // pure nothrow @nogc 270 if (isImplicitlyConvertible!(Q*, T*)) 271 { 272 this.proxySwap(*()@trusted{return cast(typeof(this)*)&rhs;}()); 273 return this; 274 } 275 } 276 277 /// ditto 278 alias RCArray = mir_rcarray; 279 280 /// 281 version(mir_test) 282 @safe pure @nogc nothrow 283 unittest 284 { 285 auto a = RCArray!double(10); 286 foreach(i, ref e; a) 287 e = i; 288 auto b = a; 289 assert(b[$ - 1] == 9); 290 foreach(i, ref e; b) 291 assert(e == i); 292 b[4] = 100; 293 assert(a[4] == 100); 294 295 import mir.ndslice.slice; 296 297 auto s = a.asSlice; // as RC random access range (ndslice) 298 static assert(is(typeof(s) == Slice!(RCI!double))); 299 static assert(is(typeof(s) == mir_slice!(mir_rci!double))); 300 301 auto r = a[]; // scope array 302 static assert(is(typeof(r) == double[])); 303 304 auto fs = r.sliced; // scope fast random access range (ndslice) 305 static assert(is(typeof(fs) == Slice!(double*))); 306 } 307 308 version(mir_test) 309 @safe pure @nogc nothrow 310 unittest 311 { 312 import mir.complex; 313 auto a = rcarray(complex(2.0, 3), complex(4.9, 2)); 314 } 315 316 package template LikeArray(Range) 317 { 318 static if (__traits(identifier, Range) == "mir_slice") 319 { 320 import mir.ndslice.slice: Slice, SliceKind; 321 enum LikeArray = is(Range : Slice!(T*, N, SliceKind.contiguous), T, size_t N); 322 } 323 else 324 { 325 enum LikeArray = false; 326 } 327 } 328 329 /// 330 auto rcarray(T = void, Range)(ref Range range) 331 if (is(T == void) && !is(Range == LightScopeOf!Range)) 332 { 333 return .rcarray(range.lightScope); 334 } 335 336 /// ditto 337 auto rcarray(T = void, Range)(Range range) 338 if (is(T == void) && isIterable!Range && is(Range == LightScopeOf!Range) && !isArray!Range) 339 { 340 static if (LikeArray!Range) 341 { 342 return .rcarray(range.field); 343 } 344 else 345 { 346 return .rcarray!(ForeachType!Range)(range); 347 } 348 } 349 350 /// ditto 351 RCArray!V rcarray(T = void, V)(scope V[] values...) 352 if (is(T == void)) 353 { 354 return .rcarray(values, true); 355 } 356 357 /// ditto 358 RCArray!V rcarray(T = void, V)(scope V[] values, bool deallocate) 359 if (is(T == void)) 360 { 361 return .rcarray!V(values, deallocate); 362 } 363 364 /// ditto 365 template rcarray(T) 366 if(!is(T == E[], E) && !is(T == void)) 367 { 368 import mir.primitives: isInputRange, isInfinite; 369 370 /// 371 auto rcarray(Range)(ref Range range) 372 if (!is(Range == LightScopeOf!Range)) 373 { 374 return .rcarray!T(range.lightScope); 375 } 376 377 /// ditto 378 auto rcarray(Range)(Range range) 379 if ((isInputRange!Range || isIterable!Range) && !isInfinite!Range && !isArray!Range || isPointer!Range && (isInputRange!(PointerTarget!Range) || isIterable!(PointerTarget!Range))) 380 { 381 static if (LikeArray!Range) 382 { 383 return .rcarray!T(range.field); 384 } 385 else static if (hasLength!Range) 386 { 387 import mir.conv: emplaceRef; 388 auto ret = RCArray!T(range.length, false); 389 size_t i; 390 static if (isInputRange!Range) 391 for (; !range.empty; range.popFront) 392 ret[i++].emplaceRef!T(range.front); 393 else 394 static if (isPointer!Range) 395 foreach (e; *range) 396 ret[i++].emplaceRef!T(e); 397 else 398 foreach (e; range) 399 ret[i++].emplaceRef!T(e); 400 return ret; 401 } 402 else 403 { 404 import mir.appender: scopedBuffer; 405 import mir.conv: emplaceRef; 406 auto a = scopedBuffer!T; 407 static if (isInputRange!Range) 408 for (; !range.empty; range.popFront) 409 a.put(range.front); 410 else 411 static if (isPointer!Range) 412 foreach (e; *range) 413 a.put(e); 414 else 415 foreach (e; range) 416 a.put(e); 417 scope values = a.data; 418 return ()@trusted { 419 auto ret = RCArray!T(values.length, false); 420 a.moveDataAndEmplaceTo(ret[]); 421 return ret; 422 } (); 423 } 424 } 425 426 /// ditto 427 RCArray!T rcarray(V)(scope V[] values...) 428 { 429 return .rcarray!T(values, true); 430 } 431 432 /// ditto 433 RCArray!T rcarray(V)(scope V[] values, bool deallocate) 434 { 435 auto ret = mir_rcarray!T(values.length, hasElaborateDestructor!T, deallocate); 436 static if (!hasElaborateAssign!(Unqual!T) && is(Unqual!V == Unqual!T)) 437 { 438 ()@trusted { 439 import core.stdc.string: memcpy; 440 memcpy(cast(void*)ret.ptr, cast(const void*)values.ptr, values.length * T.sizeof); 441 }(); 442 } 443 else 444 { 445 import mir.conv: emplaceRef; 446 auto lhs = ret[]; 447 foreach (i, ref e; values) 448 lhs[i].emplaceRef!T(e); 449 } 450 return ret; 451 } 452 } 453 454 /// 455 version(mir_test) 456 @safe pure @nogc nothrow 457 unittest 458 { 459 RCArray!double a = rcarray!double(1.0, 2, 5, 3); 460 assert(a[0] == 1); 461 assert(a[$ - 1] == 3); 462 463 auto s = rcarray!char("hello!"); 464 assert(s[0] == 'h'); 465 assert(s[$ - 1] == '!'); 466 467 alias rcstring = rcarray!(immutable char); 468 auto r = rcstring("string"); 469 assert(r[0] == 's'); 470 assert(r[$ - 1] == 'g'); 471 } 472 473 /// With Input Ranges 474 version(mir_test) 475 @safe pure @nogc nothrow 476 unittest 477 { 478 import mir.algorithm.iteration: filter; 479 static immutable numbers = [3, 2, 5, 2, 3, 7, 3]; 480 static immutable filtered = [5.0, 7]; 481 auto result = numbers.filter!"a > 3".rcarray!(immutable double); 482 static assert(is(typeof(result) == RCArray!(immutable double))); 483 assert (result[] == filtered); 484 } 485 486 /++ 487 Params: 488 length = array length 489 deallocate = Flag, never deallocates memory if `false`. 490 Returns: minimally initialized rcarray. 491 +/ 492 RCArray!T mininitRcarray(T)(size_t length, bool deallocate = true) 493 { 494 return RCArray!T(length, false, deallocate); 495 } 496 497 /// 498 version(mir_test) 499 @safe pure nothrow @nogc unittest 500 { 501 auto a = mininitRcarray!double(5); 502 assert(a.length == 5); 503 assert(a._counter == 1); 504 a[][] = 0; // a.opIndex()[] = 0; 505 } 506 507 /++ 508 Thread safe reference counting iterator. 509 +/ 510 struct mir_rci(T) 511 { 512 import mir.ndslice.slice: Slice; 513 import mir.ndslice.iterator: IotaIterator; 514 515 /// 516 T* _iterator; 517 518 /// 519 RCArray!T _array; 520 521 /// 522 this(RCArray!T array) 523 { 524 this._iterator = (()@trusted => array.ptr)(); 525 this._array.proxySwap(array); 526 } 527 528 /// 529 this(T* _iterator, RCArray!T array) 530 { 531 this._iterator = _iterator; 532 this._array.proxySwap(array); 533 } 534 535 /// 536 inout(T)* lightScope()() return scope inout @property @trusted 537 { 538 debug 539 { 540 assert(_array._payload <= _iterator); 541 assert(_iterator is null || _iterator <= _array._payload + _array.length); 542 } 543 return _iterator; 544 } 545 546 /// 547 ref opAssign(typeof(null)) scope return nothrow 548 { 549 pragma(inline, true); 550 _iterator = null; 551 _array = null; 552 return this; 553 } 554 555 /// 556 ref opAssign(return typeof(this) rhs) scope return @trusted 557 { 558 _iterator = rhs._iterator; 559 _array.proxySwap(rhs._array); 560 return this; 561 } 562 563 /// 564 ref opAssign(Q)(return mir_rci!Q rhs) scope return nothrow 565 if (isImplicitlyConvertible!(Q*, T*)) 566 { 567 import core.lifetime: move; 568 _iterator = rhs._iterator; 569 _array = move(rhs._array); 570 return this; 571 } 572 573 /// 574 mir_rci!(const T) lightConst()() return scope const nothrow @property 575 { return typeof(return)(_iterator, _array.lightConst); } 576 577 /// 578 mir_rci!(immutable T) lightImmutable()() return scope immutable nothrow @property 579 { return typeof(return)(_iterator, _array.lightImmutable); } 580 581 /// 582 ref inout(T) opUnary(string op : "*")() inout return scope 583 { 584 debug 585 { 586 assert(_iterator); 587 assert(_array._payload); 588 assert(_array._payload <= _iterator); 589 assert(_iterator <= _array._payload + _array.length); 590 } 591 return *_iterator; 592 } 593 594 /// 595 ref inout(T) opIndex(ptrdiff_t index) inout return scope @trusted 596 { 597 debug 598 { 599 assert(_iterator); 600 assert(_array._payload); 601 assert(_array._payload <= _iterator + index); 602 assert(_iterator + index <= _array._payload + _array.length); 603 } 604 return _iterator[index]; 605 } 606 607 /// Returns: slice type of `Slice!(IotaIterator!size_t)` 608 Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) @safe scope const 609 if (dimension == 0) 610 in 611 { 612 assert(i <= j, "RCI!T.opSlice!0: the left opSlice boundary must be less than or equal to the right bound."); 613 } 614 do 615 { 616 return typeof(return)(j - i, typeof(return).Iterator(i)); 617 } 618 619 /// Returns: ndslice on top of the refcounted iterator 620 auto opIndex(Slice!(IotaIterator!size_t) slice) 621 { 622 import core.lifetime: move; 623 auto it = this; 624 it += slice._iterator._index; 625 return Slice!(RCI!T)(slice.length, it.move); 626 } 627 628 /// ditto 629 auto opIndex(Slice!(IotaIterator!size_t) slice) const 630 { 631 import core.lifetime: move; 632 auto it = lightConst; 633 it += slice._iterator._index; 634 return Slice!(RCI!(const T))(slice.length, it.move); 635 } 636 637 /// 638 void opUnary(string op)() scope 639 if (op == "--" || op == "++") 640 { mixin(op ~ "_iterator;"); } 641 642 /// 643 void opOpAssign(string op)(ptrdiff_t index) scope 644 if (op == "-" || op == "+") 645 { mixin("_iterator " ~ op ~ "= index;"); } 646 647 /// 648 mir_rci!T opBinary(string op)(ptrdiff_t index) 649 if (op == "+" || op == "-") 650 { return mir_rci!T(_iterator + index, _array); } 651 652 /// 653 mir_rci!(const T) opBinary(string op)(ptrdiff_t index) const 654 if (op == "+" || op == "-") 655 { return mir_rci!T(_iterator + index, _array); } 656 657 /// 658 mir_rci!(immutable T) opBinary(string op)(ptrdiff_t index) immutable 659 if (op == "+" || op == "-") 660 { return mir_rci!T(_iterator + index, _array); } 661 662 /// 663 ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const 664 { return this._iterator - right._iterator; } 665 666 /// 667 bool opEquals()(scope ref const typeof(this) right) scope const 668 { return this._iterator == right._iterator; } 669 670 /// 671 int opCmp()(scope ref const typeof(this) right) scope const 672 { auto d = this - right; return d ? d < 0 ? -1 : 1 : 0; } 673 } 674 675 /// ditto 676 alias RCI = mir_rci; 677 678 /// 679 version(mir_test) 680 @safe @nogc unittest 681 { 682 683 import mir.ndslice.traits: isIterator; 684 import mir.ndslice.slice; 685 import mir.rc.array; 686 auto slice = mir_rcarray!double(10).asSlice; 687 static assert(isIterator!(RCI!double)); 688 static assert(is(typeof(slice) == Slice!(RCI!double))); 689 auto matrix = slice.sliced(2, 5); 690 static assert(is(typeof(matrix) == Slice!(RCI!double, 2))); 691 slice[7] = 44; 692 assert(matrix[1, 2] == 44); 693 } 694 695 /// 696 version(mir_test) 697 @safe @nogc unittest 698 { 699 import mir.ndslice.slice; 700 import mir.rc.array; 701 702 alias rcvec = Slice!(RCI!double); 703 704 RCI!double a, b; 705 a = b; 706 707 RCI!(const double) ca, cb; 708 ca = cb; 709 ca = cast(const) cb; 710 711 void foo(scope ref rcvec x, scope ref rcvec y) 712 { 713 x[] = y[]; 714 x[1] = y[1]; 715 x[1 .. $] += y[1 .. $]; 716 x = x.save; 717 } 718 } 719 720 version(mir_test) 721 @safe @nogc unittest 722 { 723 import mir.ndslice; 724 import mir.rc.array; 725 import mir.series; 726 727 @safe void bar(ref const mir_rcarray!(const double) a, ref mir_rcarray!(const double) b) 728 { 729 b = a; 730 } 731 732 @safe void bari(ref immutable mir_rcarray!(immutable double) a, ref mir_rcarray!(immutable double) b) 733 { 734 b = a; 735 } 736 737 @safe void foo(ref const RCI!(const double) a, ref RCI!(const double) b) 738 { 739 b = a; 740 } 741 742 @safe void fooi(ref immutable RCI!(immutable double) a, ref RCI!(immutable double) b) 743 { 744 b = a; 745 } 746 747 struct S 748 { 749 uint i; 750 @safe pure: 751 ~this() {} 752 } 753 754 @safe void goo(ref const Series!(RCI!(const double), RCI!(const S)) a, ref Series!(RCI!(const double), RCI!(const S)) b) 755 { 756 b = a; 757 } 758 759 @safe void gooi(ref immutable Series!(RCI!(immutable double), RCI!(const S)) a, ref Series!(RCI!(immutable double), RCI!(const S)) b) 760 { 761 b = a; 762 } 763 764 struct C 765 { 766 Series!(RCI!(const S), RCI!(const S)) a; 767 Series!(RCI!(const S), RCI!(const S)) b; 768 } 769 770 C a, b; 771 a = b; 772 a = cast(const) b; 773 } 774 775 version(mir_test) 776 unittest 777 { 778 import mir.ndslice.slice: Slice; 779 static RCArray!int foo() @safe 780 { 781 auto ret = RCArray!int(10); 782 return ret; 783 } 784 785 786 static Slice!(RCI!int) bat() @safe 787 { 788 auto ret = RCArray!int(10); 789 return ret.asSlice; 790 } 791 792 static Slice!(RCI!int) bar() @safe 793 { 794 auto ret = RCArray!int(10); 795 auto d = ret.asSlice; 796 return d; 797 } 798 } 799 800 version(mir_test) 801 @safe unittest 802 { 803 struct S 804 { 805 uint s; 806 this(this) @nogc nothrow @safe 807 { 808 // () @trusted { 809 // puts("this(this)\n"); 810 // } (); 811 } 812 813 ~this() nothrow @nogc @safe 814 { 815 // () @trusted { 816 // if (s) 817 // puts("~this()\n"); 818 // else 819 // puts("~this() - zero\n"); 820 // } (); 821 } 822 } 823 824 struct C 825 { 826 S s; 827 } 828 829 S[1] d = [S(1)]; 830 auto r = rcarray(d); 831 } 832 833 version(mir_test) 834 unittest 835 { 836 import mir.small_string; 837 alias S = SmallString!32u; 838 auto ars = [S("123"), S("422")]; 839 alias R = mir_rcarray!S; 840 auto rc = ars.rcarray!S; 841 842 RCArray!int value = null; 843 value = null; 844 }