@@ -18,13 +18,15 @@ Suffixes are slower to get at because of alignment rounding, so prefixes should
1818be preferred. However, small prefixes blunt the alignment so if a large
1919alignment 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 */
2324struct 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