1 /// 2 module stdx.allocator.building_blocks.affix_allocator; 3 4 /** 5 6 Allocator that adds some extra data before (of type $(D Prefix)) and/or after 7 (of type $(D Suffix)) any allocation made with its parent allocator. This is 8 useful for uses where additional allocation-related information is needed, such 9 as mutexes, reference counts, or walls for debugging memory corruption errors. 10 11 If $(D Prefix) is not $(D void), $(D Allocator) must guarantee an alignment at 12 least as large as $(D Prefix.alignof). 13 14 Suffixes are slower to get at because of alignment rounding, so prefixes should 15 be preferred. However, small prefixes blunt the alignment so if a large 16 alignment with a small affix is needed, suffixes should be chosen. 17 18 The following methods are defined if $(D Allocator) defines them, and forward to it: $(D deallocateAll), $(D empty), $(D owns). 19 */ 20 struct AffixAllocator(Allocator, Prefix, Suffix = void) 21 { 22 import mir.utility : min; 23 import mir.conv : emplace; 24 import stdx.allocator : IAllocator, theAllocator; 25 import stdx.allocator.common : stateSize, forwardToMember, 26 roundUpToMultipleOf, alignedAt, alignDownTo, roundUpToMultipleOf, 27 hasStaticallyKnownAlignment; 28 import stdx.allocator.internal : isPowerOf2; 29 import stdx.allocator.internal : Ternary; 30 31 static if (hasStaticallyKnownAlignment!Allocator) 32 { 33 static assert( 34 !stateSize!Prefix || Allocator.alignment >= Prefix.alignof, 35 "AffixAllocator does not work with allocators offering a smaller" 36 ~ " alignment than the prefix alignment."); 37 } 38 static assert(alignment % Suffix.alignof == 0, 39 "This restriction could be relaxed in the future."); 40 41 /** 42 If $(D Prefix) is $(D void), the alignment is that of the parent. Otherwise, the alignment is the same as the $(D Prefix)'s alignment. 43 */ 44 static if (hasStaticallyKnownAlignment!Allocator) 45 { 46 enum uint alignment = isPowerOf2(stateSize!Prefix) 47 ? min(stateSize!Prefix, Allocator.alignment) 48 : (stateSize!Prefix ? Prefix.alignof : Allocator.alignment); 49 } 50 else static if (is(Prefix == void)) 51 { 52 enum uint alignment = platformAlignment; 53 } 54 else 55 { 56 enum uint alignment = Prefix.alignof; 57 } 58 59 /** 60 If the parent allocator $(D Allocator) is stateful, an instance of it is 61 stored as a member. Otherwise, $(D AffixAllocator) uses 62 `Allocator.instance`. In either case, the name $(D _parent) is uniformly 63 used for accessing the parent allocator. 64 */ 65 static if (stateSize!Allocator) 66 { 67 Allocator _parent; 68 static if (is(Allocator == IAllocator)) 69 { 70 Allocator parent() 71 { 72 if (_parent is null) _parent = theAllocator; 73 assert(alignment <= _parent.alignment); 74 return _parent; 75 } 76 } 77 else 78 { 79 alias parent = _parent; 80 } 81 } 82 else 83 { 84 alias parent = Allocator.instance; 85 } 86 87 private template Impl(bool isStatic) 88 { 89 90 size_t goodAllocSize(size_t s) 91 { 92 import stdx.allocator.common : goodAllocSize; 93 auto a = actualAllocationSize(s); 94 return roundUpToMultipleOf(parent.goodAllocSize(a) 95 - stateSize!Prefix - stateSize!Suffix, 96 this.alignment); 97 } 98 99 static if (isStatic) 100 private size_t actualAllocationSize(size_t s) 101 { 102 assert(s > 0); 103 static if (!stateSize!Suffix) 104 { 105 return s + stateSize!Prefix; 106 } 107 else 108 { 109 return 110 roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof) 111 + stateSize!Suffix; 112 } 113 } 114 else 115 private size_t actualAllocationSize(size_t s) const 116 { 117 assert(s > 0); 118 static if (!stateSize!Suffix) 119 { 120 return s + stateSize!Prefix; 121 } 122 else 123 { 124 return 125 roundUpToMultipleOf(s + stateSize!Prefix, Suffix.alignof) 126 + stateSize!Suffix; 127 } 128 } 129 130 static if (isStatic) 131 private void[] actualAllocation(void[] b) 132 { 133 assert(b !is null); 134 return (b.ptr - stateSize!Prefix) 135 [0 .. actualAllocationSize(b.length)]; 136 } 137 else 138 private void[] actualAllocation(void[] b) const 139 { 140 assert(b !is null); 141 return (b.ptr - stateSize!Prefix) 142 [0 .. actualAllocationSize(b.length)]; 143 } 144 145 void[] allocate(size_t bytes) 146 { 147 if (!bytes) return null; 148 auto result = parent.allocate(actualAllocationSize(bytes)); 149 if (result is null) return null; 150 static if (stateSize!Prefix) 151 { 152 assert(result.ptr.alignedAt(Prefix.alignof)); 153 emplace!Prefix(cast(Prefix*) result.ptr); 154 } 155 static if (stateSize!Suffix) 156 { 157 auto suffixP = result.ptr + result.length - Suffix.sizeof; 158 assert(suffixP.alignedAt(Suffix.alignof)); 159 emplace!Suffix(cast(Suffix*)(suffixP)); 160 } 161 return result[stateSize!Prefix .. stateSize!Prefix + bytes]; 162 } 163 164 static if (__traits(hasMember, Allocator, "allocateAll")) 165 void[] allocateAll() 166 { 167 auto result = parent.allocateAll(); 168 if (result is null) return null; 169 if (result.length < actualAllocationSize(1)) 170 { 171 deallocate(result); 172 return null; 173 } 174 static if (stateSize!Prefix) 175 { 176 assert(result.length > stateSize!Prefix); 177 emplace!Prefix(cast(Prefix*) result.ptr); 178 result = result[stateSize!Prefix .. $]; 179 } 180 static if (stateSize!Suffix) 181 { 182 assert(result.length > stateSize!Suffix); 183 // Ehm, find a properly aligned place for the suffix 184 auto p = (result.ptr + result.length - stateSize!Suffix) 185 .alignDownTo(Suffix.alignof); 186 assert(p > result.ptr); 187 emplace!Suffix(cast(Suffix*) p); 188 result = result[0 .. p - result.ptr]; 189 } 190 return result; 191 } 192 193 static if (__traits(hasMember, Allocator, "owns")) 194 Ternary owns(void[] b) 195 { 196 if (b is null) return Ternary.no; 197 return parent.owns(actualAllocation(b)); 198 } 199 200 static if (__traits(hasMember, Allocator, "resolveInternalPointer")) 201 Ternary resolveInternalPointer(const void* p, ref void[] result) 202 { 203 void[] p1; 204 Ternary r = parent.resolveInternalPointer(p, p1); 205 if (r != Ternary.yes || p1 is null) 206 return r; 207 p1 = p1[stateSize!Prefix .. $]; 208 auto p2 = (p1.ptr + p1.length - stateSize!Suffix) 209 .alignDownTo(Suffix.alignof); 210 result = p1[0 .. p2 - p1.ptr]; 211 return Ternary.yes; 212 } 213 214 static if (!stateSize!Suffix && __traits(hasMember, Allocator, "expand")) 215 bool expand(ref void[] b, size_t delta) 216 { 217 if (!b.ptr) return delta == 0; 218 auto t = actualAllocation(b); 219 const result = parent.expand(t, delta); 220 if (!result) return false; 221 b = b.ptr[0 .. b.length + delta]; 222 return true; 223 } 224 225 static if (__traits(hasMember, Allocator, "reallocate")) 226 bool reallocate(ref void[] b, size_t s) 227 { 228 if (b is null) 229 { 230 b = allocate(s); 231 return b.length == s; 232 } 233 auto t = actualAllocation(b); 234 const result = parent.reallocate(t, actualAllocationSize(s)); 235 if (!result) return false; // no harm done 236 b = t.ptr[stateSize!Prefix .. stateSize!Prefix + s]; 237 return true; 238 } 239 240 static if (__traits(hasMember, Allocator, "deallocate")) 241 bool deallocate(void[] b) 242 { 243 if (!b.ptr) return true; 244 return parent.deallocate(actualAllocation(b)); 245 } 246 247 /* The following methods are defined if $(D ParentAllocator) defines 248 them, and forward to it: $(D deallocateAll), $(D empty).*/ 249 mixin(forwardToMember("parent", 250 "deallocateAll", "empty")); 251 252 // Computes suffix type given buffer type 253 private template Payload2Affix(Payload, Affix) 254 { 255 static if (is(Payload[] : void[])) 256 alias Payload2Affix = Affix; 257 else static if (is(Payload[] : shared(void)[])) 258 alias Payload2Affix = shared Affix; 259 else static if (is(Payload[] : immutable(void)[])) 260 alias Payload2Affix = shared Affix; 261 else static if (is(Payload[] : const(shared(void))[])) 262 alias Payload2Affix = shared Affix; 263 else static if (is(Payload[] : const(void)[])) 264 alias Payload2Affix = const Affix; 265 else 266 static assert(0, "Internal error for type " ~ Payload.stringof); 267 } 268 269 // Extra functions 270 static if (stateSize!Prefix) 271 { 272 static auto ref prefix(T)(T[] b) 273 { 274 assert(b.ptr && b.ptr.alignedAt(Prefix.alignof)); 275 return (cast(Payload2Affix!(T, Prefix)*) b.ptr)[-1]; 276 } 277 } 278 static if (stateSize!Suffix) 279 auto ref suffix(T)(T[] b) 280 { 281 assert(b.ptr); 282 auto p = b.ptr - stateSize!Prefix 283 + actualAllocationSize(b.length); 284 assert(p && p.alignedAt(Suffix.alignof)); 285 return (cast(Payload2Affix!(T, Suffix)*) p)[-1]; 286 } 287 } 288 289 version (StdDdoc) 290 { 291 /** 292 Standard allocator methods. Each is defined if and only if the parent 293 allocator defines the homonym method (except for $(D goodAllocSize), 294 which may use the global default). Also, the methods will be $(D 295 shared) if the parent allocator defines them as such. 296 */ 297 size_t goodAllocSize(size_t); 298 /// Ditto 299 void[] allocate(size_t); 300 /// Ditto 301 Ternary owns(void[]); 302 /// Ditto 303 bool expand(ref void[] b, size_t delta); 304 /// Ditto 305 bool reallocate(ref void[] b, size_t s); 306 /// Ditto 307 bool deallocate(void[] b); 308 /// Ditto 309 bool deallocateAll(); 310 /// Ditto 311 Ternary empty(); 312 313 /** 314 The `instance` singleton is defined if and only if the parent allocator 315 has no state and defines its own `it` object. 316 */ 317 static AffixAllocator instance; 318 319 /** 320 Affix access functions offering references to the affixes of a 321 block `b` previously allocated with this allocator. `b` may not be null. 322 They are defined if and only if the corresponding affix is not `void`. 323 324 The qualifiers of the affix are not always the same as the qualifiers 325 of the argument. This is because the affixes are not part of the data 326 itself, but instead are just $(I associated) with the data and known 327 to the allocator. The table below documents the type of `preffix(b)` and 328 `affix(b)` depending on the type of `b`. 329 330 $(BOOKTABLE Result of `prefix`/`suffix` depending on argument (`U` is 331 any unqualified type, `Affix` is `Prefix` or `Suffix`), 332 $(TR $(TH Argument$(NBSP)Type) $(TH Return) $(TH Comments)) 333 334 $(TR $(TD `shared(U)[]`) $(TD `ref shared Affix`) 335 $(TD Data is shared across threads and the affix follows suit.)) 336 337 $(TR $(TD `immutable(U)[]`) $(TD `ref shared Affix`) 338 $(TD Although the data is immutable, the allocator "knows" the 339 underlying memory is mutable, so `immutable` is elided for the affix 340 which is independent from the data itself. However, the result is 341 `shared` because `immutable` is implicitly shareable so multiple 342 threads may access and manipulate the affix for the same data.)) 343 344 $(TR $(TD `const(shared(U))[]`) $(TD `ref shared Affix`) 345 $(TD The data is always shareable across threads. Even if the data 346 is `const`, the affix is modifiable by the same reasoning as for 347 `immutable`.)) 348 349 $(TR $(TD `const(U)[]`) $(TD `ref const Affix`) 350 $(TD The input may have originated from `U[]` or `immutable(U)[]`, 351 so it may be actually shared or not. Returning an unqualified affix 352 may result in race conditions, whereas returning a `shared` affix 353 may result in inadvertent sharing of mutable thread-local data 354 across multiple threads. So the returned type is conservatively 355 `ref const`.)) 356 357 $(TR $(TD `U[]`) $(TD `ref Affix`) 358 $(TD Unqualified data has unqualified affixes.)) 359 ) 360 361 Precondition: `b !is null` and `b` must have been allocated with 362 this allocator. 363 */ 364 static ref auto prefix(T)(T[] b); 365 /// Ditto 366 ref auto suffix(T)(T[] b); 367 } 368 else static if (is(typeof(Allocator.instance) == shared)) 369 { // for backward compatability 370 enum shared AffixAllocator instance = AffixAllocator(); 371 static { mixin Impl!true; } 372 } 373 else 374 { 375 static if (stateSize!Allocator == 0) 376 { 377 enum AffixAllocator instance = AffixAllocator(); 378 static { mixin Impl!true; } 379 } 380 else 381 { 382 mixin Impl!false; 383 } 384 } 385 } 386 387 /// 388 @system unittest 389 { 390 import stdx.allocator.mallocator : Mallocator; 391 // One word before and after each allocation. 392 alias A = AffixAllocator!(Mallocator, size_t, size_t); 393 auto b = A.instance.allocate(11); 394 A.instance.prefix(b) = 0xCAFE_BABE; 395 A.instance.suffix(b) = 0xDEAD_BEEF; 396 assert(A.instance.prefix(b) == 0xCAFE_BABE 397 && A.instance.suffix(b) == 0xDEAD_BEEF); 398 } 399 400 @system unittest 401 { 402 import stdx.allocator.gc_allocator : GCAllocator; 403 import stdx.allocator : theAllocator, IAllocator; 404 405 // One word before and after each allocation. 406 auto A = AffixAllocator!(IAllocator, size_t, size_t)(theAllocator); 407 auto a = A.allocate(11); 408 A.prefix(a) = 0xCAFE_BABE; 409 A.suffix(a) = 0xDEAD_BEEF; 410 assert(A.prefix(a) == 0xCAFE_BABE 411 && A.suffix(a) == 0xDEAD_BEEF); 412 413 // One word before and after each allocation. 414 auto B = AffixAllocator!(IAllocator, size_t, size_t)(); 415 auto b = B.allocate(11); 416 B.prefix(b) = 0xCAFE_BABE; 417 B.suffix(b) = 0xDEAD_BEEF; 418 assert(B.prefix(b) == 0xCAFE_BABE 419 && B.suffix(b) == 0xDEAD_BEEF); 420 } 421 422 @system unittest 423 { 424 import stdx.allocator.building_blocks.bitmapped_block 425 : BitmappedBlock; 426 import stdx.allocator.common : testAllocator; 427 testAllocator!({ 428 auto a = AffixAllocator!(BitmappedBlock!128, ulong, ulong) 429 (BitmappedBlock!128(new ubyte[128 * 4096])); 430 return a; 431 }); 432 } 433 434 @system unittest 435 { 436 import stdx.allocator.mallocator : Mallocator; 437 alias A = AffixAllocator!(Mallocator, size_t); 438 auto b = A.instance.allocate(10); 439 A.instance.prefix(b) = 10; 440 assert(A.instance.prefix(b) == 10); 441 442 import stdx.allocator.building_blocks.null_allocator 443 : NullAllocator; 444 alias B = AffixAllocator!(NullAllocator, size_t); 445 b = B.instance.allocate(100); 446 assert(b is null); 447 } 448 449 @system unittest 450 { 451 import stdx.allocator; 452 import stdx.allocator.gc_allocator; 453 import stdx.allocator.internal : Ternary; 454 alias MyAllocator = AffixAllocator!(GCAllocator, uint); 455 auto a = MyAllocator.instance.makeArray!(shared int)(100); 456 static assert(is(typeof(&MyAllocator.instance.prefix(a)) == shared(uint)*)); 457 auto b = MyAllocator.instance.makeArray!(shared const int)(100); 458 static assert(is(typeof(&MyAllocator.instance.prefix(b)) == shared(uint)*)); 459 auto c = MyAllocator.instance.makeArray!(immutable int)(100); 460 static assert(is(typeof(&MyAllocator.instance.prefix(c)) == shared(uint)*)); 461 auto d = MyAllocator.instance.makeArray!(int)(100); 462 static assert(is(typeof(&MyAllocator.instance.prefix(d)) == uint*)); 463 auto e = MyAllocator.instance.makeArray!(const int)(100); 464 static assert(is(typeof(&MyAllocator.instance.prefix(e)) == const(uint)*)); 465 466 void[] p; 467 assert(MyAllocator.instance.resolveInternalPointer(null, p) == Ternary.no); 468 Ternary r = MyAllocator.instance.resolveInternalPointer(d.ptr, p); 469 assert(p.ptr is d.ptr && p.length >= d.length); 470 }