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 }