1 module automem.ref_counted;
2 
3 import automem.traits: isAllocator;
4 import automem.test_utils: TestUtils;
5 import automem.unique: Unique;
6 import std.experimental.allocator: theAllocator, processAllocator;
7 import std.typecons: Flag;
8 
9 version(unittest) {
10     import unit_threaded;
11     import test_allocator: TestAllocator;
12 }
13 
14 mixin TestUtils;
15 
16 struct RefCounted(Type, Allocator = typeof(theAllocator),
17     Flag!"supportGC" supportGC = Flag!"supportGC".yes)
18 if(isAllocator!Allocator) {
19 
20     import std.traits: hasMember;
21 
22     enum isSingleton = hasMember!(Allocator, "instance");
23     enum isTheAllocator = is(Allocator == typeof(theAllocator));
24     enum isGlobal = isSingleton || isTheAllocator;
25 
26     static if(isGlobal)
27         /**
28            The allocator is a singleton, so no need to pass it in to the
29            constructor
30         */
31         this(Args...)(auto ref Args args) {
32             this.makeObject!args();
33         }
34     else
35         /**
36            Non-singleton allocator, must be passed in
37         */
38         this(Args...)(Allocator allocator, auto ref Args args) {
39             _allocator = allocator;
40             this.makeObject!args();
41         }
42 
43     static if(isGlobal)
44         /**
45             Factory method so can construct with zero args.
46         */
47         static typeof(this) construct(Args...)(auto ref Args args) {
48             static if (Args.length != 0)
49                 return typeof(return)(args);
50             else {
51                 typeof(return) ret;
52                 ret.makeObject!()();
53                 return ret;
54             }
55         }
56     else
57         /**
58             Factory method. Not necessary with non-global allocator
59             but included for symmetry.
60         */
61         static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) {
62             return typeof(return)(allocator, args);
63         }
64 
65     this(this) {
66         assert(_impl !is null);
67         inc;
68     }
69 
70     ~this() {
71         release;
72     }
73 
74     /**
75        Assign to an lvalue RefCounted
76     */
77     void opAssign(ref RefCounted other) {
78 
79         if (_impl == other._impl)
80             return;
81 
82         if(_impl !is null) {
83             release;
84         }
85         static if(!isGlobal)
86             _allocator = other._allocator;
87 
88         _impl = other._impl;
89         inc;
90     }
91 
92     /**
93        Assign to an rvalue RefCounted
94      */
95     void opAssign(RefCounted other) {
96         import std.algorithm: swap;
97         swap(_impl, other._impl);
98         static if(!isGlobal)
99             swap(_allocator, other._allocator);
100     }
101 
102     /**
103        Dereference the smart pointer and yield a reference
104        to the contained type.
105      */
106     ref auto opUnary(string s)() inout if (s == "*") {
107         return _impl._get;
108     }
109 
110     // Prevent opSlice and opIndex from being hidden by Impl*.
111     // This comment is deliberately not DDOC.
112     auto ref opSlice(A...)(auto ref A args)
113     if (__traits(compiles, Type.init.opSlice(args)))
114     {
115         return _impl._get.opSlice(args);
116     }
117     // ditto
118     auto ref opIndex(A...)(auto ref A args)
119     if (__traits(compiles, Type.init.opIndex(args)))
120     {
121         return _impl._get.opIndex(args);
122     }
123     // ditto
124     auto ref opIndexAssign(A...)(auto ref A args)
125     if (__traits(compiles, Type.init.opIndexAssign(args)))
126     {
127         return _impl._get.opIndexAssign(args);
128     }
129 
130     alias _impl this;
131 
132 private:
133 
134     static struct Impl {
135 
136         static if(is(Type == class)) {
137 
138             align ((void*).alignof)
139             void[__traits(classInstanceSize, Type)] _rawMemory;
140 
141         } else
142             Type _object;
143 
144         static if(is(Type == shared))
145             shared size_t _count;
146         else
147             size_t _count;
148 
149         static if (is(Type == class)) {
150             inout(Type) _get() inout {
151                 return cast(inout(Type))&_rawMemory[0];
152             }
153 
154             inout(shared(Type)) _get() inout shared {
155                 return cast(inout(shared(Type)))&_rawMemory[0];
156             }
157         } else {
158             ref inout(Type) _get() inout {
159                 return _object;
160             }
161 
162             ref inout(shared(Type)) _get() inout shared {
163                 return _object;
164             }
165         }
166 
167         alias _get this;
168     }
169 
170     static if(isSingleton)
171         alias _allocator = Allocator.instance;
172     else static if(isTheAllocator) {
173         static if (is(Type == shared))
174             // 'processAllocator' should be used for allocating
175             // memory shared across threads
176             alias _allocator = processAllocator;
177         else
178             alias _allocator = theAllocator;
179     }
180     else
181         Allocator _allocator;
182 
183     static if(is(Type == shared))
184         alias ImplType = shared Impl;
185     else
186         alias ImplType = Impl;
187 
188     public ImplType* _impl; // public or alias this doesn't work
189 
190     void allocateImpl() {
191         import std.experimental.allocator: make;
192         import std.traits: hasIndirections;
193 
194         _impl = cast(typeof(_impl))_allocator.allocate(Impl.sizeof);
195         _impl._count= 1;
196 
197         static if (is(Type == class)) {
198             // class representation:
199             // void* classInfoPtr
200             // void* monitorPtr
201             // []    interfaces
202             // T...  members
203             import core.memory: GC;
204             if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers))
205                 // members have pointers: we have to watch the monitor
206                 // and all members; skip the classInfoPtr
207                 GC.addRange(&_impl._rawMemory[(void*).sizeof],
208                         __traits(classInstanceSize, Type) - (void*).sizeof);
209             else
210                 // representation doesn't have pointers, just watch the
211                 // monitor pointer; skip the classInfoPtr
212                 // need to watch the monitor pointer even if supportGC is false.
213                 GC.addRange(&_impl._rawMemory[(void*).sizeof], (void*).sizeof);
214         } else static if (supportGC && hasIndirections!Type) {
215             import core.memory: GC;
216             GC.addRange(&_impl._object, Type.sizeof);
217         }
218     }
219 
220     void release() {
221         import std.traits : hasIndirections;
222         import core.memory : GC;
223         import automem.utils : destruct;
224         if(_impl is null) return;
225         assert(_impl._count > 0, "Trying to release a RefCounted but ref count is 0 or less");
226 
227         dec;
228 
229         if(_impl._count == 0) {
230             destruct(_impl._get);
231             static if (is(Type == class)) {
232                 // need to watch the monitor pointer even if supportGC is false.
233                 GC.removeRange(&_impl._rawMemory[(void*).sizeof]);
234             } else static if (supportGC && hasIndirections!Type) {
235                 GC.removeRange(&_impl._object);
236             }
237             auto mem = cast(void*)_impl;
238             _allocator.deallocate(() @trusted { return mem[0 .. Impl.sizeof]; }());
239         }
240     }
241 
242     void inc() {
243         static if(is(Type == shared)) {
244             import core.atomic: atomicOp;
245             _impl._count.atomicOp!"+="(1);
246         } else
247             ++_impl._count;
248 
249     }
250 
251     void dec() {
252         static if(is(Type == shared)) {
253             import core.atomic: atomicOp;
254             _impl._count.atomicOp!"-="(1);
255         } else
256             --_impl._count;
257     }
258 
259 }
260 
261 private template makeObject(args...)
262 {
263     void makeObject(Type, A)(ref RefCounted!(Type, A) rc) @trusted {
264         import std.conv: emplace;
265         import std.functional : forward;
266 
267         rc.allocateImpl;
268 
269         static if(is(Type == class))
270             emplace!Type(rc._impl._rawMemory, forward!args);
271         else
272             emplace(&rc._impl._object, forward!args);
273     }
274 }
275 
276 @("struct test allocator no copies")
277 @system unittest {
278     auto allocator = TestAllocator();
279     {
280         auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
281         Struct.numStructs.shouldEqual(1);
282     }
283     Struct.numStructs.shouldEqual(0);
284 }
285 
286 @("struct test allocator one lvalue assignment")
287 @system unittest {
288     auto allocator = TestAllocator();
289     {
290         auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
291         Struct.numStructs.shouldEqual(1);
292         RefCounted!(Struct, TestAllocator*) ptr2;
293         ptr2 = ptr1;
294         Struct.numStructs.shouldEqual(1);
295     }
296     Struct.numStructs.shouldEqual(0);
297 }
298 
299 @("struct test allocator one rvalue assignment test allocator")
300 @system unittest {
301     auto allocator = TestAllocator();
302     {
303         RefCounted!(Struct, TestAllocator*) ptr;
304         ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
305         Struct.numStructs.shouldEqual(1);
306     }
307     Struct.numStructs.shouldEqual(0);
308 }
309 
310 @("struct test allocator one rvalue assignment mallocator")
311 @system unittest {
312     import std.experimental.allocator.mallocator: Mallocator;
313     {
314         RefCounted!(Struct, Mallocator) ptr;
315         ptr = RefCounted!(Struct, Mallocator)(5);
316         Struct.numStructs.shouldEqual(1);
317     }
318     Struct.numStructs.shouldEqual(0);
319 }
320 
321 
322 @("struct test allocator one lvalue copy constructor")
323 @system unittest {
324     auto allocator = TestAllocator();
325     {
326         auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
327         Struct.numStructs.shouldEqual(1);
328         auto ptr2 = ptr1;
329         Struct.numStructs.shouldEqual(1);
330 
331         ptr1.i.shouldEqual(5);
332         ptr2.i.shouldEqual(5);
333     }
334     Struct.numStructs.shouldEqual(0);
335 }
336 
337 @("struct test allocator one rvalue copy constructor")
338 @system unittest {
339     auto allocator = TestAllocator();
340     {
341         auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
342         Struct.numStructs.shouldEqual(1);
343     }
344     Struct.numStructs.shouldEqual(0);
345 }
346 
347 @("many copies made")
348 @system unittest {
349     auto allocator = TestAllocator();
350 
351     // helper function for intrusive testing, in case the implementation
352     // ever changes
353     size_t refCount(T)(ref T ptr) {
354         return ptr._impl._count;
355     }
356 
357     {
358         auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
359         Struct.numStructs.shouldEqual(1);
360 
361         auto ptr2 = ptr1;
362         Struct.numStructs.shouldEqual(1);
363 
364         {
365             auto ptr3 = ptr2;
366             Struct.numStructs.shouldEqual(1);
367 
368             refCount(ptr1).shouldEqual(3);
369             refCount(ptr2).shouldEqual(3);
370             refCount(ptr3).shouldEqual(3);
371         }
372 
373         Struct.numStructs.shouldEqual(1);
374         refCount(ptr1).shouldEqual(2);
375         refCount(ptr2).shouldEqual(2);
376 
377         auto produce() {
378             return RefCounted!(Struct, TestAllocator*)(&allocator, 3);
379         }
380 
381         ptr1 = produce;
382         Struct.numStructs.shouldEqual(2);
383         refCount(ptr1).shouldEqual(1);
384         refCount(ptr2).shouldEqual(1);
385 
386         ptr1.twice.shouldEqual(6);
387         ptr2.twice.shouldEqual(10);
388     }
389 
390     Struct.numStructs.shouldEqual(0);
391 }
392 
393 @("default allocator")
394 @system unittest {
395     {
396         auto ptr = RefCounted!Struct(5);
397         Struct.numStructs.shouldEqual(1);
398     }
399     Struct.numStructs.shouldEqual(0);
400 }
401 
402 // FIXME: Github #13
403 // @("default allocator (shared)")
404 // @system unittest {
405 //     {
406 //         auto ptr = RefCounted!(shared SharedStruct)(5);
407 //         SharedStruct.numStructs.shouldEqual(1);
408 //     }
409 //     SharedStruct.numStructs.shouldEqual(0);
410 // }
411 
412 @("deref")
413 @system unittest {
414     auto allocator = TestAllocator();
415     auto rc1 = RefCounted!(int, TestAllocator*)(&allocator, 5);
416 
417     (*rc1).shouldEqual(5);
418     auto rc2 = rc1;
419     *rc2 = 42;
420     (*rc1).shouldEqual(42);
421 }
422 
423 @("swap")
424 @system unittest {
425     import std.algorithm: swap;
426     RefCounted!(int, TestAllocator*) rc1, rc2;
427     swap(rc1, rc2);
428 }
429 
430 @("phobos bug 6606")
431 @system unittest {
432 
433     union U {
434        size_t i;
435        void* p;
436     }
437 
438     struct S {
439        U u;
440     }
441 
442     alias SRC = RefCounted!(S, TestAllocator*);
443 }
444 
445 @("phobos bug 6436")
446 @system unittest
447 {
448     static struct S {
449         this(ref int val, string file = __FILE__, size_t line = __LINE__) {
450             val.shouldEqual(3, file, line);
451             ++val;
452         }
453     }
454 
455     auto allocator = TestAllocator();
456     int val = 3;
457     auto s = RefCounted!(S, TestAllocator*)(&allocator, val);
458     val.shouldEqual(4);
459 }
460 
461 @("assign from T")
462 @system unittest {
463     import std.experimental.allocator.mallocator: Mallocator;
464 
465     {
466         auto a = RefCounted!(Struct, Mallocator)(3);
467         Struct.numStructs.shouldEqual(1);
468 
469         *a = Struct(5);
470         Struct.numStructs.shouldEqual(1);
471         (*a).shouldEqual(Struct(5));
472 
473         RefCounted!(Struct, Mallocator) b;
474         b = a;
475         (*b).shouldEqual(Struct(5));
476         Struct.numStructs.shouldEqual(1);
477     }
478 
479     Struct.numStructs.shouldEqual(0);
480 }
481 
482 @("assign self")
483 @system unittest {
484     auto allocator = TestAllocator();
485     {
486         auto a = RefCounted!(Struct, TestAllocator*)(&allocator, 1);
487         a = a;
488         Struct.numStructs.shouldEqual(1);
489     }
490     Struct.numStructs.shouldEqual(0);
491 }
492 
493 // FIXME: Github #13
494 // @("SharedStruct")
495 // @system unittest {
496 //     auto allocator = TestAllocator();
497 //     {
498 //         auto ptr = RefCounted!(shared SharedStruct, TestAllocator*)(&allocator, 5);
499 //         SharedStruct.numStructs.shouldEqual(1);
500 //     }
501 //     SharedStruct.numStructs.shouldEqual(0);
502 // }
503 
504 @("@nogc @safe")
505 @safe @nogc unittest {
506 
507     auto allocator = SafeAllocator();
508 
509     {
510         const ptr = RefCounted!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6);
511         assert(ptr.i == 6);
512         assert(NoGcStruct.numStructs == 1);
513     }
514 
515     assert(NoGcStruct.numStructs == 0);
516 }
517 
518 
519 @("const object")
520 @system unittest {
521     auto allocator = TestAllocator();
522     auto ptr1 = RefCounted!(const Struct, TestAllocator*)(&allocator, 5);
523 }
524 
525 
526 @("theAllocator")
527 @system unittest {
528 
529     with(theTestAllocator) {
530         auto ptr = RefCounted!Struct(42);
531         (*ptr).shouldEqual(Struct(42));
532         Struct.numStructs.shouldEqual(1);
533     }
534 
535     Struct.numStructs.shouldEqual(0);
536 }
537 
538 // FIXME: Github #13
539 // @("threads Mallocator")
540 // @system unittest {
541 //     import std.experimental.allocator.mallocator: Mallocator;
542 //     static assert(__traits(compiles, sendRefCounted!Mallocator(7)));
543 // }
544 
545 // FIXME: Github #13
546 // @("threads SafeAllocator by value")
547 // @system unittest {
548 //     // can't even use TestAllocator because it has indirections
549 //     // can't pass by pointer since it's an indirection
550 //     auto allocator = SafeAllocator();
551 //     static assert(__traits(compiles, sendRefCounted!(SafeAllocator)(allocator, 7)));
552 // }
553 
554 // FIXME: Github #13
555 // @("threads SafeAllocator by shared pointer")
556 // @system unittest {
557 //     // can't even use TestAllocator because it has indirections
558 //     // can't only pass by pointer if shared
559 //     auto allocator = shared SafeAllocator();
560 //     static assert(__traits(compiles, sendRefCounted!(shared SafeAllocator*)(&allocator, 7)));
561 // }
562 
563 auto refCounted(Type, Allocator)(Unique!(Type, Allocator) ptr) {
564 
565     RefCounted!(Type, Allocator) ret;
566 
567     static if(!ptr.isGlobal)
568         ret._allocator = ptr.allocator;
569 
570     ret.allocateImpl;
571     *ret = *ptr;
572 
573     return ret;
574 }
575 
576 @("Construct RefCounted from Unique")
577 @system unittest {
578     import automem.unique: Unique;
579     auto allocator = TestAllocator();
580     auto ptr = refCounted(Unique!(int, TestAllocator*)(&allocator, 42));
581     (*ptr).shouldEqual(42);
582 }
583 
584 @("RefCounted with class")
585 @system unittest {
586     auto allocator = TestAllocator();
587     {
588         writelnUt("Creating ptr");
589         auto ptr = RefCounted!(Class, TestAllocator*)(&allocator, 33);
590         (*ptr).i.shouldEqual(33);
591         Class.numClasses.shouldEqual(1);
592     }
593     Class.numClasses.shouldEqual(0);
594 }
595 
596 @("@nogc class destructor")
597 @nogc unittest {
598 
599     auto allocator = SafeAllocator();
600 
601     {
602         const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6);
603         // shouldEqual isn't @nogc
604         assert(ptr.i == 6);
605         assert(NoGcClass.numClasses == 1);
606     }
607 
608     assert(NoGcClass.numClasses == 0);
609 }
610 
611 @("RefCounted opSlice and opIndex")
612 @system unittest {
613     import std.mmfile: MmFile;
614     auto file = RefCounted!MmFile(null, MmFile.Mode.readWriteNew, 120, null);
615     // The type of file[0] should be ubyte, not Impl.
616     static assert(is(typeof(file[0]) == typeof(MmFile.init[0])));
617     // opSlice should result in void[] not Impl[].
618     static assert(is(typeof(file[0 .. size_t.max]) == typeof(MmFile.init[0 .. size_t.max])));
619     ubyte[] data = cast(ubyte[]) file[0 .. cast(size_t) file.length];
620     immutable ubyte b = file[1];
621     file[1] = cast(ubyte) (b + 1);
622     assert(data[1] == cast(ubyte) (b + 1));
623 }
624 
625 @("Construct RefCounted using global allocator for struct with zero-args ctor")
626 @system unittest {
627     struct S {
628         private ulong zeroArgsCtorTest = 3;
629     }
630     auto s = RefCounted!S.construct();
631     static assert(is(typeof(s) == RefCounted!S));
632     assert(s._impl !is null);
633     assert(s.zeroArgsCtorTest == 3);
634 }
635 
636 version(unittest):
637 
638 void sendRefCounted(Allocator, Args...)(Args args) {
639     import std.concurrency: spawn, send;
640 
641     auto tid = spawn(&threadFunc);
642     auto ptr = RefCounted!(shared SharedStruct, Allocator)(args);
643 
644     tid.send(ptr);
645 }
646 
647 void threadFunc() {
648 
649 }