Merge pull request #5921 from edi33416/rc_iallocator

Replace IAllocator with reference counted struct
merged-on-behalf-of: Sebastian Wilzbach <sebi.wilzbach@gmail.com>
This commit is contained in:
The Dlang Bot 2018-01-26 19:53:03 +01:00 committed by GitHub
commit aa7b6c03ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 658 additions and 58 deletions

View file

@ -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));
------

View file

@ -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));
------

View file

@ -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;

View file

@ -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);

View file

@ -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, &reg);
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;
}
}
}