1 module automem.ref_counted;
2 
3 import automem.traits: isAllocator;
4 import automem.test_utils: TestUtils;
5 import std.experimental.allocator: theAllocator;
6 
7 version(unittest) {
8     import unit_threaded;
9     import test_allocator: TestAllocator;
10 }
11 
12 mixin TestUtils;
13 
14 
15 
16 struct RefCounted(Type, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) {
17     import std.traits: hasMember;
18     import std.typecons: Proxy;
19 
20 
21     enum isSingleton = hasMember!(Allocator, "instance");
22     enum isTheAllocator = is(Allocator == typeof(theAllocator));
23     enum isGlobal = isSingleton || isTheAllocator;
24 
25     static if(is(Type == class))
26         alias Pointer = Type;
27     else
28         alias Pointer = Type*;
29 
30     static if(isGlobal)
31         /**
32            The allocator is a singleton, so no need to pass it in to the
33            constructor
34         */
35         this(Args...)(auto ref Args args) {
36             makeObject(args);
37         }
38     else
39         /**
40            Non-singleton allocator, must be passed in
41         */
42         this(Args...)(Allocator allocator, auto ref Args args) {
43             _allocator = allocator;
44             makeObject(args);
45         }
46 
47     this(this) {
48         assert(_impl !is null);
49         inc;
50     }
51 
52     ~this() {
53         release;
54     }
55 
56     /**
57        Assign to an lvalue RefCounted
58     */
59     void opAssign(ref RefCounted other) {
60 
61         if(_impl !is null) {
62             release;
63         }
64         static if(!isGlobal)
65             _allocator = other._allocator;
66 
67         _impl = other._impl;
68         inc;
69     }
70 
71     /**
72        Assign to an rvalue RefCounted
73      */
74     void opAssign(RefCounted other) {
75         import std.algorithm: swap;
76         swap(_impl, other._impl);
77         static if(!isGlobal)
78             swap(_allocator, other._allocator);
79     }
80 
81     /**
82        Dereference the smart pointer and yield a reference
83        to the contained type.
84      */
85     ref inout(Type) opUnary(string s)() inout if(s == "*") {
86         return _impl._object;
87     }
88 
89     alias _impl this;
90 
91 private:
92 
93     static struct Impl {
94         Type _object;
95 
96         static if(is(Type == shared))
97             shared size_t _count;
98         else
99             size_t _count;
100 
101         alias _object this;
102     }
103 
104     static if(isSingleton)
105         alias _allocator = Allocator.instance;
106     else static if(isTheAllocator)
107         alias _allocator = theAllocator;
108     else
109         Allocator _allocator;
110 
111     public Impl* _impl; // or alias this doesn't work
112 
113     void makeObject(Args...)(auto ref Args args) @trusted {
114         import std.conv: emplace;
115 
116         allocateImpl;
117 
118         version(LDC) { // bug with emplace
119 
120             import std.traits: Unqual;
121             alias UnqualType = Unqual!Type;
122 
123             static if(is(Type == shared))
124                 ldcEmplace(cast(UnqualType*)&_impl._object, args);
125             else
126                 emplace(cast(UnqualType*)&_impl._object, args);
127 
128         } else
129             emplace(&_impl._object, args);
130     }
131 
132     void allocateImpl() {
133         import std.experimental.allocator: make;
134         import std.traits: hasIndirections;
135 
136         _impl = cast(Impl*)_allocator.allocate(Impl.sizeof);
137         _impl._count= 1;
138 
139         static if (hasIndirections!Type) {
140             import core.memory: GC;
141             GC.addRange(&_impl._object, Type.sizeof);
142         }
143     }
144 
145     void release() {
146         if(_impl is null) return;
147         assert(_impl._count > 0);
148 
149         dec;
150 
151         if(_impl._count == 0) {
152             destroy(_impl._object);
153             auto mem = cast(void*)_impl;
154             _allocator.deallocate(() @trusted { return mem[0 .. Impl.sizeof]; }());
155         }
156     }
157 
158     void inc() {
159         static if(is(Type == shared)) {
160             import core.atomic: atomicOp;
161             _impl._count.atomicOp!"+="(1);
162         } else
163             ++_impl._count;
164 
165     }
166 
167     void dec() {
168         static if(is(Type == shared)) {
169             import core.atomic: atomicOp;
170             _impl._count.atomicOp!"-="(1);
171         } else
172             --_impl._count;
173     }
174 
175 }
176 
177 @("struct test allocator no copies")
178 @system unittest {
179     auto allocator = TestAllocator();
180     {
181         auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
182         Struct.numStructs.shouldEqual(1);
183     }
184     Struct.numStructs.shouldEqual(0);
185 }
186 
187 @("struct test allocator one lvalue assignment")
188 @system unittest {
189     auto allocator = TestAllocator();
190     {
191         auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
192         Struct.numStructs.shouldEqual(1);
193         RefCounted!(Struct, TestAllocator*) ptr2;
194         ptr2 = ptr1;
195         Struct.numStructs.shouldEqual(1);
196     }
197     Struct.numStructs.shouldEqual(0);
198 }
199 
200 @("struct test allocator one rvalue assignment test allocator")
201 @system unittest {
202     auto allocator = TestAllocator();
203     {
204         RefCounted!(Struct, TestAllocator*) ptr;
205         ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
206         Struct.numStructs.shouldEqual(1);
207     }
208     Struct.numStructs.shouldEqual(0);
209 }
210 
211 @("struct test allocator one rvalue assignment mallocator")
212 @system unittest {
213     import std.experimental.allocator.mallocator: Mallocator;
214     {
215         RefCounted!(Struct, Mallocator) ptr;
216         ptr = RefCounted!(Struct, Mallocator)(5);
217         Struct.numStructs.shouldEqual(1);
218     }
219     Struct.numStructs.shouldEqual(0);
220 }
221 
222 
223 @("struct test allocator one lvalue copy constructor")
224 @system unittest {
225     auto allocator = TestAllocator();
226     {
227         auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
228         Struct.numStructs.shouldEqual(1);
229         auto ptr2 = ptr1;
230         Struct.numStructs.shouldEqual(1);
231 
232         ptr1.i.shouldEqual(5);
233         ptr2.i.shouldEqual(5);
234     }
235     Struct.numStructs.shouldEqual(0);
236 }
237 
238 @("struct test allocator one rvalue copy constructor")
239 @system unittest {
240     auto allocator = TestAllocator();
241     {
242         auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
243         Struct.numStructs.shouldEqual(1);
244     }
245     Struct.numStructs.shouldEqual(0);
246 }
247 
248 @("many copies made")
249 @system unittest {
250     auto allocator = TestAllocator();
251 
252     // helper function for intrusive testing, in case the implementation
253     // ever changes
254     size_t refCount(T)(ref T ptr) {
255         return ptr._impl._count;
256     }
257 
258     {
259         auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5);
260         Struct.numStructs.shouldEqual(1);
261 
262         auto ptr2 = ptr1;
263         Struct.numStructs.shouldEqual(1);
264 
265         {
266             auto ptr3 = ptr2;
267             Struct.numStructs.shouldEqual(1);
268 
269             refCount(ptr1).shouldEqual(3);
270             refCount(ptr2).shouldEqual(3);
271             refCount(ptr3).shouldEqual(3);
272         }
273 
274         Struct.numStructs.shouldEqual(1);
275         refCount(ptr1).shouldEqual(2);
276         refCount(ptr2).shouldEqual(2);
277 
278         auto produce() {
279             return RefCounted!(Struct, TestAllocator*)(&allocator, 3);
280         }
281 
282         ptr1 = produce;
283         Struct.numStructs.shouldEqual(2);
284         refCount(ptr1).shouldEqual(1);
285         refCount(ptr2).shouldEqual(1);
286 
287         ptr1.twice.shouldEqual(6);
288         ptr2.twice.shouldEqual(10);
289     }
290 
291     Struct.numStructs.shouldEqual(0);
292 }
293 
294 
295 @("deref")
296 @system unittest {
297     auto allocator = TestAllocator();
298     auto rc1 = RefCounted!(int, TestAllocator*)(&allocator, 5);
299 
300     (*rc1).shouldEqual(5);
301     auto rc2 = rc1;
302     *rc2 = 42;
303     (*rc1).shouldEqual(42);
304 }
305 
306 @("swap")
307 @system unittest {
308     import std.algorithm: swap;
309     RefCounted!(int, TestAllocator*) rc1, rc2;
310     swap(rc1, rc2);
311 }
312 
313 @("phobos bug 6606")
314 @system unittest {
315 
316     union U {
317        size_t i;
318        void* p;
319     }
320 
321     struct S {
322        U u;
323     }
324 
325     alias SRC = RefCounted!(S, TestAllocator*);
326 }
327 
328 @("phobos bug 6436")
329 @system unittest
330 {
331     static struct S {
332         this(ref int val, string file = __FILE__, size_t line = __LINE__) {
333             val.shouldEqual(3, file, line);
334             ++val;
335         }
336     }
337 
338     auto allocator = TestAllocator();
339     int val = 3;
340     auto s = RefCounted!(S, TestAllocator*)(&allocator, val);
341     val.shouldEqual(4);
342 }
343 
344 @("assign from T")
345 @system unittest {
346     import std.experimental.allocator.mallocator: Mallocator;
347 
348     {
349         auto a = RefCounted!(Struct, Mallocator)(3);
350         Struct.numStructs.shouldEqual(1);
351 
352         *a = Struct(5);
353         Struct.numStructs.shouldEqual(1);
354         (*a).shouldEqual(Struct(5));
355 
356         RefCounted!(Struct, Mallocator) b;
357         b = a;
358         (*b).shouldEqual(Struct(5));
359         Struct.numStructs.shouldEqual(1);
360     }
361 
362     Struct.numStructs.shouldEqual(0);
363 }
364 
365 @("SharedStruct")
366 @system unittest {
367     auto allocator = TestAllocator();
368     {
369         auto ptr = RefCounted!(shared SharedStruct, TestAllocator*)(&allocator, 5);
370         SharedStruct.numStructs.shouldEqual(1);
371     }
372     SharedStruct.numStructs.shouldEqual(0);
373 }
374 
375 @("@nogc @safe")
376 @safe @nogc unittest {
377 
378     auto allocator = SafeAllocator();
379 
380     {
381         const ptr = RefCounted!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6);
382         assert(ptr.i == 6);
383         assert(NoGcStruct.numStructs == 1);
384     }
385 
386     assert(NoGcStruct.numStructs == 0);
387 }
388 
389 
390 @("const object")
391 @system unittest {
392     auto allocator = TestAllocator();
393     auto ptr1 = RefCounted!(const Struct, TestAllocator*)(&allocator, 5);
394 }
395 
396 
397 @("theAllocator")
398 @system unittest {
399     import std.experimental.allocator: allocatorObject, dispose;
400 
401     auto allocator = TestAllocator();
402     auto oldAllocator = theAllocator;
403     scope(exit) {
404         allocator.dispose(theAllocator);
405         theAllocator = oldAllocator;
406     }
407     theAllocator = allocatorObject(allocator);
408 
409     {
410         auto ptr = RefCounted!Struct(42);
411         (*ptr).shouldEqual(Struct(42));
412         Struct.numStructs.shouldEqual(1);
413     }
414 
415     Struct.numStructs.shouldEqual(0);
416 }
417 
418 version(LDC) {
419 
420     //copied and modified from Phobos or else won't compile
421 
422     T* ldcEmplace(T, Args...)(T* chunk, auto ref Args args) if (is(T == struct) || Args.length == 1)
423     {
424         ldcEmplaceRef!T(*chunk, args);
425         return chunk;
426     }
427 
428 
429     import std.traits;
430 
431     void ldcEmplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args)
432         if (is(UT == Unqual!T))
433         {
434             static if (args.length == 0)
435             {
436                 static assert (is(typeof({static T i;})),
437                                convFormat("Cannot emplace a %1$s because %1$s.this() is annotated with @disable.", T.stringof));
438                 static if (is(T == class)) static assert (!isAbstractClass!T,
439                                                           T.stringof ~ " is abstract and it can't be emplaced");
440                 emplaceInitializer(chunk);
441             }
442             else static if (
443                 !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */
444                 ||
445                 Args.length == 1 && is(typeof({T t = args[0];})) /* conversions */
446                 ||
447                 is(typeof(T(args))) /* general constructors */
448                 ||
449                 is(typeof(shared T(args)))
450                 )
451             {
452                 static struct S
453                 {
454                     static if(is(typeof(shared T(args))))
455                         shared T payload;
456                     else
457                         T payload;
458 
459                     this(ref Args x)
460                         {
461                             static if (Args.length == 1)
462                                 static if (is(typeof(payload = x[0])))
463                                     payload = x[0];
464                                 else static if(is(typeof(shared T(x[0]))))
465                                     payload = shared T(x[0]);
466                                 else
467                                     payload = T(x[0]);
468                             else static if(is(typeof(shared T(x))))
469                                 payload = shared T(x);
470                             else
471                                 payload = T(x);
472                         }
473                 }
474                 if (__ctfe)
475                 {
476                     static if (is(typeof(chunk = T(args))))
477                         chunk = T(args);
478                     else static if (args.length == 1 && is(typeof(chunk = args[0])))
479                         chunk = args[0];
480                     else assert(0, "CTFE emplace doesn't support "
481                                 ~ T.stringof ~ " from " ~ Args.stringof);
482                 }
483                 else
484                 {
485                     S* p = () @trusted { return cast(S*) &chunk; }();
486                     emplaceInitializer(*p);
487                     p.__ctor(args);
488                 }
489             }
490             else static if (is(typeof(chunk.__ctor(args))))
491             {
492                 // This catches the rare case of local types that keep a frame pointer
493                 emplaceInitializer(chunk);
494                 chunk.__ctor(args);
495             }
496             else
497             {
498                 //We can't emplace. Try to diagnose a disabled postblit.
499                 static assert(!(Args.length == 1 && is(Args[0] : T)),
500                               convFormat("Cannot emplace a %1$s because %1$s.this(this) is annotated with @disable.", T.stringof));
501 
502                 //We can't emplace.
503                 static assert(false,
504                               convFormat("%s cannot be emplaced from %s.", T.stringof, Args[].stringof));
505             }
506         }
507 
508 
509     //emplace helper functions
510     private void emplaceInitializer(T)(ref T chunk) @trusted pure nothrow
511     {
512         static if (!hasElaborateAssign!T && isAssignable!T)
513             chunk = T.init;
514         else
515         {
516             import core.stdc.string : memcpy;
517             static immutable T init = T.init;
518             memcpy(&chunk, &init, T.sizeof);
519         }
520     }
521 
522 }