1 // Written in the D programming language.
2 /**
3 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/scoped_allocator.d)
4 */
5 module std.experimental.allocator.building_blocks.scoped_allocator;
6 
7 import std.experimental.allocator.common;
8 
9 /**
10 
11 `ScopedAllocator` delegates all allocation requests to `ParentAllocator`.
12 When destroyed, the `ScopedAllocator` object automatically calls $(D
13 deallocate) for all memory allocated through its lifetime. (The $(D
14 deallocateAll) function is also implemented with the same semantics.)
15 
16 `deallocate` is also supported, which is where most implementation effort
17 and overhead of `ScopedAllocator` go. If `deallocate` is not needed, a
18 simpler design combining `AllocatorList` with `Region` is recommended.
19 
20 */
21 struct ScopedAllocator(ParentAllocator)
22 {
23     static if (!stateSize!ParentAllocator)
24     {
25         // This test is available only for stateless allocators
26         version (StdUnittest)
27         @system unittest
28         {
29             testAllocator!(() => ScopedAllocator());
30         }
31     }
32 
33     import std.experimental.allocator.building_blocks.affix_allocator
34         : AffixAllocator;
35     import std.traits : hasMember;
36     import std.typecons : Ternary;
37 
38     private struct Node
39     {
40         Node* prev;
41         Node* next;
42         size_t length;
43     }
44 
45     alias Allocator = AffixAllocator!(ParentAllocator, Node);
46 
47     // state
48     /**
49     If `ParentAllocator` is stateful, `parent` is a property giving access
50     to an `AffixAllocator!ParentAllocator`. Otherwise, `parent` is an alias for `AffixAllocator!ParentAllocator.instance`.
51     */
52     static if (stateSize!ParentAllocator)
53     {
54         Allocator parent;
55     }
56     else
57     {
58         alias parent = Allocator.instance;
59     }
60     private Node* root;
61 
62     /**
63     `ScopedAllocator` is not copyable.
64     */
65     @disable this(this);
66 
67     /**
68     `ScopedAllocator`'s destructor releases all memory allocated during its
69     lifetime.
70     */
71     ~this()
72     {
73         deallocateAll;
74     }
75 
76     /// Alignment offered
77     enum alignment = Allocator.alignment;
78 
79     /**
80     Forwards to `parent.goodAllocSize` (which accounts for the management
81     overhead).
82     */
83     size_t goodAllocSize(size_t n)
84     {
85         return parent.goodAllocSize(n);
86     }
87 
88     // Common code shared between allocate and allocateZeroed.
89     private enum _processAndReturnAllocateResult =
90     q{
91        if (!b.ptr) return b;
92         Node* toInsert = & parent.prefix(b);
93         toInsert.prev = null;
94         toInsert.next = root;
95         toInsert.length = n;
96         assert(!root || !root.prev);
97         if (root) root.prev = toInsert;
98         root = toInsert;
99         return b;
100     };
101 
102     /**
103     Allocates memory. For management it actually allocates extra memory from
104     the parent.
105     */
106     void[] allocate(size_t n)
107     {
108         auto b = parent.allocate(n);
109         mixin(_processAndReturnAllocateResult);
110     }
111 
112     static if (hasMember!(Allocator, "allocateZeroed"))
113     package(std) void[] allocateZeroed()(size_t n)
114     {
115         auto b = parent.allocateZeroed(n);
116         mixin(_processAndReturnAllocateResult);
117     }
118 
119     /**
120     Forwards to $(D parent.expand(b, delta)).
121     */
122     static if (hasMember!(Allocator, "expand"))
123     bool expand(ref void[] b, size_t delta)
124     {
125         auto result = parent.expand(b, delta);
126         if (result && b)
127         {
128             () @trusted { parent.prefix(b).length = b.length; }();
129         }
130         return result;
131     }
132 
133     /**
134     Reallocates `b` to new size `s`.
135     */
136     bool reallocate(ref void[] b, size_t s)
137     {
138         // Remove from list
139         if (b.ptr)
140         {
141             Node* n = & parent.prefix(b);
142             if (n.prev) n.prev.next = n.next;
143             else root = n.next;
144             if (n.next) n.next.prev = n.prev;
145         }
146         auto result = parent.reallocate(b, s);
147         // Add back to list
148         if (b.ptr)
149         {
150             Node* n = & parent.prefix(b);
151             n.prev = null;
152             n.next = root;
153             n.length = s;
154             if (root) root.prev = n;
155             root = n;
156         }
157         return result;
158     }
159 
160     /**
161     Forwards to `parent.owns(b)`.
162     */
163     static if (hasMember!(Allocator, "owns"))
164     Ternary owns(void[] b)
165     {
166         return parent.owns(b);
167     }
168 
169     /**
170     Deallocates `b`.
171     */
172     static if (hasMember!(Allocator, "deallocate"))
173     bool deallocate(void[] b)
174     {
175         // Remove from list
176         if (b.ptr)
177         {
178             Node* n = & parent.prefix(b);
179             if (n.prev) n.prev.next = n.next;
180             else root = n.next;
181             if (n.next) n.next.prev = n.prev;
182         }
183         return parent.deallocate(b);
184     }
185 
186     /**
187     Deallocates all memory allocated.
188     */
189     bool deallocateAll()
190     {
191         bool result = true;
192         for (auto n = root; n; )
193         {
194             void* p = n + 1;
195             auto length = n.length;
196             n = n.next;
197             if (!parent.deallocate(p[0 .. length]))
198                 result = false;
199         }
200         root = null;
201         return result;
202     }
203 
204     /**
205     Returns `Ternary.yes` if this allocator is not responsible for any memory,
206     `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
207     */
208     pure nothrow @safe @nogc
209     Ternary empty() const
210     {
211         return Ternary(root is null);
212     }
213 }
214 
215 ///
216 @system unittest
217 {
218     import std.experimental.allocator.mallocator : Mallocator;
219     import std.typecons : Ternary;
220     ScopedAllocator!Mallocator alloc;
221     assert(alloc.empty == Ternary.yes);
222     const b = alloc.allocate(10);
223     assert(b.length == 10);
224     assert(alloc.empty == Ternary.no);
225 }
226 
227 version (StdUnittest)
228 @system unittest
229 {
230     import std.experimental.allocator.gc_allocator : GCAllocator;
231     testAllocator!(() => ScopedAllocator!GCAllocator());
232 }
233 
234 @system unittest // https://issues.dlang.org/show_bug.cgi?id=16046
235 {
236     import std.exception;
237     import std.experimental.allocator;
238     import std.experimental.allocator.mallocator;
239     ScopedAllocator!Mallocator alloc;
240     auto foo = alloc.make!int(1).enforce;
241     auto bar = alloc.make!int(2).enforce;
242     alloc.dispose(foo);
243     alloc.dispose(bar); // segfault here
244 }
245 
246 @system unittest
247 {
248     import std.experimental.allocator.gc_allocator : GCAllocator;
249     ScopedAllocator!GCAllocator a;
250 
251     assert(__traits(compiles, (() nothrow @safe @nogc => a.goodAllocSize(0))()));
252 
253     // Ensure deallocate inherits from parent allocators
254     auto b = a.allocate(42);
255     assert(b.length == 42);
256     () nothrow @nogc { a.deallocate(b); }();
257 }
258 
259 // Test that deallocateAll infers from parent
260 @system unittest
261 {
262     import std.experimental.allocator.building_blocks.region : BorrowedRegion;
263 
264     ScopedAllocator!(BorrowedRegion!()) a;
265     a.parent.parent = BorrowedRegion!()(new ubyte[1024 * 64]);
266     auto b = a.allocate(42);
267     assert(b.length == 42);
268     assert((() pure nothrow @safe @nogc => a.expand(b, 22))());
269     assert(b.length == 64);
270     assert((() nothrow @nogc => a.reallocate(b, 100))());
271     assert(b.length == 100);
272     assert((() nothrow @nogc => a.deallocateAll())());
273 }
274 
275 @system unittest
276 {
277     import std.experimental.allocator.building_blocks.region : Region;
278     import std.experimental.allocator.mallocator : Mallocator;
279     import std.typecons : Ternary;
280 
281     auto a = Region!(Mallocator)(1024 * 64);
282     auto b = a.allocate(42);
283     assert(b.length == 42);
284     assert((() pure nothrow @safe @nogc => a.expand(b, 22))());
285     assert(b.length == 64);
286     assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes);
287     assert((() nothrow @nogc => a.reallocate(b, 100))());
288     assert(b.length == 100);
289     assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes);
290     assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no);
291 }
292 
293 // Test empty
294 @system unittest
295 {
296     import std.experimental.allocator.mallocator : Mallocator;
297     import std.typecons : Ternary;
298     ScopedAllocator!Mallocator alloc;
299 
300     assert((() pure nothrow @safe @nogc => alloc.empty)() == Ternary.yes);
301     const b = alloc.allocate(10);
302     assert((() pure nothrow @safe @nogc => alloc.empty)() == Ternary.no);
303 }