1 ///
2 module std.experimental.allocator.building_blocks.scoped_allocator;
3 
4 import std.experimental.allocator.common;
5 
6 /**
7 
8 $(D ScopedAllocator) delegates all allocation requests to $(D ParentAllocator).
9 When destroyed, the $(D ScopedAllocator) object automatically calls $(D
10 deallocate) for all memory allocated through its lifetime. (The $(D
11 deallocateAll) function is also implemented with the same semantics.)
12 
13 $(D deallocate) is also supported, which is where most implementation effort
14 and overhead of $(D ScopedAllocator) go. If $(D deallocate) is not needed, a
15 simpler design combining $(D AllocatorList) with $(D Region) is recommended.
16 
17 */
ScopedAllocator(ParentAllocator)18 struct ScopedAllocator(ParentAllocator)
19 {
20     @system unittest
21     {
22         testAllocator!(() => ScopedAllocator());
23     }
24 
25     import std.experimental.allocator.building_blocks.affix_allocator
26         : AffixAllocator;
27     import std.traits : hasMember;
28     import std.typecons : Ternary;
29 
30     private struct Node
31     {
32         Node* prev;
33         Node* next;
34         size_t length;
35     }
36 
37     alias Allocator = AffixAllocator!(ParentAllocator, Node);
38 
39     // state
40     /**
41     If $(D ParentAllocator) is stateful, $(D parent) is a property giving access
42     to an $(D AffixAllocator!ParentAllocator). Otherwise, $(D parent) is an alias for `AffixAllocator!ParentAllocator.instance`.
43     */
44     static if (stateSize!ParentAllocator)
45     {
46         Allocator parent;
47     }
48     else
49     {
50         alias parent = Allocator.instance;
51     }
52     private Node* root;
53 
54     /**
55     $(D ScopedAllocator) is not copyable.
56     */
57     @disable this(this);
58 
59     /**
60     $(D ScopedAllocator)'s destructor releases all memory allocated during its
61     lifetime.
62     */
63     ~this()
64     {
65         deallocateAll;
66     }
67 
68     /// Alignment offered
69     enum alignment = Allocator.alignment;
70 
71     /**
72     Forwards to $(D parent.goodAllocSize) (which accounts for the management
73     overhead).
74     */
75     size_t goodAllocSize(size_t n)
76     {
77         return parent.goodAllocSize(n);
78     }
79 
80     /**
81     Allocates memory. For management it actually allocates extra memory from
82     the parent.
83     */
84     void[] allocate(size_t n)
85     {
86         auto b = parent.allocate(n);
87         if (!b.ptr) return b;
88         Node* toInsert = & parent.prefix(b);
89         toInsert.prev = null;
90         toInsert.next = root;
91         toInsert.length = n;
92         assert(!root || !root.prev);
93         if (root) root.prev = toInsert;
94         root = toInsert;
95         return b;
96     }
97 
98     /**
99     Forwards to $(D parent.expand(b, delta)).
100     */
101     static if (hasMember!(Allocator, "expand"))
102     bool expand(ref void[] b, size_t delta)
103     {
104         auto result = parent.expand(b, delta);
105         if (result && b.ptr)
106         {
107             parent.prefix(b).length = b.length;
108         }
109         return result;
110     }
111 
112     /**
113     Reallocates $(D b) to new size $(D s).
114     */
115     bool reallocate(ref void[] b, size_t s)
116     {
117         // Remove from list
118         if (b.ptr)
119         {
120             Node* n = & parent.prefix(b);
121             if (n.prev) n.prev.next = n.next;
122             else root = n.next;
123             if (n.next) n.next.prev = n.prev;
124         }
125         auto result = parent.reallocate(b, s);
126         // Add back to list
127         if (b.ptr)
128         {
129             Node* n = & parent.prefix(b);
130             n.prev = null;
131             n.next = root;
132             n.length = s;
133             if (root) root.prev = n;
134             root = n;
135         }
136         return result;
137     }
138 
139     /**
140     Forwards to $(D parent.owns(b)).
141     */
142     static if (hasMember!(Allocator, "owns"))
143     Ternary owns(void[] b)
144     {
145         return parent.owns(b);
146     }
147 
148     /**
149     Deallocates $(D b).
150     */
151     static if (hasMember!(Allocator, "deallocate"))
152     bool deallocate(void[] b)
153     {
154         // Remove from list
155         if (b.ptr)
156         {
157             Node* n = & parent.prefix(b);
158             if (n.prev) n.prev.next = n.next;
159             else root = n.next;
160             if (n.next) n.next.prev = n.prev;
161         }
162         return parent.deallocate(b);
163     }
164 
165     /**
166     Deallocates all memory allocated.
167     */
168     bool deallocateAll()
169     {
170         bool result = true;
171         for (auto n = root; n; )
172         {
173             void* p = n + 1;
174             auto length = n.length;
175             n = n.next;
176             if (!parent.deallocate(p[0 .. length]))
177                 result = false;
178         }
179         root = null;
180         return result;
181     }
182 
183     /**
184     Returns `Ternary.yes` if this allocator is not responsible for any memory,
185     `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
186     */
187     Ternary empty() const
188     {
189         return Ternary(root is null);
190     }
191 }
192 
193 ///
194 @system unittest
195 {
196     import std.experimental.allocator.mallocator : Mallocator;
197     import std.typecons : Ternary;
198     ScopedAllocator!Mallocator alloc;
199     assert(alloc.empty == Ternary.yes);
200     const b = alloc.allocate(10);
201     assert(b.length == 10);
202     assert(alloc.empty == Ternary.no);
203 }
204 
205 @system unittest
206 {
207     import std.experimental.allocator.gc_allocator : GCAllocator;
208     testAllocator!(() => ScopedAllocator!GCAllocator());
209 }
210 
211 @system unittest // https://issues.dlang.org/show_bug.cgi?id=16046
212 {
213     import std.exception;
214     import std.experimental.allocator;
215     import std.experimental.allocator.mallocator;
216     ScopedAllocator!Mallocator alloc;
217     auto foo = alloc.make!int(1).enforce;
218     auto bar = alloc.make!int(2).enforce;
219     alloc.dispose(foo);
220     alloc.dispose(bar); // segfault here
221 }
222