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 }