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