1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 /**
6  * Lifetime management functions, types and related exceptions.
7  *
8  * Copyright: Eugene Wissner 2019-2020.
9  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
10  *                  Mozilla Public License, v. 2.0).
11  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
12  * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/lifetime.d,
13  *                 tanya/memory/lifetime.d)
14  */
15 module tanya.memory.lifetime;
16 
17 import tanya.memory.allocator;
18 import tanya.meta.metafunction;
19 import tanya.meta.trait;
20 
21 package(tanya) void destroyAllImpl(R, E)(R p)
22 {
23     static if (hasElaborateDestructor!E)
24     {
25         foreach (ref e; p)
26         {
27             destroy(e);
28         }
29     }
30 }
31 
32 /**
33  * Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the
34  * given arguments.
35  *
36  * If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference
37  * of type $(D_PARAM T), otherwise a pointer to the constructed object is
38  * returned.
39  *
40  * If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer)
41  * should be an instance of the outer class.
42  *
43  * $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If
44  * $(D_PARAM T) isn't an aggregate type and doesn't have a constructor,
45  * $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`,
46  * `Args[0]` should be implicitly convertible to $(D_PARAM T) then.
47  *
48  * Params:
49  *  T     = Constructed type.
50  *  U     = Type of the outer class if $(D_PARAM T) is a nested class.
51  *  Args  = Types of the constructor arguments if $(D_PARAM T) has a constructor
52  *          or the type of the initial value.
53  *  outer = Outer class instance if $(D_PARAM T) is a nested class.
54  *  args  = Constructor arguments if $(D_PARAM T) has a constructor or the
55  *          initial value.
56  *
57  * Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory).
58  *
59  * Precondition: `memory.length == stateSize!T`.
60  * Postcondition: $(D_PARAM memory) and the result point to the same memory.
61  */
62 T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args)
63 if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U))
64 in
65 {
66     assert(memory.length >= stateSize!T);
67 }
68 out (result)
69 {
70     assert(memory.ptr is (() @trusted => cast(void*) result)());
71 }
72 do
73 {
74     import tanya.memory.op : copy;
75 
76     copy(typeid(T).initializer, memory);
77 
78     auto result = (() @trusted => cast(T) memory.ptr)();
79     result.outer = outer;
80 
81     static if (is(typeof(result.__ctor(args))))
82     {
83         result.__ctor(args);
84     }
85 
86     return result;
87 }
88 
89 /// ditto
90 T emplace(T, Args...)(void[] memory, auto ref Args args)
91 if (is(T == class) && !isAbstractClass!T && !isInnerClass!T)
92 in
93 {
94     assert(memory.length == stateSize!T);
95 }
96 out (result)
97 {
98     assert(memory.ptr is (() @trusted => cast(void*) result)());
99 }
100 do
101 {
102     import tanya.memory.op : copy;
103 
104     copy(typeid(T).initializer, memory);
105 
106     auto result = (() @trusted => cast(T) memory.ptr)();
107     static if (is(typeof(result.__ctor(args))))
108     {
109         result.__ctor(args);
110     }
111     return result;
112 }
113 
114 ///
115 @nogc nothrow pure @safe unittest
116 {
117     class C
118     {
119         int i = 5;
120         class Inner
121         {
122             int i;
123 
124             this(int param) pure nothrow @safe @nogc
125             {
126                 this.i = param;
127             }
128         }
129     }
130     ubyte[stateSize!C] memory1;
131     ubyte[stateSize!(C.Inner)] memory2;
132 
133     auto c = emplace!C(memory1);
134     assert(c.i == 5);
135 
136     auto inner = emplace!(C.Inner)(memory2, c, 8);
137     assert(c.i == 5);
138     assert(inner.i == 8);
139     assert(inner.outer is c);
140 }
141 
142 /// ditto
143 T* emplace(T, Args...)(void[] memory, auto ref Args args)
144 if (!isAggregateType!T && (Args.length <= 1))
145 in
146 {
147     assert(memory.length >= T.sizeof);
148 }
149 out (result)
150 {
151     assert(memory.ptr is result);
152 }
153 do
154 {
155     auto result = (() @trusted => cast(T*) memory.ptr)();
156     static if (Args.length == 1)
157     {
158         *result = T(args[0]);
159     }
160     else
161     {
162         *result = T.init;
163     }
164     return result;
165 }
166 
167 private void initializeOne(T)(ref void[] memory, ref T* result) @trusted
168 {
169     import tanya.memory.op : copy, fill;
170 
171     static if (!hasElaborateAssign!T && isAssignable!T)
172     {
173         *result = T.init;
174     }
175     else static if (__VERSION__ >= 2083 // __traits(isZeroInit) available.
176         && __traits(isZeroInit, T))
177     {
178         memory.ptr[0 .. T.sizeof].fill!0;
179     }
180     else
181     {
182         static immutable T init = T.init;
183         copy((&init)[0 .. 1], memory);
184     }
185 }
186 
187 /// ditto
188 T* emplace(T, Args...)(void[] memory, auto ref Args args)
189 if (!isPolymorphicType!T && isAggregateType!T)
190 in
191 {
192     assert(memory.length >= T.sizeof);
193 }
194 out (result)
195 {
196     assert(memory.ptr is result);
197 }
198 do
199 {
200     auto result = (() @trusted => cast(T*) memory.ptr)();
201 
202     static if (Args.length == 0)
203     {
204         static assert(is(typeof({ static T t; })),
205                       "Default constructor is disabled");
206         initializeOne(memory, result);
207     }
208     else static if (is(typeof(result.__ctor(args))))
209     {
210         initializeOne(memory, result);
211         result.__ctor(args);
212     }
213     else static if (Args.length == 1 && is(typeof({ T t = args[0]; })))
214     {
215         import tanya.memory.op : copy;
216 
217         ((ref arg) @trusted =>
218             copy((cast(void*) &arg)[0 .. T.sizeof], memory))(args[0]);
219         static if (hasElaborateCopyConstructor!T)
220         {
221             result.__postblit();
222         }
223     }
224     else static if (is(typeof({ T t = T(args); })))
225     {
226         auto init = T(args);
227         (() @trusted => moveEmplace(init, *result))();
228     }
229     else
230     {
231         static assert(false,
232                       "Unable to construct value with the given arguments");
233     }
234     return result;
235 }
236 
237 ///
238 @nogc nothrow pure @safe unittest
239 {
240     ubyte[4] memory;
241 
242     auto i = emplace!int(memory);
243     static assert(is(typeof(i) == int*));
244     assert(*i == 0);
245 
246     i = emplace!int(memory, 5);
247     assert(*i == 5);
248 
249     static struct S
250     {
251         int i;
252         @disable this();
253         @disable this(this);
254         this(int i) @nogc nothrow pure @safe
255         {
256             this.i = i;
257         }
258     }
259     auto s = emplace!S(memory, 8);
260     static assert(is(typeof(s) == S*));
261     assert(s.i == 8);
262 }
263 
264 private void deinitialize(bool zero, T)(ref T value)
265 {
266     static if (is(T == U[S], U, size_t S))
267     {
268         foreach (ref e; value)
269         {
270             deinitialize!zero(e);
271         }
272     }
273     else
274     {
275         import tanya.memory.op : copy, fill;
276 
277         static if (isNested!T)
278         {
279             // Don't override the context pointer.
280             enum size_t size = T.sizeof - (void*).sizeof;
281         }
282         else
283         {
284             enum size_t size = T.sizeof;
285         }
286         static if (zero)
287         {
288             fill!0((cast(void*) &value)[0 .. size]);
289         }
290         else
291         {
292             copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]);
293         }
294     }
295 }
296 
297 /**
298  * Moves $(D_PARAM source) into $(D_PARAM target) assuming that
299  * $(D_PARAM target) isn't initialized.
300  *
301  * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
302  * the $(D_PARAM source) into a valid but unspecified state, which means that
303  * after moving $(D_PARAM source) can be destroyed or assigned a new value, but
304  * accessing it yields an unspecified value. No postblits or destructors are
305  * called. If the $(D_PARAM target) should be destroyed before, use
306  * $(D_PSYMBOL move).
307  *
308  * $(D_PARAM source) and $(D_PARAM target) must be different objects.
309  *
310  * Params:
311  *  T      = Object type.
312  *  source = Source object.
313  *  target = Target object.
314  *
315  * See_Also: $(D_PSYMBOL move),
316  *           $(D_PSYMBOL hasElaborateCopyConstructor),
317  *           $(D_PSYMBOL hasElaborateDestructor).
318  *
319  * Precondition: `&source !is &target`.
320  */
321 void moveEmplace(T)(ref T source, ref T target) @system
322 in
323 {
324     assert(&source !is &target, "Source and target must be different");
325 }
326 do
327 {
328     static if (is(T == struct) || isStaticArray!T)
329     {
330         import tanya.memory.op : copy;
331 
332         copy((&source)[0 .. 1], (&target)[0 .. 1]);
333 
334         static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
335         {
336             static if (__VERSION__ >= 2083) // __traits(isZeroInit) available.
337             {
338                 deinitialize!(__traits(isZeroInit, T))(source);
339             }
340             else
341             {
342                 if (typeid(T).initializer().ptr is null)
343                 {
344                     deinitialize!true(source);
345                 }
346                 else
347                 {
348                     deinitialize!false(source);
349                 }
350             }
351         }
352     }
353     else
354     {
355         target = source;
356     }
357 }
358 
359 ///
360 @nogc nothrow pure @system unittest
361 {
362     static struct S
363     {
364         int member = 5;
365 
366         this(this) @nogc nothrow pure @safe
367         {
368             assert(false);
369         }
370     }
371     S source, target = void;
372     moveEmplace(source, target);
373     assert(target.member == 5);
374 
375     int x1 = 5, x2;
376     moveEmplace(x1, x2);
377     assert(x2 == 5);
378 }
379 
380 /**
381  * Moves $(D_PARAM source) into $(D_PARAM target) assuming that
382  * $(D_PARAM target) isn't initialized.
383  *
384  * Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
385  * the $(D_PARAM source) into a valid but unspecified state, which means that
386  * after moving $(D_PARAM source) can be destroyed or assigned a new value, but
387  * accessing it yields an unspecified value. $(D_PARAM target) is destroyed before
388  * the new value is assigned. If $(D_PARAM target) isn't initialized and
389  * therefore shouldn't be destroyed, $(D_PSYMBOL moveEmplace) can be used.
390  *
391  * If $(D_PARAM target) isn't specified, $(D_PSYMBOL move) returns the source
392  * as rvalue without calling its copy constructor or destructor.
393  *
394  * $(D_PARAM source) and $(D_PARAM target) are the same object,
395  * $(D_PSYMBOL move) does nothing.
396  *
397  * Params:
398  *  T      = Object type.
399  *  source = Source object.
400  *  target = Target object.
401  *
402  * See_Also: $(D_PSYMBOL moveEmplace).
403  */
404 void move(T)(ref T source, ref T target)
405 {
406     if ((() @trusted => &source is &target)())
407     {
408         return;
409     }
410     static if (hasElaborateDestructor!T)
411     {
412         target.__xdtor();
413     }
414     (() @trusted => moveEmplace(source, target))();
415 }
416 
417 /// ditto
418 T move(T)(ref T source) @trusted
419 {
420     static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
421     {
422         T target = void;
423         moveEmplace(source, target);
424         return target;
425     }
426     else
427     {
428         return source;
429     }
430 }
431 
432 ///
433 @nogc nothrow pure @safe unittest
434 {
435     static struct S
436     {
437         int member = 5;
438 
439         this(this) @nogc nothrow pure @safe
440         {
441             assert(false);
442         }
443     }
444     S source, target = void;
445     move(source, target);
446     assert(target.member == 5);
447     assert(move(target).member == 5);
448 
449     int x1 = 5, x2;
450     move(x1, x2);
451     assert(x2 == 5);
452     assert(move(x2) == 5);
453 }
454 
455 /**
456  * Exchanges the values of $(D_PARAM a) and $(D_PARAM b).
457  *
458  * $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b)
459  * without calling its postblits or destructors.
460  *
461  * Params:
462  *  a = The first object.
463  *  b = The second object.
464  */
465 void swap(T)(ref T a, ref T b) @trusted
466 {
467     T tmp = void;
468     moveEmplace(a, tmp);
469     moveEmplace(b, a);
470     moveEmplace(tmp, b);
471 }
472 
473 ///
474 @nogc nothrow pure @safe unittest
475 {
476     int a = 3, b = 5;
477     swap(a, b);
478     assert(a == 5);
479     assert(b == 3);
480 }
481 
482 /**
483  * Forwards its argument list preserving $(D_KEYWORD ref) and $(D_KEYWORD out)
484  * storage classes.
485  *
486  * $(D_PSYMBOL forward) accepts a list of variables or literals. It returns an
487  * argument list of the same length that can be for example passed to a
488  * function accepting the arguments of this type.
489  *
490  * Params:
491  *  args = Argument list.
492  *
493  * Returns: $(D_PARAM args) with their original storage classes.
494  */
495 template forward(args...)
496 {
497     static if (args.length == 0)
498     {
499         alias forward = AliasSeq!();
500     }
501     else static if (__traits(isRef, args[0]) || __traits(isOut, args[0]))
502     {
503         static if (args.length == 1)
504         {
505             alias forward = args[0];
506         }
507         else
508         {
509             alias forward = AliasSeq!(args[0], forward!(args[1 .. $]));
510         }
511     }
512     else
513     {
514         @property auto forwardOne()
515         {
516             return move(args[0]);
517         }
518         static if (args.length == 1)
519         {
520             alias forward = forwardOne;
521         }
522         else
523         {
524             alias forward = AliasSeq!(forwardOne, forward!(args[1 .. $]));
525         }
526     }
527 }
528 
529 ///
530 @nogc nothrow pure @safe unittest
531 {
532     static assert(is(typeof((int i) { int v = forward!i; })));
533     static assert(is(typeof((ref int i) { int v = forward!i; })));
534     static assert(is(typeof({
535         void f(int i, ref int j, out int k)
536         {
537             f(forward!(i, j, k));
538         }
539     })));
540 }