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 }