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