Skip to content

Commit

Permalink
Make AffixAllocator take into account RCISharedAllocator
Browse files Browse the repository at this point in the history
  • Loading branch information
edi33416 committed Apr 25, 2018
1 parent 4549c4b commit 25c65ab
Showing 1 changed file with 138 additions and 45 deletions.
183 changes: 138 additions & 45 deletions std/experimental/allocator/building_blocks/affix_allocator.d
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ Suffixes are slower to get at because of alignment rounding, so prefixes should
be preferred. However, small prefixes blunt the alignment so if a large
alignment with a small affix is needed, suffixes should be chosen.
The following methods are defined if `Allocator` defines them, and forward to it: `deallocateAll`, `empty`, `owns`.
The following methods are defined if `Allocator` defines them,
and forwarded to it: `deallocateAll`, `empty`, `owns`.
*/
struct AffixAllocator(Allocator, Prefix, Suffix = void)
{
import std.algorithm.comparison : min;
import std.conv : emplace;
import std.experimental.allocator : RCIAllocator, theAllocator;
import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
theAllocator, processAllocator;
import std.experimental.allocator.common : stateSize, forwardToMember,
roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf,
hasStaticallyKnownAlignment;
Expand All @@ -43,7 +45,8 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
"This restriction could be relaxed in the future.");

/**
If `Prefix` is `void`, the alignment is that of the parent. Otherwise, the alignment is the same as the `Prefix`'s alignment.
If `Prefix` is `void`, the alignment is that of the parent.
Otherwise, the alignment is the same as the `Prefix`'s alignment.
*/
static if (hasStaticallyKnownAlignment!Allocator)
{
Expand All @@ -60,61 +63,96 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
enum uint alignment = Prefix.alignof;
}

/**
If the parent allocator `Allocator` is stateful, an instance of it is
stored as a member. Otherwise, `AffixAllocator` uses
`Allocator.instance`. In either case, the name `_parent` is uniformly
used for accessing the parent allocator.
*/
static if (stateSize!Allocator)
private template Impl()
{
Allocator _parent;
static if (is(Allocator == RCIAllocator))
static if (stateSize!Allocator)
{
@nogc nothrow pure @safe
Allocator parent()
Allocator _parent;

static if (is(Allocator == RCIAllocator) || is(Allocator == RCISharedAllocator))
{
static @nogc nothrow
RCIAllocator wrapAllocatorObject()
static if (is(Allocator == RCISharedAllocator))
{
import std.experimental.allocator.gc_allocator : GCAllocator;
import std.experimental.allocator : allocatorObject;

return allocatorObject(GCAllocator.instance);
// synchronization variables
// can't use Mutex or SpinLock because they aren't pure
shared bool nullParent = true;
shared bool nullParentWait = true;
}

if (_parent.isNull)
@nogc nothrow pure @safe
Allocator parent()
{
// If the `_parent` allocator is `null` we will assign
// an object that references the GC as the `parent`.
auto fn = (() @trusted =>
cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
_parent = fn();
static @nogc nothrow
RCIAllocator wrapAllocatorObject()
{
import std.experimental.allocator.gc_allocator : GCAllocator;
import std.experimental.allocator : allocatorObject;

return allocatorObject(GCAllocator.instance);
}

static @nogc nothrow
RCISharedAllocator wrapProcAllocatorObject()
{
import std.experimental.allocator.gc_allocator : GCAllocator;
import std.experimental.allocator : sharedAllocatorObject;

return sharedAllocatorObject(GCAllocator.instance);
}

if (_parent.isNull)
{
static if (is(Allocator == RCIAllocator))
{
// If the `_parent` allocator is `null` we will assign
// an object that references the GC as the `parent`.
auto fn = (() @trusted =>
cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
_parent = fn();
}
else
{
import core.atomic : cas, atomicLoad, atomicStore;

if ((() @trusted => cas(&nullParent, true, false))())
{
auto fn = (() @trusted =>
cast(RCISharedAllocator function() @nogc nothrow pure @safe)
(&wrapProcAllocatorObject))();
_parent = fn();
// Notify other threads that the parent has been set
atomicStore(nullParentWait, false);
}
else
{
while (atomicLoad(nullParentWait))
{
// Busy-wait for the parent to be set
}
}
}
}

// `RCIAllocator.alignment` currently doesn't have any attributes
// so we must cast; throughout the allocators module, `alignment`
// is defined as an `enum` for the existing allocators.
// `alignment` should always be `@nogc nothrow pure @safe`; once
// this is enforced by the interface we can remove the cast
auto pureAlign = (() @trusted =>
cast(uint delegate() @nogc nothrow pure @safe)(&_parent.alignment))();
assert(alignment <= pureAlign());
return _parent;
}

// `RCIAllocator.alignment` currently doesn't have any attributes
// so we must cast; throughout the allocators module, `alignment`
// is defined as an `enum` for the existing allocators.
// `alignment` should always be `@nogc nothrow pure @safe`; once
// this is enforced by the interface we can remove the cast
auto pureAlign = (() @trusted =>
cast(uint delegate() @nogc nothrow pure @safe)(&_parent.alignment))();
assert(alignment <= pureAlign());
return _parent;
}
else
{
alias parent = _parent;
}
}
else
{
alias parent = _parent;
alias parent = Allocator.instance;
}
}
else
{
alias parent = Allocator.instance;
}

private template Impl()
{

size_t goodAllocSize(size_t s)
{
Expand Down Expand Up @@ -323,6 +361,19 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
*/
static AffixAllocator instance;

/**
If the parent allocator `Allocator` is stateful, an instance of it is
stored as a member. If the parent allocator is null instance of
$(REF RCIAllocator, std,experimental,allocator) or
$(REF RCISharedAllocator, std,experimental,allocator) then `AffixAllocator`
will use $(REF GCAllocator, std,experimental,allocator,gc_allocator).
If the parent allocator `Allocator` is stateless, `AffixAllocator` uses
`Allocator.instance`.
In either case, the name `_parent` is uniformly used for accessing the
parent allocator.
*/
Allocator parent();

/**
Affix access functions offering references to the affixes of a
block `b` previously allocated with this allocator. `b` may not be null.
Expand Down Expand Up @@ -545,3 +596,45 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
static assert(is(typeof(a.allocate) == shared));
assert(buf.length == 10);
}

@system unittest
{
import std.experimental.allocator.mallocator : Mallocator;
import std.experimental.allocator.building_blocks.stats_collector;

alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
alias AffixAl = AffixAllocator!(SCAlloc, uint);

AffixAl a;
auto b = a.allocate(42);
assert(b.length == 42);
assert(a.parent.bytesUsed == 42 + uint.sizeof);
assert((() nothrow @nogc => a.reallocate(b, 100))());
assert(b.length == 100);
assert(a.parent.bytesUsed == 100 + uint.sizeof);
assert((() nothrow @nogc => a.deallocate(b))());
}

@system unittest
{
import std.experimental.allocator : RCISharedAllocator;
import core.thread : ThreadGroup;

shared AffixAllocator!(RCISharedAllocator, size_t) a;

void fun()
{
enum allocSize = 42;
void[] b = a.allocate(allocSize);
assert(b.length == allocSize);
a.deallocate(b);
}

enum numThreads = 10;
auto tg = new ThreadGroup;
foreach (i; 0 .. numThreads)
{
tg.create(&fun);
}
tg.joinAll();
}

0 comments on commit 25c65ab

Please sign in to comment.