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 }