]>
Commit | Line | Data |
---|---|---|
5fee5ec3 IB |
1 | // Written in the D programming language. |
2 | /** | |
3 | Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/scoped_allocator.d) | |
4 | */ | |
b4c522fa IB |
5 | module std.experimental.allocator.building_blocks.scoped_allocator; |
6 | ||
7 | import std.experimental.allocator.common; | |
8 | ||
9 | /** | |
10 | ||
5fee5ec3 IB |
11 | `ScopedAllocator` delegates all allocation requests to `ParentAllocator`. |
12 | When destroyed, the `ScopedAllocator` object automatically calls $(D | |
b4c522fa IB |
13 | deallocate) for all memory allocated through its lifetime. (The $(D |
14 | deallocateAll) function is also implemented with the same semantics.) | |
15 | ||
5fee5ec3 IB |
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. | |
b4c522fa IB |
19 | |
20 | */ | |
21 | struct ScopedAllocator(ParentAllocator) | |
22 | { | |
5fee5ec3 | 23 | static if (!stateSize!ParentAllocator) |
b4c522fa | 24 | { |
5fee5ec3 IB |
25 | // This test is available only for stateless allocators |
26 | version (StdUnittest) | |
27 | @system unittest | |
28 | { | |
29 | testAllocator!(() => ScopedAllocator()); | |
30 | } | |
b4c522fa IB |
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 | /** | |
5fee5ec3 IB |
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`. | |
b4c522fa IB |
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 | /** | |
5fee5ec3 | 63 | `ScopedAllocator` is not copyable. |
b4c522fa IB |
64 | */ |
65 | @disable this(this); | |
66 | ||
67 | /** | |
5fee5ec3 | 68 | `ScopedAllocator`'s destructor releases all memory allocated during its |
b4c522fa IB |
69 | lifetime. |
70 | */ | |
71 | ~this() | |
72 | { | |
73 | deallocateAll; | |
74 | } | |
75 | ||
76 | /// Alignment offered | |
77 | enum alignment = Allocator.alignment; | |
78 | ||
79 | /** | |
5fee5ec3 | 80 | Forwards to `parent.goodAllocSize` (which accounts for the management |
b4c522fa IB |
81 | overhead). |
82 | */ | |
83 | size_t goodAllocSize(size_t n) | |
84 | { | |
85 | return parent.goodAllocSize(n); | |
86 | } | |
87 | ||
5fee5ec3 IB |
88 | // Common code shared between allocate and allocateZeroed. |
89 | private enum _processAndReturnAllocateResult = | |
90 | q{ | |
91 | if (!b.ptr) return b; | |
b4c522fa IB |
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; | |
5fee5ec3 IB |
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); | |
b4c522fa IB |
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); | |
5fee5ec3 | 126 | if (result && b) |
b4c522fa | 127 | { |
5fee5ec3 | 128 | () @trusted { parent.prefix(b).length = b.length; }(); |
b4c522fa IB |
129 | } |
130 | return result; | |
131 | } | |
132 | ||
133 | /** | |
5fee5ec3 | 134 | Reallocates `b` to new size `s`. |
b4c522fa IB |
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 | /** | |
5fee5ec3 | 161 | Forwards to `parent.owns(b)`. |
b4c522fa IB |
162 | */ |
163 | static if (hasMember!(Allocator, "owns")) | |
164 | Ternary owns(void[] b) | |
165 | { | |
166 | return parent.owns(b); | |
167 | } | |
168 | ||
169 | /** | |
5fee5ec3 | 170 | Deallocates `b`. |
b4c522fa IB |
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 | */ | |
5fee5ec3 | 208 | pure nothrow @safe @nogc |
b4c522fa IB |
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 | ||
5fee5ec3 | 227 | version (StdUnittest) |
b4c522fa IB |
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 | } | |
5fee5ec3 IB |
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 | { | |
c8dfa79c | 262 | import std.experimental.allocator.building_blocks.region : BorrowedRegion; |
5fee5ec3 | 263 | |
c8dfa79c IB |
264 | ScopedAllocator!(BorrowedRegion!()) a; |
265 | a.parent.parent = BorrowedRegion!()(new ubyte[1024 * 64]); | |
5fee5ec3 IB |
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 | } |