From 573a85b101d2371fe4ddba9b51763ba4094aaffd Mon Sep 17 00:00:00 2001 From: Eduard Staniloiu Date: Fri, 1 Dec 2017 20:54:28 +0200 Subject: [PATCH] Replace IAllocator with reference counted struct --- ...std-experimental-allocator-rciallocator.dd | 19 + ...perimental-allocator-rcisharedallocator.dd | 23 + .../building_blocks/affix_allocator.d | 12 +- std/experimental/allocator/common.d | 12 +- std/experimental/allocator/package.d | 650 ++++++++++++++++-- 5 files changed, 658 insertions(+), 58 deletions(-) create mode 100644 changelog/std-experimental-allocator-rciallocator.dd create mode 100644 changelog/std-experimental-allocator-rcisharedallocator.dd diff --git a/changelog/std-experimental-allocator-rciallocator.dd b/changelog/std-experimental-allocator-rciallocator.dd new file mode 100644 index 000000000..c59c8687c --- /dev/null +++ b/changelog/std-experimental-allocator-rciallocator.dd @@ -0,0 +1,19 @@ +Replace `std.experimental.allocator.IAllocator` with `std.experimental.allocator.RCIAllocator` + +$(B Motivation): + +Keep track of references to allocators so they don't escape, dangle, +and cause undefined behavior. + +From now on, `RCIAllocator` will be used instead of the old `IAllocator` +interface. $(REF allocatorObject, std, experimental, allocator) can be used to build +a `RCIAllocator` out of a custom allocator. + +------ +import std.experimental.allocator.mallocator : Mallocator; + +RCIAllocator a = allocatorObject(Mallocator.instance); +auto b = a.allocate(100); +assert(b.length == 100); +assert(a.deallocate(b)); +------ diff --git a/changelog/std-experimental-allocator-rcisharedallocator.dd b/changelog/std-experimental-allocator-rcisharedallocator.dd new file mode 100644 index 000000000..858d28c5b --- /dev/null +++ b/changelog/std-experimental-allocator-rcisharedallocator.dd @@ -0,0 +1,23 @@ +Replace `std.experimental.allocator.ISharedAllocator` with `std.experimental.allocator.RCISharedAllocator` + +$(B Motivation): + +Keep track of references to allocators so they don't escape, dangle, +and cause undefined behavior. + +From now on, `RCISharedAllocator` will be used instead of the old +`ISharedAllocator` interface. +$(REF sharedAllocatorObject, std, experimental, allocator)` can be used to build +a `RCISharedAllocator` out of a custom allocator. + +------ +import std.experimental.allocator.building_blocks.free_list : SharedFreeList; +import std.experimental.allocator.mallocator : Mallocator; + +shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) sharedFL; +shared RCISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); + +auto b = sharedFLObj.allocate(100); +assert(b.length == 100); +assert(sharedFLObj.deallocate(b)); +------ diff --git a/std/experimental/allocator/building_blocks/affix_allocator.d b/std/experimental/allocator/building_blocks/affix_allocator.d index 6042fe539..393dff77e 100644 --- a/std/experimental/allocator/building_blocks/affix_allocator.d +++ b/std/experimental/allocator/building_blocks/affix_allocator.d @@ -24,7 +24,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) { import std.algorithm.comparison : min; import std.conv : emplace; - import std.experimental.allocator : IAllocator, theAllocator; + import std.experimental.allocator : RCIAllocator, theAllocator; import std.experimental.allocator.common : stateSize, forwardToMember, roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf, hasStaticallyKnownAlignment; @@ -69,11 +69,11 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) static if (stateSize!Allocator) { Allocator _parent; - static if (is(Allocator == IAllocator)) + static if (is(Allocator == RCIAllocator)) { Allocator parent() { - if (_parent is null) _parent = theAllocator; + if (_parent.isNull) _parent = theAllocator; assert(alignment <= _parent.alignment); return _parent; } @@ -376,10 +376,10 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; - import std.experimental.allocator : theAllocator, IAllocator; + import std.experimental.allocator : theAllocator, RCIAllocator; // One word before and after each allocation. - auto A = AffixAllocator!(IAllocator, size_t, size_t)(theAllocator); + auto A = AffixAllocator!(RCIAllocator, size_t, size_t)(theAllocator); auto a = A.allocate(11); A.prefix(a) = 0xCAFE_BABE; A.suffix(a) = 0xDEAD_BEEF; @@ -387,7 +387,7 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void) && A.suffix(a) == 0xDEAD_BEEF); // One word before and after each allocation. - auto B = AffixAllocator!(IAllocator, size_t, size_t)(); + auto B = AffixAllocator!(RCIAllocator, size_t, size_t)(); auto b = B.allocate(11); B.prefix(b) = 0xCAFE_BABE; B.suffix(b) = 0xDEAD_BEEF; diff --git a/std/experimental/allocator/common.d b/std/experimental/allocator/common.d index 81839dee0..86115095b 100644 --- a/std/experimental/allocator/common.d +++ b/std/experimental/allocator/common.d @@ -481,7 +481,7 @@ Forwards each of the methods in `funs` (if defined) to `member`. version(unittest) { - import std.experimental.allocator : IAllocator, ISharedAllocator; + import std.experimental.allocator : RCIAllocator, RCISharedAllocator; package void testAllocator(alias make)() { @@ -607,18 +607,18 @@ version(unittest) }} } - package void testAllocatorObject(AllocInterface)(AllocInterface a) - if (is(AllocInterface : IAllocator) - || is (AllocInterface : shared ISharedAllocator)) + package void testAllocatorObject(RCAllocInterface)(RCAllocInterface a) + if (is(RCAllocInterface == RCIAllocator) + || is (RCAllocInterface == shared RCISharedAllocator)) { import std.conv : text; import std.math : isPowerOf2; import std.stdio : writeln, stderr; import std.typecons : Ternary; scope(failure) stderr.writeln("testAllocatorObject failed for ", - AllocInterface.stringof); + RCAllocInterface.stringof); - assert(a); + assert(!a.isNull); // Test alignment assert(a.alignment.isPowerOf2); diff --git a/std/experimental/allocator/package.d b/std/experimental/allocator/package.d index ecd2b6745..c7ecf9aea 100644 --- a/std/experimental/allocator/package.d +++ b/std/experimental/allocator/package.d @@ -286,6 +286,7 @@ implemented by the allocator instance. */ interface IAllocator { +nothrow: /** Returns the alignment offered. */ @@ -361,6 +362,251 @@ interface IAllocator $(D Ternary.unknown) if not supported. */ Ternary empty(); + + /** + Increases the reference count of the concrete class that implements this + interface. + + For stateless allocators, this does nothing. + */ + @safe @nogc + void incRef(); + + /** + Decreases the reference count of the concrete class that implements this + interface. + When the reference count is `0`, the object self-destructs. + + Returns: `true` if the reference count is greater than `0` and `false` when + it hits `0`. For stateless allocators, it always returns `true`. + */ + @safe @nogc + bool decRef(); +} + +/** +A reference counted struct that wraps the dynamic allocator interface. +This should be used wherever a uniform type is required for encapsulating +various allocator implementations. + +Code that defines allocators ultimately implements the $(LREF IAllocator) +interface, possibly by using $(LREF CAllocatorImpl) below, and then build a +`RCIAllocator` out of this. + +Composition of allocators is not recommended at this level due to +inflexibility of dynamic interfaces and inefficiencies caused by cascaded +multiple calls. Instead, compose allocators using the static interface defined +in $(A std_experimental_allocator_building_blocks.html, +`std.experimental.allocator.building_blocks`), then adapt the composed +allocator to `RCIAllocator` (possibly by using $(LREF allocatorObject) below). +*/ +struct RCIAllocator +{ + private IAllocator _alloc; + +nothrow: + private this(this _)(IAllocator alloc) + { + assert(alloc); + _alloc = alloc; + } + + @nogc @safe + this(this) + { + if (_alloc !is null) + { + _alloc.incRef(); + } + } + + @nogc + ~this() + { + if (_alloc !is null) + { + bool isLast = !_alloc.decRef(); + if (isLast) _alloc = null; + } + } + + @nogc + auto ref opAssign()(typeof(this) rhs) + { + if (_alloc is rhs._alloc) + { + return this; + } + // incRef was allready called by rhs posblit, so we're just moving + // calling dtor is the equivalent of decRef + __dtor(); + _alloc = rhs._alloc; + // move + rhs._alloc = null; + return this; + } + + pure nothrow @safe @nogc + bool isNull(this _)() + { + return _alloc is null; + } + + @property uint alignment() + { + assert(_alloc); + return _alloc.alignment(); + } + + size_t goodAllocSize(size_t s) + { + assert(_alloc); + return _alloc.goodAllocSize(s); + } + + void[] allocate(size_t n, TypeInfo ti = null) + { + assert(_alloc); + return _alloc.allocate(n, ti); + } + + void[] alignedAllocate(size_t n, uint a) + { + assert(_alloc); + return _alloc.alignedAllocate(n, a); + } + + void[] allocateAll() + { + assert(_alloc); + return _alloc.allocateAll(); + } + + bool expand(ref void[] b, size_t size) + { + assert(_alloc); + return _alloc.expand(b, size); + } + + bool reallocate(ref void[] b, size_t size) + { + assert(_alloc); + return _alloc.reallocate(b, size); + } + + bool alignedReallocate(ref void[] b, size_t size, uint alignment) + { + assert(_alloc); + return _alloc.alignedReallocate(b, size, alignment); + } + + Ternary owns(void[] b) + { + assert(_alloc); + return _alloc.owns(b); + } + + Ternary resolveInternalPointer(const void* p, ref void[] result) + { + assert(_alloc); + return _alloc.resolveInternalPointer(p, result); + } + + bool deallocate(void[] b) + { + assert(_alloc); + return _alloc.deallocate(b); + } + + bool deallocateAll() + { + assert(_alloc); + return _alloc.deallocateAll(); + } + + Ternary empty() + { + assert(_alloc); + return _alloc.empty(); + } +} + +unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + import std.conv : emplace; + + auto reg = Region!()(new ubyte[1024]); + auto state = reg.allocate(stateSize!(CAllocatorImpl!(Region!(), Yes.indirect))); + auto regObj = emplace!(CAllocatorImpl!(Region!(), Yes.indirect))(state, ®); + + auto rcalloc = RCIAllocator(regObj); + auto b = rcalloc.allocate(10); + assert(b.length == 10); + + // The reference counting is zero based + assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); + { + auto rca2 = rcalloc; + assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 2); + } + assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); +} + +unittest +{ + import std.conv; + import std.experimental.allocator.mallocator; + import std.experimental.allocator.building_blocks.stats_collector; + + alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); + SCAlloc statsCollectorAlloc; + + ulong bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == 0); + + { + auto _allocator = allocatorObject(&statsCollectorAlloc); + bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc, Yes.indirect))); + } + + bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == 0, "RCIAllocator leaks memory; leaked " + ~ to!string(bytesUsed) ~ " bytes"); +} + +unittest +{ + import std.conv; + import std.experimental.allocator.mallocator; + import std.experimental.allocator.building_blocks.stats_collector; + + alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); + SCAlloc statsCollectorAlloc; + + ulong bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == 0); + + { + auto _allocator = allocatorObject(statsCollectorAlloc); + + // Ensure that the allocator was passed through in CAllocatorImpl + // This allocator was used to allocate the chunk that holds the + // CAllocatorImpl object; which is it's own wrapper + bytesUsed = (cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)), + "RCIAllocator leaks memory; leaked " ~ to!string(bytesUsed) ~ " bytes"); + _allocator.allocate(1); + bytesUsed = (cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)) + 1, + "RCIAllocator leaks memory; leaked " ~ to!string(bytesUsed) ~ " bytes"); + } + + bytesUsed = statsCollectorAlloc.bytesUsed; + assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)), + "RCIAllocator leaks memory; leaked " + ~ to!string(bytesUsed) ~ " bytes"); } /** @@ -382,6 +628,7 @@ implemented by the allocator instance. */ interface ISharedAllocator { +nothrow: /** Returns the alignment offered. */ @@ -457,18 +704,192 @@ interface ISharedAllocator $(D Ternary.unknown) if not supported. */ Ternary empty() shared; + + /** + Increases the reference count of the concrete class that implements this + interface. + + For stateless allocators, this does nothing. + */ + @safe @nogc + void incRef() shared; + + /** + Decreases the reference count of the concrete class that implements this + interface. + When the reference count is `0`, the object self-destructs. + + For stateless allocators, this does nothing. + + Returns: `true` if the reference count is greater than `0` and `false` when + it hits `0`. For stateless allocators, it always returns `true`. + */ + @safe @nogc + bool decRef() shared; } -private shared ISharedAllocator _processAllocator; -private IAllocator _threadAllocator; +/** +A reference counted struct that wraps the dynamic shared allocator interface. +This should be used wherever a uniform type is required for encapsulating +various allocator implementations. -private IAllocator setupThreadAllocator() nothrow @nogc @safe +Code that defines allocators shareable across threads ultimately implements the +$(LREF ISharedAllocator) interface, possibly by using +$(LREF CSharedAllocatorImpl) below, and then build a `RCISharedAllocator` out +of this. + +Composition of allocators is not recommended at this level due to +inflexibility of dynamic interfaces and inefficiencies caused by cascaded +multiple calls. Instead, compose allocators using the static interface defined +in $(A std_experimental_allocator_building_blocks.html, +`std.experimental.allocator.building_blocks`), then adapt the composed allocator +to `RCISharedAllocator` (possibly by using $(LREF sharedAllocatorObject) below). +*/ +struct RCISharedAllocator +{ + private shared ISharedAllocator _alloc; + +nothrow: + private this(shared ISharedAllocator alloc) + { + assert(alloc); + _alloc = alloc; + } + + @nogc + this(this) shared + { + if (_alloc !is null) + { + _alloc.incRef(); + } + } + + @nogc + ~this() + { + if (_alloc !is null) + { + bool isLast = !_alloc.decRef(); + if (isLast) _alloc = null; + } + } + + @nogc + auto ref opAssign()(shared RCISharedAllocator rhs) shared + { + if (_alloc is rhs._alloc) + { + return this; + } + // incRef was allready called by rhs posblit, so we're just moving + if (_alloc !is null) + { + _alloc.decRef(); + } + _alloc = rhs._alloc; + // move + rhs._alloc = null; + return this; + } + + pure nothrow @safe @nogc + bool isNull(this _)() shared + { + return _alloc is null; + } + + @property uint alignment() shared + { + assert(_alloc); + return _alloc.alignment(); + } + + size_t goodAllocSize(size_t s) shared + { + assert(_alloc); + return _alloc.goodAllocSize(s); + } + + void[] allocate(size_t n, TypeInfo ti = null) shared + { + assert(_alloc); + return _alloc.allocate(n, ti); + } + + void[] alignedAllocate(size_t n, uint a) shared + { + assert(_alloc); + return _alloc.alignedAllocate(n, a); + } + + void[] allocateAll() shared + { + assert(_alloc); + return _alloc.allocateAll(); + } + + bool expand(ref void[] b, size_t size) shared + { + assert(_alloc); + return _alloc.expand(b, size); + } + + bool reallocate(ref void[] b, size_t size) shared + { + assert(_alloc); + return _alloc.reallocate(b, size); + } + + bool alignedReallocate(ref void[] b, size_t size, uint alignment) shared + { + assert(_alloc); + return _alloc.alignedReallocate(b, size, alignment); + } + + Ternary owns(void[] b) shared + { + assert(_alloc); + return _alloc.owns(b); + } + + Ternary resolveInternalPointer(const void* p, ref void[] result) shared + { + assert(_alloc); + return _alloc.resolveInternalPointer(p, result); + } + + bool deallocate(void[] b) shared + { + assert(_alloc); + return _alloc.deallocate(b); + } + + bool deallocateAll() shared + { + assert(_alloc); + return _alloc.deallocateAll(); + } + + Ternary empty() shared + { + assert(_alloc); + return _alloc.empty(); + } +} + +private shared RCISharedAllocator _processAllocator; +private RCIAllocator _threadAllocator; + +nothrow @nogc @safe +private ref RCIAllocator setupThreadAllocator() { /* Forwards the `_threadAllocator` calls to the `processAllocator` */ static class ThreadAllocator : IAllocator { + nothrow: override @property uint alignment() { return processAllocator.alignment(); @@ -533,12 +954,26 @@ private IAllocator setupThreadAllocator() nothrow @nogc @safe { return processAllocator.empty(); } + + //nothrow @safe @nogc + @safe @nogc + override void incRef() + { + processAllocator._alloc.incRef(); + } + + //nothrow @safe @nogc + @safe @nogc + override bool decRef() + { + return processAllocator._alloc.decRef(); + } } - assert(!_threadAllocator); + assert(_threadAllocator.isNull); import std.conv : emplace; static ulong[stateSize!(ThreadAllocator).divideRoundUp(ulong.sizeof)] _threadAllocatorState; - _threadAllocator = () @trusted { return emplace!(ThreadAllocator)(_threadAllocatorState[]); } (); + () @trusted { _threadAllocator = RCIAllocator(emplace!(ThreadAllocator)(_threadAllocatorState[])); }(); return _threadAllocator; } @@ -549,16 +984,18 @@ to be shared across threads, use $(D processAllocator) (below). By default, $(D theAllocator) ultimately fetches memory from $(D processAllocator), which in turn uses the garbage collected heap. */ -nothrow @safe @nogc @property IAllocator theAllocator() +nothrow @safe @nogc +@property ref RCIAllocator theAllocator() { - auto p = _threadAllocator; - return p !is null ? p : setupThreadAllocator(); + alias p = _threadAllocator; + return !p.isNull() ? p : setupThreadAllocator(); } /// Ditto -nothrow @safe @nogc @property void theAllocator(IAllocator a) +nothrow @system @nogc +@property void theAllocator(RCIAllocator a) { - assert(a); + assert(!a.isNull); _threadAllocator = a; } @@ -582,18 +1019,26 @@ Gets/sets the allocator for the current process. This allocator must be used for allocating memory shared across threads. Objects created using this allocator can be cast to $(D shared). */ -@property shared(ISharedAllocator) processAllocator() +@trusted nothrow @nogc +@property ref shared(RCISharedAllocator) processAllocator() { import std.experimental.allocator.gc_allocator : GCAllocator; import std.concurrency : initOnce; - return initOnce!_processAllocator( - sharedAllocatorObject(GCAllocator.instance)); + + static shared(RCISharedAllocator)* forceAttributes() + { + return &initOnce!_processAllocator( + sharedAllocatorObject(GCAllocator.instance)); + } + + return *(cast(shared(RCISharedAllocator)* function() nothrow @nogc)(&forceAttributes))(); } /// Ditto -@property void processAllocator(shared ISharedAllocator a) +nothrow @system @nogc +@property void processAllocator(shared RCISharedAllocator a) { - assert(a); + assert(!a.isNull); _processAllocator = a; } @@ -604,34 +1049,47 @@ allocator can be cast to $(D shared). import std.experimental.allocator.building_blocks.free_list : SharedFreeList; import std.experimental.allocator.mallocator : Mallocator; - assert(processAllocator); - assert(theAllocator); + assert(!processAllocator.isNull); + assert(!theAllocator.isNull); testAllocatorObject(processAllocator); testAllocatorObject(theAllocator); shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) sharedFL; - shared ISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); - assert(sharedFLObj); + shared RCISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); + alias SharedAllocT = CSharedAllocatorImpl!( + shared SharedFreeList!( + Mallocator, chooseAtRuntime, chooseAtRuntime)); + + assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 1); + assert(!sharedFLObj.isNull); testAllocatorObject(sharedFLObj); // Test processAllocator setter - shared ISharedAllocator oldProcessAllocator = processAllocator; + shared RCISharedAllocator oldProcessAllocator = processAllocator; processAllocator = sharedFLObj; - assert(processAllocator is sharedFLObj); + assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 2); + assert(processAllocator._alloc is sharedFLObj._alloc); testAllocatorObject(processAllocator); testAllocatorObject(theAllocator); - assertThrown!AssertError(processAllocator = null); + assertThrown!AssertError(processAllocator = RCISharedAllocator(null)); // Restore initial processAllocator state processAllocator = oldProcessAllocator; + assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 1); assert(processAllocator is oldProcessAllocator); - shared ISharedAllocator indirectShFLObj = sharedAllocatorObject(&sharedFL); + shared RCISharedAllocator indirectShFLObj = sharedAllocatorObject(&sharedFL); testAllocatorObject(indirectShFLObj); + alias IndirectSharedAllocT = CSharedAllocatorImpl!( + shared SharedFreeList!( + Mallocator, chooseAtRuntime, chooseAtRuntime) + , Yes.indirect); - IAllocator indirectMallocator = allocatorObject(&Mallocator.instance); + assert((cast(IndirectSharedAllocT)(indirectShFLObj._alloc)).rc == 1); + + RCIAllocator indirectMallocator = allocatorObject(&Mallocator.instance); testAllocatorObject(indirectMallocator); } @@ -2029,7 +2487,7 @@ own statically-typed allocator.) ) */ -CAllocatorImpl!A allocatorObject(A)(auto ref A a) +RCIAllocator allocatorObject(A)(auto ref A a) if (!isPointer!A) { import std.conv : emplace; @@ -2037,13 +2495,13 @@ if (!isPointer!A) { enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof); static __gshared ulong[s] state; - static __gshared CAllocatorImpl!A result; - if (!result) + static __gshared RCIAllocator result; + if (result.isNull) { // Don't care about a few races - result = emplace!(CAllocatorImpl!A)(state[]); + result = RCIAllocator(emplace!(CAllocatorImpl!A)(state[])); } - assert(result); + assert(!result.isNull); return result; } else @@ -2057,12 +2515,12 @@ if (!isPointer!A) } auto tmp = cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state); move(a, tmp.impl); - return tmp; + return RCIAllocator(tmp); } } /// Ditto -CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa) +RCIAllocator allocatorObject(A)(A* pa) { assert(pa); import std.conv : emplace; @@ -2072,15 +2530,16 @@ CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa) { scope(failure) pa.deallocate(state); } - return emplace!(CAllocatorImpl!(A, Yes.indirect)) - (state, pa); + return RCIAllocator(emplace!(CAllocatorImpl!(A, Yes.indirect)) + (state, pa)); } /// @system unittest { import std.experimental.allocator.mallocator : Mallocator; - IAllocator a = allocatorObject(Mallocator.instance); + + RCIAllocator a = allocatorObject(Mallocator.instance); auto b = a.allocate(100); assert(b.length == 100); assert(a.deallocate(b)); @@ -2109,9 +2568,11 @@ unittest // Ensure that the allocator was passed through in CAllocatorImpl // This allocator was used to allocate the chunk that holds the // CAllocatorImpl object; which is it's own wrapper - assert(_allocator.impl.bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc))); + assert((cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed + == stateSize!(CAllocatorImpl!(SCAlloc))); _allocator.allocate(1); - assert(_allocator.impl.bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)) + 1); + assert((cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed + == stateSize!(CAllocatorImpl!(SCAlloc)) + 1); } /** @@ -2134,22 +2595,33 @@ statically-typed allocator.) ) */ -shared(CSharedAllocatorImpl!A) sharedAllocatorObject(A)(auto ref A a) +//nothrow @safe +//nothrow @nogc @safe +nothrow +shared(RCISharedAllocator) sharedAllocatorObject(A)(auto ref A a) if (!isPointer!A) { import std.conv : emplace; static if (stateSize!A == 0) { enum s = stateSize!(CSharedAllocatorImpl!A).divideRoundUp(ulong.sizeof); - static __gshared ulong[s] state; - static shared CSharedAllocatorImpl!A result; - if (!result) + static shared ulong[s] state; + static shared RCISharedAllocator result; + if (result.isNull) { // Don't care about a few races - result = cast(shared - CSharedAllocatorImpl!A)(emplace!(CSharedAllocatorImpl!A)(state[])); + //auto tmp = emplace!(CSharedAllocatorImpl!A)( + //(() @trusted => cast(ulong[]) state[])()); + //result = RCISharedAllocator( + //(() @trusted => (cast(shared CSharedAllocatorImpl!A)( + //tmp) + //))()); + result = RCISharedAllocator( + (cast(shared CSharedAllocatorImpl!A)( + emplace!(CSharedAllocatorImpl!A)( + (() @trusted => cast(ulong[]) state[])())))); } - assert(result); + assert(!result.isNull); return result; } else static if (is(typeof({ shared A b = a; shared A c = b; }))) // copyable @@ -2163,7 +2635,7 @@ if (!isPointer!A) } auto tmp = emplace!(shared CSharedAllocatorImpl!A)(state); move(a, tmp.impl); - return tmp; + return RCISharedAllocator(tmp); } else // the allocator object is not copyable { @@ -2172,7 +2644,7 @@ if (!isPointer!A) } /// Ditto -shared(CSharedAllocatorImpl!(A, Yes.indirect)) sharedAllocatorObject(A)(A* pa) +shared(RCISharedAllocator) sharedAllocatorObject(A)(A* pa) { assert(pa); import std.conv : emplace; @@ -2182,7 +2654,7 @@ shared(CSharedAllocatorImpl!(A, Yes.indirect)) sharedAllocatorObject(A)(A* pa) { scope(failure) pa.deallocate(state); } - return emplace!(shared CSharedAllocatorImpl!(A, Yes.indirect))(state, pa); + return RCISharedAllocator(emplace!(shared CSharedAllocatorImpl!(A, Yes.indirect))(state, pa)); } @@ -2199,12 +2671,16 @@ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) { import std.traits : hasMember; + static if (stateSize!Allocator) private size_t rc = 1; + /** The implementation is available as a public member. */ static if (indirect) { + nothrow: private Allocator* pimpl; + @nogc ref Allocator impl() { return *pimpl; @@ -2220,6 +2696,7 @@ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) else alias impl = Allocator.instance; } +nothrow: /// Returns `impl.alignment`. override @property uint alignment() { @@ -2366,6 +2843,44 @@ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) return null; } } + + nothrow @safe @nogc + override void incRef() + { + static if (stateSize!Allocator) ++rc; + } + + nothrow @trusted @nogc + override bool decRef() + { + static if (stateSize!Allocator) + { + import core.stdc.string : memcpy; + + if (rc == 1) + { + static if (indirect) + { + Allocator* tmp = pimpl; + } + else + { + Allocator tmp; + memcpy(&tmp, &this.impl, Allocator.sizeof); + } + void[] support = (cast(void*) this)[0 .. stateSize!(typeof(this))]; + tmp.deallocate(support); + return false; + } + + --rc; + return true; + } + else + { + return true; + } + } } /** @@ -2381,13 +2896,18 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) : ISharedAllocator { import std.traits : hasMember; + import core.atomic : atomicOp, atomicLoad; + + static if (stateSize!Allocator) shared size_t rc = 1; /** The implementation is available as a public member. */ static if (indirect) { + nothrow: private shared Allocator* pimpl; + @nogc ref Allocator impl() shared { return *pimpl; @@ -2403,6 +2923,7 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) else alias impl = Allocator.instance; } +nothrow: /// Returns `impl.alignment`. override @property uint alignment() shared { @@ -2549,6 +3070,43 @@ class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) return null; } } + + nothrow @safe @nogc + override void incRef() shared + { + static if (stateSize!Allocator) atomicOp!"+="(rc, 1); + } + + nothrow @trusted @nogc + override bool decRef() shared + { + static if (stateSize!Allocator) + { + import core.stdc.string : memcpy; + + // rc starts as 1 to avoid comparing with size_t(0) - 1 + if (atomicOp!"-="(rc, 1) == 0) + { + static if (indirect) + { + Allocator* tmp = pimpl; + } + else + { + Allocator tmp; + memcpy(cast(void*) &tmp, cast(void*) &this.impl, Allocator.sizeof); + } + void[] support = (cast(void*) this)[0 .. stateSize!(typeof(this))]; + tmp.deallocate(support); + return false; + } + return true; + } + else + { + return true; + } + } }