1 module libd.memory.funcs;
2 
3 import libd.memory.allocator : SystemAllocator, AllocatorWrapperOf;
4 import libd.memory.ptr;
5 import libd.meta.traits : isPointer, UdaOrDefault;
6 
7 __gshared AllocatorWrapperOf!SystemAllocator g_alloc;
8 
9 enum OnMove
10 {
11     allow,
12     forbid,
13 }
14 
15 @nogc nothrow
16 void memcpy(scope const void* source, scope void* dest, size_t bytes)
17 {
18     auto sourceBytes = cast(const ubyte*)source;
19     auto destBytes = cast(ubyte*)dest;
20 
21     // LDC knows how to auto-vectorise this.
22     // Shock horror, DMD can't though.
23     // Even worse, if we write it slightly differently, it calls the C lib's memcpy, which defeats the point of this hobby project xP
24     for(size_t i = 0; i < bytes; i++)
25         destBytes[i] = sourceBytes[i];
26 }
27 ///
28 @("memcpy")
29 unittest
30 {
31     const ubyte[5] source = [1, 2, 3, 4, 5];
32     ubyte[5] dest;
33     memcpy(source.ptr, &dest[0], 5);
34     assert(dest[] == source);
35 }
36 
37 private struct ByteSplitInfo
38 {
39     size_t xmms;
40     size_t longs;
41     size_t ints;
42     size_t shorts;
43     size_t bytes;
44 
45     private static struct xmm
46     {
47         ubyte[16] _;
48     }
49 
50     static ByteSplitInfo of(alias T)()
51     {
52         import libd.meta : AliasSeq;
53 
54         auto size = T.sizeof;
55         ByteSplitInfo info;
56 
57         size_t result;
58         
59         static foreach(type; AliasSeq!(xmm, long, int, short, byte))
60         {
61             result = size / type.sizeof;
62             if(result > 0)
63             {
64                 mixin("info."~type.stringof~"s = result;");
65                 size -= type.sizeof * result;
66             }
67         }
68 
69         return info;
70     }
71     @("ByteSplitInfo.of")
72     unittest
73     {
74         static struct S(size_t size)
75         {
76             ubyte[size] s;
77         }
78 
79         assert(ByteSplitInfo.of!long  == ByteSplitInfo(0, 1));
80         assert(ByteSplitInfo.of!int   == ByteSplitInfo(0, 0, 1));
81         assert(ByteSplitInfo.of!short == ByteSplitInfo(0, 0, 0, 1));
82         assert(ByteSplitInfo.of!byte  == ByteSplitInfo(0, 0, 0, 0, 1));
83 
84         assert(ByteSplitInfo.of!(S!3)  == ByteSplitInfo(0, 0, 0, 1, 1));
85         assert(ByteSplitInfo.of!(S!3)  == ByteSplitInfo(0, 0, 0, 1, 1));
86         assert(ByteSplitInfo.of!(S!6)  == ByteSplitInfo(0, 0, 1, 1, 0));
87         assert(ByteSplitInfo.of!(S!13) == ByteSplitInfo(0, 1, 1, 0, 1));
88     }
89 }
90 
91 // This is faster than a normal byte-by-byte memcpy. It *should* be faster than the vectoirsed version LDC produces as well
92 // as we don't have to handle every different possible situation.
93 private void memcpySmart(alias T)(scope T* source, scope T* dest)
94 {
95     static if(!is(T == struct) || (T.sizeof <= 64 && __traits(isPOD, T)))
96     {
97         *dest = *source;
98     }
99     else
100     {
101         // LDC -O3 knows how to alias these properly, so no extra instructions are made.
102         auto source8  = cast(ubyte*)source;
103         auto source16 = cast(ushort*)source;
104         auto source32 = cast(uint*)source;
105         auto source64 = cast(ulong*)source;
106         auto dest8    = cast(ubyte*)dest;
107         auto dest16   = cast(ushort*)dest;
108         auto dest32   = cast(uint*)dest;
109         auto dest64   = cast(ulong*)dest;
110 
111         enum info = ByteSplitInfo.of!T;
112         static foreach(i; 0..info.xmms)
113         {
114             static if(i == 0)
115             asm @nogc nothrow pure
116             {
117                 mov RAX, [source];
118                 mov RCX, [dest];
119             }
120 
121             asm @nogc nothrow pure
122             {
123                 movdqu XMM1, [RAX+i*16];
124                 movdqu [RCX+i*16], XMM1;
125             }
126         }
127 
128         enum offset64 = info.xmms * 2;
129         static foreach(i; 0..info.longs)
130             dest64[i+offset64] = source64[i+offset64];
131 
132         enum offset32 = info.longs * 2;
133         static foreach(i; 0..info.ints)
134             dest32[i+offset32] = source32[i+offset32];
135 
136         enum offset16 = offset32 + (info.ints * 2);
137         static foreach(i; 0..info.shorts)
138             dest16[i+offset16] = source16[i+offset16];
139 
140         enum offset8 = offset16 + (info.shorts * 2);
141         static foreach(i; 0..info.bytes)
142             dest8[i+offset8] = source8[i+offset8];
143         return;
144     }
145 }
146 
147 @nogc nothrow
148 void memset(ubyte value, scope void* dest, size_t amount)
149 {
150     auto destBytes = cast(ubyte*)dest;
151     for(size_t i = 0; i < amount; i++)
152         destBytes[i] = value;
153 }
154 ///
155 @("memset")
156 unittest
157 {
158     ubyte[5] dest;
159     memset(128, dest.ptr, 5);
160     // assert(dest == [128, 128, 128, 128, 128]);
161 }
162 
163 @nogc nothrow pragma(inline, true)
164 void move(T, bool makeSourceInit = true, bool destroyDest = true)(scope ref T source, scope return ref T dest)
165 {
166     enum MoveAction = UdaOrDefault!(OnMove, T, OnMove.allow);
167     static assert(MoveAction != OnMove.forbid, "Type `"~T.stringof~"` explicitly forbids being moved.");
168 
169     static if(destroyDest && __traits(compiles, T.init.__xdtor()) && !isPointer!T)
170         dest.__xdtor();
171     static if(__traits(hasCopyConstructor, T))
172         dest.__ctor(source);
173     else
174         memcpySmart!T(&source, &dest);
175 
176     static if(makeSourceInit)
177     {
178         auto init = T.init;
179         memcpySmart!T(&init, &source);
180     }
181     
182     // TODO: Apply OnMove actions for struct members as well, as members' `OnMove` udas aren't respected like this.
183     ///      e.g. T could have a member marked as @OnMove.forbid, but that wouldn't be respected since we don't look for it.
184 }
185 ///
186 @("move")
187 unittest
188 {
189     // int postblitCount;
190     // int dtorCount;
191     // struct S
192     // {
193     //     @nogc nothrow:
194     //     int value;
195     //     this(this){ postblitCount++; }
196     //     ~this(){ if(value > 0) dtorCount++; }
197     // }
198 
199     // S a = S(20);
200     // S b = S(40);
201 
202     // assert(postblitCount == 0 && dtorCount == 0);
203     // move(a, b);
204     // assert(dtorCount == 1);
205     // assert(postblitCount == 0);
206     // assert(a == S.init);
207     // assert(b.value == 20);
208 }
209 @("move - !isCopyable")
210 unittest
211 {
212     struct S
213     {
214         @nogc nothrow:
215         int value;
216         @disable this(this){}
217     }
218 
219     S a = S(20);
220     S b = S(40);
221     move(a, b);
222 
223     assert(a == S.init);
224     assert(b.value == 20);
225 }
226 @("move - forbid")
227 unittest
228 {
229     @(OnMove.forbid)
230     struct S
231     {
232     }
233 
234     S a, b;
235     static assert(!__traits(compiles, move(a, b)));
236 }
237 @("move - callPostblit")
238 unittest
239 {
240     // int c;
241 
242     // struct S
243     // {
244     //     @nogc nothrow
245     //     this(ref return scope S s)
246     //     {
247     //         c++;
248     //     }
249     // }
250 
251     // S a, b;
252     // move(a, b);
253     // assert(c == 1);
254 }
255 
256 @nogc nothrow
257 void emplaceCtor(T, Params...)(scope ref T dest, scope auto ref Params params)
258 {
259     static if(is(T == struct))
260         auto value = T(params);
261     else static if(Params.length > 0)
262         auto value = params[0];
263     else
264         auto value = T.init;
265     move(value, dest);
266 }
267 @("emplaceCtor")
268 unittest
269 {
270     static struct S
271     {
272         @nogc nothrow:
273         int value;
274         @disable this(this){}
275     }
276 
277     S a;
278     emplaceCtor(a, 20);
279     assert(a.value == 20);
280 }
281 
282 @nogc nothrow
283 void emplaceInit(T)(scope ref T dest)
284 {
285     auto value = T.init;
286     move!(T, false)(value, dest);
287 }
288 @("emplaceInit")
289 unittest
290 {
291     static struct S
292     {
293         int value = 100;
294         @disable this(this){}
295     }
296     
297     S a = S(200);
298     emplaceInit(a);
299     assert(a.value == 100);
300 }
301 
302 @nogc nothrow
303 void dtorSliceIfNeeded(T)(scope NotNullSlice!T slice)
304 {
305     static if(__traits(hasMember, T, "__xdtor"))
306     {
307         foreach(ref item; slice[0..$])
308             item.__xdtor();
309     }
310 }
311 @("dtorSliceIfNeeded")
312 unittest
313 {
314     int dtor = 0;
315     static struct S
316     {
317         int* dtor;
318         @disable this(this){}
319         ~this() @nogc nothrow
320         {
321             if(dtor) (*dtor)++;
322         }
323     }
324 
325     S[2] array = [S(&dtor), S(&dtor)];
326     array.notNull.dtorSliceIfNeeded();
327     assert(dtor == 2);
328     array[0] = S.init;
329     array[1] = S.init;
330 }
331 
332 void _d_memoryInit()
333 {
334     
335 }