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