1 module automem.ref_counted; 2 3 import automem.traits: isAllocator; 4 import automem.test_utils: TestUtils; 5 import automem.unique: Unique; 6 import std.experimental.allocator: theAllocator, processAllocator; 7 import std.typecons: Flag; 8 9 version(unittest) { 10 import unit_threaded; 11 import test_allocator: TestAllocator; 12 } 13 14 mixin TestUtils; 15 16 struct RefCounted(Type, Allocator = typeof(theAllocator), 17 Flag!"supportGC" supportGC = Flag!"supportGC".yes) 18 if(isAllocator!Allocator) { 19 20 import std.traits: hasMember; 21 22 enum isSingleton = hasMember!(Allocator, "instance"); 23 enum isTheAllocator = is(Allocator == typeof(theAllocator)); 24 enum isGlobal = isSingleton || isTheAllocator; 25 26 static if(isGlobal) 27 /** 28 The allocator is a singleton, so no need to pass it in to the 29 constructor 30 */ 31 this(Args...)(auto ref Args args) { 32 this.makeObject!args(); 33 } 34 else 35 /** 36 Non-singleton allocator, must be passed in 37 */ 38 this(Args...)(Allocator allocator, auto ref Args args) { 39 _allocator = allocator; 40 this.makeObject!args(); 41 } 42 43 static if(isGlobal) 44 /** 45 Factory method so can construct with zero args. 46 */ 47 static typeof(this) construct(Args...)(auto ref Args args) { 48 static if (Args.length != 0) 49 return typeof(return)(args); 50 else { 51 typeof(return) ret; 52 ret.makeObject!()(); 53 return ret; 54 } 55 } 56 else 57 /** 58 Factory method. Not necessary with non-global allocator 59 but included for symmetry. 60 */ 61 static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) { 62 return typeof(return)(allocator, args); 63 } 64 65 this(this) { 66 assert(_impl !is null); 67 inc; 68 } 69 70 ~this() { 71 release; 72 } 73 74 /** 75 Assign to an lvalue RefCounted 76 */ 77 void opAssign(ref RefCounted other) { 78 79 if (_impl == other._impl) 80 return; 81 82 if(_impl !is null) { 83 release; 84 } 85 static if(!isGlobal) 86 _allocator = other._allocator; 87 88 _impl = other._impl; 89 inc; 90 } 91 92 /** 93 Assign to an rvalue RefCounted 94 */ 95 void opAssign(RefCounted other) { 96 import std.algorithm: swap; 97 swap(_impl, other._impl); 98 static if(!isGlobal) 99 swap(_allocator, other._allocator); 100 } 101 102 /** 103 Dereference the smart pointer and yield a reference 104 to the contained type. 105 */ 106 ref auto opUnary(string s)() inout if (s == "*") { 107 return _impl._get; 108 } 109 110 // Prevent opSlice and opIndex from being hidden by Impl*. 111 // This comment is deliberately not DDOC. 112 auto ref opSlice(A...)(auto ref A args) 113 if (__traits(compiles, Type.init.opSlice(args))) 114 { 115 return _impl._get.opSlice(args); 116 } 117 // ditto 118 auto ref opIndex(A...)(auto ref A args) 119 if (__traits(compiles, Type.init.opIndex(args))) 120 { 121 return _impl._get.opIndex(args); 122 } 123 // ditto 124 auto ref opIndexAssign(A...)(auto ref A args) 125 if (__traits(compiles, Type.init.opIndexAssign(args))) 126 { 127 return _impl._get.opIndexAssign(args); 128 } 129 130 alias _impl this; 131 132 private: 133 134 static struct Impl { 135 136 static if(is(Type == class)) { 137 138 align ((void*).alignof) 139 void[__traits(classInstanceSize, Type)] _rawMemory; 140 141 } else 142 Type _object; 143 144 static if(is(Type == shared)) 145 shared size_t _count; 146 else 147 size_t _count; 148 149 static if (is(Type == class)) { 150 inout(Type) _get() inout { 151 return cast(inout(Type))&_rawMemory[0]; 152 } 153 154 inout(shared(Type)) _get() inout shared { 155 return cast(inout(shared(Type)))&_rawMemory[0]; 156 } 157 } else { 158 ref inout(Type) _get() inout { 159 return _object; 160 } 161 162 ref inout(shared(Type)) _get() inout shared { 163 return _object; 164 } 165 } 166 167 alias _get this; 168 } 169 170 static if(isSingleton) 171 alias _allocator = Allocator.instance; 172 else static if(isTheAllocator) { 173 static if (is(Type == shared)) 174 // 'processAllocator' should be used for allocating 175 // memory shared across threads 176 alias _allocator = processAllocator; 177 else 178 alias _allocator = theAllocator; 179 } 180 else 181 Allocator _allocator; 182 183 static if(is(Type == shared)) 184 alias ImplType = shared Impl; 185 else 186 alias ImplType = Impl; 187 188 public ImplType* _impl; // public or alias this doesn't work 189 190 void allocateImpl() { 191 import std.experimental.allocator: make; 192 import std.traits: hasIndirections; 193 194 _impl = cast(typeof(_impl))_allocator.allocate(Impl.sizeof); 195 _impl._count= 1; 196 197 static if (is(Type == class)) { 198 // class representation: 199 // void* classInfoPtr 200 // void* monitorPtr 201 // [] interfaces 202 // T... members 203 import core.memory: GC; 204 if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) 205 // members have pointers: we have to watch the monitor 206 // and all members; skip the classInfoPtr 207 GC.addRange(&_impl._rawMemory[(void*).sizeof], 208 __traits(classInstanceSize, Type) - (void*).sizeof); 209 else 210 // representation doesn't have pointers, just watch the 211 // monitor pointer; skip the classInfoPtr 212 // need to watch the monitor pointer even if supportGC is false. 213 GC.addRange(&_impl._rawMemory[(void*).sizeof], (void*).sizeof); 214 } else static if (supportGC && hasIndirections!Type) { 215 import core.memory: GC; 216 GC.addRange(&_impl._object, Type.sizeof); 217 } 218 } 219 220 void release() { 221 import std.traits : hasIndirections; 222 import core.memory : GC; 223 import automem.utils : destruct; 224 if(_impl is null) return; 225 assert(_impl._count > 0, "Trying to release a RefCounted but ref count is 0 or less"); 226 227 dec; 228 229 if(_impl._count == 0) { 230 destruct(_impl._get); 231 static if (is(Type == class)) { 232 // need to watch the monitor pointer even if supportGC is false. 233 GC.removeRange(&_impl._rawMemory[(void*).sizeof]); 234 } else static if (supportGC && hasIndirections!Type) { 235 GC.removeRange(&_impl._object); 236 } 237 auto mem = cast(void*)_impl; 238 _allocator.deallocate(() @trusted { return mem[0 .. Impl.sizeof]; }()); 239 } 240 } 241 242 void inc() { 243 static if(is(Type == shared)) { 244 import core.atomic: atomicOp; 245 _impl._count.atomicOp!"+="(1); 246 } else 247 ++_impl._count; 248 249 } 250 251 void dec() { 252 static if(is(Type == shared)) { 253 import core.atomic: atomicOp; 254 _impl._count.atomicOp!"-="(1); 255 } else 256 --_impl._count; 257 } 258 259 } 260 261 private template makeObject(args...) 262 { 263 void makeObject(Type, A)(ref RefCounted!(Type, A) rc) @trusted { 264 import std.conv: emplace; 265 import std.functional : forward; 266 267 rc.allocateImpl; 268 269 static if(is(Type == class)) 270 emplace!Type(rc._impl._rawMemory, forward!args); 271 else 272 emplace(&rc._impl._object, forward!args); 273 } 274 } 275 276 @("struct test allocator no copies") 277 @system unittest { 278 auto allocator = TestAllocator(); 279 { 280 auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 281 Struct.numStructs.shouldEqual(1); 282 } 283 Struct.numStructs.shouldEqual(0); 284 } 285 286 @("struct test allocator one lvalue assignment") 287 @system unittest { 288 auto allocator = TestAllocator(); 289 { 290 auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 291 Struct.numStructs.shouldEqual(1); 292 RefCounted!(Struct, TestAllocator*) ptr2; 293 ptr2 = ptr1; 294 Struct.numStructs.shouldEqual(1); 295 } 296 Struct.numStructs.shouldEqual(0); 297 } 298 299 @("struct test allocator one rvalue assignment test allocator") 300 @system unittest { 301 auto allocator = TestAllocator(); 302 { 303 RefCounted!(Struct, TestAllocator*) ptr; 304 ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 305 Struct.numStructs.shouldEqual(1); 306 } 307 Struct.numStructs.shouldEqual(0); 308 } 309 310 @("struct test allocator one rvalue assignment mallocator") 311 @system unittest { 312 import std.experimental.allocator.mallocator: Mallocator; 313 { 314 RefCounted!(Struct, Mallocator) ptr; 315 ptr = RefCounted!(Struct, Mallocator)(5); 316 Struct.numStructs.shouldEqual(1); 317 } 318 Struct.numStructs.shouldEqual(0); 319 } 320 321 322 @("struct test allocator one lvalue copy constructor") 323 @system unittest { 324 auto allocator = TestAllocator(); 325 { 326 auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 327 Struct.numStructs.shouldEqual(1); 328 auto ptr2 = ptr1; 329 Struct.numStructs.shouldEqual(1); 330 331 ptr1.i.shouldEqual(5); 332 ptr2.i.shouldEqual(5); 333 } 334 Struct.numStructs.shouldEqual(0); 335 } 336 337 @("struct test allocator one rvalue copy constructor") 338 @system unittest { 339 auto allocator = TestAllocator(); 340 { 341 auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 342 Struct.numStructs.shouldEqual(1); 343 } 344 Struct.numStructs.shouldEqual(0); 345 } 346 347 @("many copies made") 348 @system unittest { 349 auto allocator = TestAllocator(); 350 351 // helper function for intrusive testing, in case the implementation 352 // ever changes 353 size_t refCount(T)(ref T ptr) { 354 return ptr._impl._count; 355 } 356 357 { 358 auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 359 Struct.numStructs.shouldEqual(1); 360 361 auto ptr2 = ptr1; 362 Struct.numStructs.shouldEqual(1); 363 364 { 365 auto ptr3 = ptr2; 366 Struct.numStructs.shouldEqual(1); 367 368 refCount(ptr1).shouldEqual(3); 369 refCount(ptr2).shouldEqual(3); 370 refCount(ptr3).shouldEqual(3); 371 } 372 373 Struct.numStructs.shouldEqual(1); 374 refCount(ptr1).shouldEqual(2); 375 refCount(ptr2).shouldEqual(2); 376 377 auto produce() { 378 return RefCounted!(Struct, TestAllocator*)(&allocator, 3); 379 } 380 381 ptr1 = produce; 382 Struct.numStructs.shouldEqual(2); 383 refCount(ptr1).shouldEqual(1); 384 refCount(ptr2).shouldEqual(1); 385 386 ptr1.twice.shouldEqual(6); 387 ptr2.twice.shouldEqual(10); 388 } 389 390 Struct.numStructs.shouldEqual(0); 391 } 392 393 @("default allocator") 394 @system unittest { 395 { 396 auto ptr = RefCounted!Struct(5); 397 Struct.numStructs.shouldEqual(1); 398 } 399 Struct.numStructs.shouldEqual(0); 400 } 401 402 // FIXME: Github #13 403 // @("default allocator (shared)") 404 // @system unittest { 405 // { 406 // auto ptr = RefCounted!(shared SharedStruct)(5); 407 // SharedStruct.numStructs.shouldEqual(1); 408 // } 409 // SharedStruct.numStructs.shouldEqual(0); 410 // } 411 412 @("deref") 413 @system unittest { 414 auto allocator = TestAllocator(); 415 auto rc1 = RefCounted!(int, TestAllocator*)(&allocator, 5); 416 417 (*rc1).shouldEqual(5); 418 auto rc2 = rc1; 419 *rc2 = 42; 420 (*rc1).shouldEqual(42); 421 } 422 423 @("swap") 424 @system unittest { 425 import std.algorithm: swap; 426 RefCounted!(int, TestAllocator*) rc1, rc2; 427 swap(rc1, rc2); 428 } 429 430 @("phobos bug 6606") 431 @system unittest { 432 433 union U { 434 size_t i; 435 void* p; 436 } 437 438 struct S { 439 U u; 440 } 441 442 alias SRC = RefCounted!(S, TestAllocator*); 443 } 444 445 @("phobos bug 6436") 446 @system unittest 447 { 448 static struct S { 449 this(ref int val, string file = __FILE__, size_t line = __LINE__) { 450 val.shouldEqual(3, file, line); 451 ++val; 452 } 453 } 454 455 auto allocator = TestAllocator(); 456 int val = 3; 457 auto s = RefCounted!(S, TestAllocator*)(&allocator, val); 458 val.shouldEqual(4); 459 } 460 461 @("assign from T") 462 @system unittest { 463 import std.experimental.allocator.mallocator: Mallocator; 464 465 { 466 auto a = RefCounted!(Struct, Mallocator)(3); 467 Struct.numStructs.shouldEqual(1); 468 469 *a = Struct(5); 470 Struct.numStructs.shouldEqual(1); 471 (*a).shouldEqual(Struct(5)); 472 473 RefCounted!(Struct, Mallocator) b; 474 b = a; 475 (*b).shouldEqual(Struct(5)); 476 Struct.numStructs.shouldEqual(1); 477 } 478 479 Struct.numStructs.shouldEqual(0); 480 } 481 482 @("assign self") 483 @system unittest { 484 auto allocator = TestAllocator(); 485 { 486 auto a = RefCounted!(Struct, TestAllocator*)(&allocator, 1); 487 a = a; 488 Struct.numStructs.shouldEqual(1); 489 } 490 Struct.numStructs.shouldEqual(0); 491 } 492 493 // FIXME: Github #13 494 // @("SharedStruct") 495 // @system unittest { 496 // auto allocator = TestAllocator(); 497 // { 498 // auto ptr = RefCounted!(shared SharedStruct, TestAllocator*)(&allocator, 5); 499 // SharedStruct.numStructs.shouldEqual(1); 500 // } 501 // SharedStruct.numStructs.shouldEqual(0); 502 // } 503 504 @("@nogc @safe") 505 @safe @nogc unittest { 506 507 auto allocator = SafeAllocator(); 508 509 { 510 const ptr = RefCounted!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 511 assert(ptr.i == 6); 512 assert(NoGcStruct.numStructs == 1); 513 } 514 515 assert(NoGcStruct.numStructs == 0); 516 } 517 518 519 @("const object") 520 @system unittest { 521 auto allocator = TestAllocator(); 522 auto ptr1 = RefCounted!(const Struct, TestAllocator*)(&allocator, 5); 523 } 524 525 526 @("theAllocator") 527 @system unittest { 528 529 with(theTestAllocator) { 530 auto ptr = RefCounted!Struct(42); 531 (*ptr).shouldEqual(Struct(42)); 532 Struct.numStructs.shouldEqual(1); 533 } 534 535 Struct.numStructs.shouldEqual(0); 536 } 537 538 // FIXME: Github #13 539 // @("threads Mallocator") 540 // @system unittest { 541 // import std.experimental.allocator.mallocator: Mallocator; 542 // static assert(__traits(compiles, sendRefCounted!Mallocator(7))); 543 // } 544 545 // FIXME: Github #13 546 // @("threads SafeAllocator by value") 547 // @system unittest { 548 // // can't even use TestAllocator because it has indirections 549 // // can't pass by pointer since it's an indirection 550 // auto allocator = SafeAllocator(); 551 // static assert(__traits(compiles, sendRefCounted!(SafeAllocator)(allocator, 7))); 552 // } 553 554 // FIXME: Github #13 555 // @("threads SafeAllocator by shared pointer") 556 // @system unittest { 557 // // can't even use TestAllocator because it has indirections 558 // // can't only pass by pointer if shared 559 // auto allocator = shared SafeAllocator(); 560 // static assert(__traits(compiles, sendRefCounted!(shared SafeAllocator*)(&allocator, 7))); 561 // } 562 563 auto refCounted(Type, Allocator)(Unique!(Type, Allocator) ptr) { 564 565 RefCounted!(Type, Allocator) ret; 566 567 static if(!ptr.isGlobal) 568 ret._allocator = ptr.allocator; 569 570 ret.allocateImpl; 571 *ret = *ptr; 572 573 return ret; 574 } 575 576 @("Construct RefCounted from Unique") 577 @system unittest { 578 import automem.unique: Unique; 579 auto allocator = TestAllocator(); 580 auto ptr = refCounted(Unique!(int, TestAllocator*)(&allocator, 42)); 581 (*ptr).shouldEqual(42); 582 } 583 584 @("RefCounted with class") 585 @system unittest { 586 auto allocator = TestAllocator(); 587 { 588 writelnUt("Creating ptr"); 589 auto ptr = RefCounted!(Class, TestAllocator*)(&allocator, 33); 590 (*ptr).i.shouldEqual(33); 591 Class.numClasses.shouldEqual(1); 592 } 593 Class.numClasses.shouldEqual(0); 594 } 595 596 @("@nogc class destructor") 597 @nogc unittest { 598 599 auto allocator = SafeAllocator(); 600 601 { 602 const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6); 603 // shouldEqual isn't @nogc 604 assert(ptr.i == 6); 605 assert(NoGcClass.numClasses == 1); 606 } 607 608 assert(NoGcClass.numClasses == 0); 609 } 610 611 @("RefCounted opSlice and opIndex") 612 @system unittest { 613 import std.mmfile: MmFile; 614 auto file = RefCounted!MmFile(null, MmFile.Mode.readWriteNew, 120, null); 615 // The type of file[0] should be ubyte, not Impl. 616 static assert(is(typeof(file[0]) == typeof(MmFile.init[0]))); 617 // opSlice should result in void[] not Impl[]. 618 static assert(is(typeof(file[0 .. size_t.max]) == typeof(MmFile.init[0 .. size_t.max]))); 619 ubyte[] data = cast(ubyte[]) file[0 .. cast(size_t) file.length]; 620 immutable ubyte b = file[1]; 621 file[1] = cast(ubyte) (b + 1); 622 assert(data[1] == cast(ubyte) (b + 1)); 623 } 624 625 @("Construct RefCounted using global allocator for struct with zero-args ctor") 626 @system unittest { 627 struct S { 628 private ulong zeroArgsCtorTest = 3; 629 } 630 auto s = RefCounted!S.construct(); 631 static assert(is(typeof(s) == RefCounted!S)); 632 assert(s._impl !is null); 633 assert(s.zeroArgsCtorTest == 3); 634 } 635 636 version(unittest): 637 638 void sendRefCounted(Allocator, Args...)(Args args) { 639 import std.concurrency: spawn, send; 640 641 auto tid = spawn(&threadFunc); 642 auto ptr = RefCounted!(shared SharedStruct, Allocator)(args); 643 644 tid.send(ptr); 645 } 646 647 void threadFunc() { 648 649 }