The OpenD Programming Language

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 }