Skip to content

Commit b03976f

Browse files
committed
Make AffixAllocator take into account RCISharedAllocator
1 parent 4549c4b commit b03976f

File tree

1 file changed

+138
-45
lines changed

1 file changed

+138
-45
lines changed

std/experimental/allocator/building_blocks/affix_allocator.d

Lines changed: 138 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ Suffixes are slower to get at because of alignment rounding, so prefixes should
1818
be preferred. However, small prefixes blunt the alignment so if a large
1919
alignment with a small affix is needed, suffixes should be chosen.
2020
21-
The following methods are defined if `Allocator` defines them, and forward to it: `deallocateAll`, `empty`, `owns`.
21+
The following methods are defined if `Allocator` defines them,
22+
and forwarded to it: `deallocateAll`, `empty`, `owns`.
2223
*/
2324
struct AffixAllocator(Allocator, Prefix, Suffix = void)
2425
{
2526
import std.algorithm.comparison : min;
2627
import std.conv : emplace;
27-
import std.experimental.allocator : RCIAllocator, theAllocator;
28+
import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
29+
theAllocator, processAllocator;
2830
import std.experimental.allocator.common : stateSize, forwardToMember,
2931
roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf,
3032
hasStaticallyKnownAlignment;
@@ -43,7 +45,8 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
4345
"This restriction could be relaxed in the future.");
4446

4547
/**
46-
If `Prefix` is `void`, the alignment is that of the parent. Otherwise, the alignment is the same as the `Prefix`'s alignment.
48+
If `Prefix` is `void`, the alignment is that of the parent.
49+
Otherwise, the alignment is the same as the `Prefix`'s alignment.
4750
*/
4851
static if (hasStaticallyKnownAlignment!Allocator)
4952
{
@@ -60,61 +63,96 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
6063
enum uint alignment = Prefix.alignof;
6164
}
6265

63-
/**
64-
If the parent allocator `Allocator` is stateful, an instance of it is
65-
stored as a member. Otherwise, `AffixAllocator` uses
66-
`Allocator.instance`. In either case, the name `_parent` is uniformly
67-
used for accessing the parent allocator.
68-
*/
69-
static if (stateSize!Allocator)
66+
private template Impl()
7067
{
71-
Allocator _parent;
72-
static if (is(Allocator == RCIAllocator))
68+
static if (stateSize!Allocator)
7369
{
74-
@nogc nothrow pure @safe
75-
Allocator parent()
70+
Allocator _parent;
71+
72+
static if (is(Allocator == RCIAllocator) || is(Allocator == RCISharedAllocator))
7673
{
77-
static @nogc nothrow
78-
RCIAllocator wrapAllocatorObject()
74+
static if (is(Allocator == RCISharedAllocator))
7975
{
80-
import std.experimental.allocator.gc_allocator : GCAllocator;
81-
import std.experimental.allocator : allocatorObject;
82-
83-
return allocatorObject(GCAllocator.instance);
76+
// synchronization variables
77+
// can't use Mutex or SpinLock because they aren't pure
78+
shared bool nullParent = true;
79+
shared bool nullParentWait = true;
8480
}
8581

86-
if (_parent.isNull)
82+
@nogc nothrow pure @safe
83+
Allocator parent()
8784
{
88-
// If the `_parent` allocator is `null` we will assign
89-
// an object that references the GC as the `parent`.
90-
auto fn = (() @trusted =>
91-
cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
92-
_parent = fn();
85+
static @nogc nothrow
86+
RCIAllocator wrapAllocatorObject()
87+
{
88+
import std.experimental.allocator.gc_allocator : GCAllocator;
89+
import std.experimental.allocator : allocatorObject;
90+
91+
return allocatorObject(GCAllocator.instance);
92+
}
93+
94+
static @nogc nothrow
95+
RCISharedAllocator wrapProcAllocatorObject()
96+
{
97+
import std.experimental.allocator.gc_allocator : GCAllocator;
98+
import std.experimental.allocator : sharedAllocatorObject;
99+
100+
return sharedAllocatorObject(GCAllocator.instance);
101+
}
102+
103+
if (_parent.isNull)
104+
{
105+
static if (is(Allocator == RCIAllocator))
106+
{
107+
// If the `_parent` allocator is `null` we will assign
108+
// an object that references the GC as the `parent`.
109+
auto fn = (() @trusted =>
110+
cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
111+
_parent = fn();
112+
}
113+
else
114+
{
115+
import core.atomic : cas, atomicLoad, atomicStore;
116+
117+
if ((() @trusted => cas(&nullParent, true, false))())
118+
{
119+
auto fn = (() @trusted =>
120+
cast(RCISharedAllocator function() @nogc nothrow pure @safe)
121+
(&wrapProcAllocatorObject))();
122+
_parent = fn();
123+
// Notify other threads that the parent has been set
124+
atomicStore(nullParentWait, false);
125+
}
126+
else
127+
{
128+
while(atomicLoad(nullParentWait))
129+
{
130+
// Busy-wait for the parent to be set
131+
}
132+
}
133+
}
134+
}
135+
136+
// `RCIAllocator.alignment` currently doesn't have any attributes
137+
// so we must cast; throughout the allocators module, `alignment`
138+
// is defined as an `enum` for the existing allocators.
139+
// `alignment` should always be `@nogc nothrow pure @safe`; once
140+
// this is enforced by the interface we can remove the cast
141+
auto pureAlign = (() @trusted =>
142+
cast(uint delegate() @nogc nothrow pure @safe)(&_parent.alignment))();
143+
assert(alignment <= pureAlign());
144+
return _parent;
93145
}
94-
95-
// `RCIAllocator.alignment` currently doesn't have any attributes
96-
// so we must cast; throughout the allocators module, `alignment`
97-
// is defined as an `enum` for the existing allocators.
98-
// `alignment` should always be `@nogc nothrow pure @safe`; once
99-
// this is enforced by the interface we can remove the cast
100-
auto pureAlign = (() @trusted =>
101-
cast(uint delegate() @nogc nothrow pure @safe)(&_parent.alignment))();
102-
assert(alignment <= pureAlign());
103-
return _parent;
146+
}
147+
else
148+
{
149+
alias parent = _parent;
104150
}
105151
}
106152
else
107153
{
108-
alias parent = _parent;
154+
alias parent = Allocator.instance;
109155
}
110-
}
111-
else
112-
{
113-
alias parent = Allocator.instance;
114-
}
115-
116-
private template Impl()
117-
{
118156

119157
size_t goodAllocSize(size_t s)
120158
{
@@ -323,6 +361,19 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
323361
*/
324362
static AffixAllocator instance;
325363

364+
/**
365+
If the parent allocator `Allocator` is stateful, an instance of it is
366+
stored as a member. If the parent allocator is null instance of
367+
$(REF RCIAllocator, std,experimental,allocator) or
368+
$(REF RCISharedAllocator, std,experimental,allocator) then `AffixAllocator`
369+
will use $(REF GCAllocator, std,experimental,allocator,gc_allocator).
370+
If the parent allocator `Allocator` is stateless, `AffixAllocator` uses
371+
`Allocator.instance`.
372+
In either case, the name `_parent` is uniformly used for accessing the
373+
parent allocator.
374+
*/
375+
Allocator parent();
376+
326377
/**
327378
Affix access functions offering references to the affixes of a
328379
block `b` previously allocated with this allocator. `b` may not be null.
@@ -545,3 +596,45 @@ struct AffixAllocator(Allocator, Prefix, Suffix = void)
545596
static assert(is(typeof(a.allocate) == shared));
546597
assert(buf.length == 10);
547598
}
599+
600+
@system unittest
601+
{
602+
import std.experimental.allocator.mallocator : Mallocator;
603+
import std.experimental.allocator.building_blocks.stats_collector;
604+
605+
alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
606+
alias AffixAl = AffixAllocator!(SCAlloc, uint);
607+
608+
AffixAl a;
609+
auto b = a.allocate(42);
610+
assert(b.length == 42);
611+
assert(a.parent.bytesUsed == 42 + uint.sizeof);
612+
assert((() nothrow @nogc => a.reallocate(b, 100))());
613+
assert(b.length == 100);
614+
assert(a.parent.bytesUsed == 100 + uint.sizeof);
615+
assert((() nothrow @nogc => a.deallocate(b))());
616+
}
617+
618+
@system unittest
619+
{
620+
import std.experimental.allocator : RCISharedAllocator;
621+
import core.thread : ThreadGroup;
622+
623+
shared AffixAllocator!(RCISharedAllocator, size_t) a;
624+
625+
void fun()
626+
{
627+
enum allocSize = 42;
628+
void[] b = a.allocate(allocSize);
629+
assert(b.length == allocSize);
630+
a.deallocate(b);
631+
}
632+
633+
enum numThreads = 10;
634+
auto tg = new ThreadGroup;
635+
foreach (i; 0 .. numThreads)
636+
{
637+
tg.create(&fun);
638+
}
639+
tg.joinAll();
640+
}

0 commit comments

Comments
 (0)