1 // Written in the D programming language. 2 /** 3 This module defines `TypedAllocator`, a statically-typed allocator that 4 aggregates multiple untyped allocators and uses them depending on the static 5 properties of the types allocated. For example, distinct allocators may be used 6 for thread-local vs. thread-shared data, or for fixed-size data (`struct`, 7 `class` objects) vs. resizable data (arrays). 8 9 Source: $(PHOBOSSRC std/experimental/allocator/typed.d) 10 11 Macros: 12 T2=$(TR <td style="text-align:left">`$1`</td> $(TD $(ARGS $+))) 13 */ 14 15 module std.experimental.allocator.typed; 16 17 import std.experimental.allocator; 18 import std.experimental.allocator.common; 19 import std.range : isInputRange, isForwardRange, walkLength, save, empty, 20 front, popFront; 21 import std.traits : isPointer, hasElaborateDestructor; 22 import std.typecons : Flag, Yes, No; 23 24 /** 25 Allocation-related flags dictated by type characteristics. `TypedAllocator` 26 deduces these flags from the type being allocated and uses the appropriate 27 allocator accordingly. 28 */ 29 enum AllocFlag : uint 30 { 31 _init = 0, 32 /** 33 Fixed-size allocation (unlikely to get reallocated later). Examples: `int`, 34 `double`, any `struct` or `class` type. By default it is assumed that the 35 allocation is variable-size, i.e. susceptible to later reallocation 36 (for example all array types). This flag is advisory, i.e. in-place resizing 37 may be attempted for `fixedSize` allocations and may succeed. The flag is 38 just a hint to the compiler it may use allocation strategies that work well 39 with objects of fixed size. 40 */ 41 fixedSize = 1, 42 /** 43 The type being allocated embeds no pointers. Examples: `int`, `int[]`, $(D 44 Tuple!(int, float)). The implicit conservative assumption is that the type 45 has members with indirections so it needs to be scanned if garbage 46 collected. Example of types with pointers: `int*[]`, $(D Tuple!(int, 47 string)). 48 */ 49 hasNoIndirections = 4, 50 /** 51 By default it is conservatively assumed that allocated memory may be `cast` 52 to `shared`, passed across threads, and deallocated in a different thread 53 than the one that allocated it. If that's not the case, there are two 54 options. First, `immutableShared` means the memory is allocated for 55 `immutable` data and will be deallocated in the same thread it was 56 allocated in. Second, `threadLocal` means the memory is not to be shared 57 across threads at all. The two flags cannot be simultaneously present. 58 */ 59 immutableShared = 8, 60 /// ditto 61 threadLocal = 16, 62 } 63 64 /** 65 `TypedAllocator` acts like a chassis on which several specialized allocators 66 can be assembled. To let the system make a choice about a particular kind of 67 allocation, use `Default` for the respective parameters. 68 69 There is a hierarchy of allocation kinds. When an allocator is implemented for 70 a given combination of flags, it is used. Otherwise, the next down the list is 71 chosen. 72 73 $(BOOKTABLE , 74 75 $(TR $(TH `AllocFlag` combination) $(TH Description)) 76 77 $(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections 78 |$(NBSP)AllocFlag.fixedSize, 79 This is the most specific allocation policy: the memory being allocated is 80 thread local, has no indirections at all, and will not be reallocated. Examples 81 of types fitting this description: `int`, `double`, $(D Tuple!(int, long)), but 82 not $(D Tuple!(int, string)), which contains an indirection.) 83 84 $(T2 AllocFlag.threadLocal |$(NBSP)AllocFlag.hasNoIndirections, 85 As above, but may be reallocated later. Examples of types fitting this 86 description are `int[]`, `double[]`, $(D Tuple!(int, long)[]), but not 87 $(D Tuple!(int, string)[]), which contains an indirection.) 88 89 $(T2 AllocFlag.threadLocal, 90 As above, but may embed indirections. Examples of types fitting this 91 description are `int*[]`, `Object[]`, $(D Tuple!(int, string)[]).) 92 93 $(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections 94 |$(NBSP)AllocFlag.fixedSize, 95 The type being allocated is `immutable` and has no pointers. The thread that 96 allocated it must also deallocate it. Example: `immutable(int)`.) 97 98 $(T2 AllocFlag.immutableShared |$(NBSP)AllocFlag.hasNoIndirections, 99 As above, but the type may be appended to in the future. Example: `string`.) 100 101 $(T2 AllocFlag.immutableShared, 102 As above, but the type may embed references. Example: `immutable(Object)[]`.) 103 104 $(T2 AllocFlag.hasNoIndirections |$(NBSP)AllocFlag.fixedSize, 105 The type being allocated may be shared across threads, embeds no indirections, 106 and has fixed size.) 107 108 $(T2 AllocFlag.hasNoIndirections, 109 The type being allocated may be shared across threads, may embed indirections, 110 and has variable size.) 111 112 $(T2 AllocFlag.fixedSize, 113 The type being allocated may be shared across threads, may embed indirections, 114 and has fixed size.) 115 116 $(T2 0, The most conservative/general allocation: memory may be shared, 117 deallocated in a different thread, may or may not be resized, and may embed 118 references.) 119 ) 120 121 Params: 122 PrimaryAllocator = The default allocator. 123 Policies = Zero or more pairs consisting of an `AllocFlag` and an allocator 124 type. 125 */ 126 struct TypedAllocator(PrimaryAllocator, Policies...) 127 { 128 import std.algorithm.sorting : isSorted; 129 import std.meta : AliasSeq; 130 import std.typecons : Tuple; 131 132 static assert(Policies.length == 0 || isSorted([Stride2!Policies])); 133 134 private template Stride2(T...) 135 { 136 static if (T.length >= 2) 137 { 138 alias Stride2 = AliasSeq!(T[0], Stride2!(T[2 .. $])); 139 } 140 else 141 { 142 alias Stride2 = AliasSeq!(T[0 .. $]); 143 } 144 } 145 146 // state 147 static if (stateSize!PrimaryAllocator) private PrimaryAllocator primary; 148 else alias primary = PrimaryAllocator.instance; 149 static if (Policies.length > 0) 150 private Tuple!(Stride2!(Policies[1 .. $])) extras; 151 152 private static bool match(uint have, uint want) 153 { 154 enum uint maskAway = 155 ~(AllocFlag.immutableShared | AllocFlag.threadLocal); 156 // Do we offer thread local? 157 if (have & AllocFlag.threadLocal) 158 { 159 if (want & AllocFlag.threadLocal) 160 return match(have & maskAway, want & maskAway); 161 return false; 162 } 163 if (have & AllocFlag.immutableShared) 164 { 165 // Okay to ask for either thread local or immutable shared 166 if (want & (AllocFlag.threadLocal 167 | AllocFlag.immutableShared)) 168 return match(have & maskAway, want & maskAway); 169 return false; 170 } 171 // From here on we have full-blown thread sharing. 172 if (have & AllocFlag.hasNoIndirections) 173 { 174 if (want & AllocFlag.hasNoIndirections) 175 return match(have & ~AllocFlag.hasNoIndirections, 176 want & ~AllocFlag.hasNoIndirections); 177 return false; 178 } 179 // Fixed size or variable size both match. 180 return true; 181 } 182 183 /** 184 Given `flags` as a combination of `AllocFlag` values, or a type `T`, returns 185 the allocator that's a closest fit in capabilities. 186 */ 187 auto ref allocatorFor(uint flags)() 188 { 189 static if (Policies.length == 0 || !match(Policies[0], flags)) 190 { 191 return primary; 192 } 193 else static if (Policies.length && match(Policies[$ - 2], flags)) 194 { 195 return extras[$ - 1]; 196 } 197 else 198 { 199 foreach (i, choice; Stride2!Policies) 200 { 201 static if (!match(choice, flags)) 202 { 203 return extras[i - 1]; 204 } 205 } 206 assert(0); 207 } 208 } 209 210 /// ditto 211 auto ref allocatorFor(T)() 212 { 213 static if (is(T == void[])) 214 { 215 return primary; 216 } 217 else 218 { 219 return allocatorFor!(type2flags!T)(); 220 } 221 } 222 223 /** 224 Given a type `T`, returns its allocation-related flags as a combination of 225 `AllocFlag` values. 226 */ 227 static uint type2flags(T)() 228 { 229 uint result; 230 static if (is(T == immutable)) 231 result |= AllocFlag.immutableShared; 232 else static if (is(T == shared)) 233 result |= AllocFlag.forSharing; 234 static if (!is(T == U[], U)) 235 result |= AllocFlag.fixedSize; 236 import std.traits : hasIndirections; 237 static if (!hasIndirections!T) 238 result |= AllocFlag.hasNoIndirections; 239 return result; 240 } 241 242 /** 243 Dynamically allocates (using the appropriate allocator chosen with 244 `allocatorFor!T`) and then creates in the memory allocated an object of 245 type `T`, using `args` (if any) for its initialization. Initialization 246 occurs in the memory allocated and is otherwise semantically the same as 247 `T(args)`. (Note that using `make!(T[])` creates a pointer to an 248 (empty) array of `T`s, not an array. To allocate and initialize an 249 array, use `makeArray!T` described below.) 250 251 Params: 252 T = Type of the object being created. 253 args = Optional arguments used for initializing the created object. If not 254 present, the object is default constructed. 255 256 Returns: If `T` is a class type, returns a reference to the created `T` 257 object. Otherwise, returns a `T*` pointing to the created object. In all 258 cases, returns `null` if allocation failed. 259 260 Throws: If `T`'s constructor throws, deallocates the allocated memory and 261 propagates the exception. 262 */ 263 auto make(T, A...)(auto ref A args) 264 { 265 return .make!T(allocatorFor!T, args); 266 } 267 268 /** 269 Create an array of `T` with `length` elements. The array is either 270 default-initialized, filled with copies of `init`, or initialized with 271 values fetched from `range`. 272 273 Params: 274 T = element type of the array being created 275 length = length of the newly created array 276 init = element used for filling the array 277 range = range used for initializing the array elements 278 279 Returns: 280 The newly-created array, or `null` if either `length` was `0` or 281 allocation failed. 282 283 Throws: 284 The first two overloads throw only if the used allocator's primitives do. 285 The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws. 286 */ 287 T[] makeArray(T)(size_t length) 288 { 289 return .makeArray!T(allocatorFor!(T[]), length); 290 } 291 292 /// Ditto 293 T[] makeArray(T)(size_t length, auto ref T init) 294 { 295 return .makeArray!T(allocatorFor!(T[]), init, length); 296 } 297 298 /// Ditto 299 T[] makeArray(T, R)(R range) 300 if (isInputRange!R) 301 { 302 return .makeArray!T(allocatorFor!(T[]), range); 303 } 304 305 /** 306 Grows `array` by appending `delta` more elements. The needed memory is 307 allocated using the same allocator that was used for the array type. The 308 extra elements added are either default-initialized, filled with copies of 309 `init`, or initialized with values fetched from `range`. 310 311 Params: 312 T = element type of the array being created 313 array = a reference to the array being grown 314 delta = number of elements to add (upon success the new length of `array` 315 is $(D array.length + delta)) 316 init = element used for filling the array 317 range = range used for initializing the array elements 318 319 Returns: 320 `true` upon success, `false` if memory could not be allocated. In the 321 latter case `array` is left unaffected. 322 323 Throws: 324 The first two overloads throw only if the used allocator's primitives do. 325 The overloads that involve copy initialization deallocate memory and 326 propagate the exception if the copy operation throws. 327 */ 328 bool expandArray(T)(ref T[] array, size_t delta) 329 { 330 return .expandArray(allocatorFor!(T[]), array, delta); 331 } 332 /// Ditto 333 bool expandArray(T)(T[] array, size_t delta, auto ref T init) 334 { 335 return .expandArray(allocatorFor!(T[]), array, delta, init); 336 } 337 /// Ditto 338 bool expandArray(T, R)(ref T[] array, R range) 339 if (isInputRange!R) 340 { 341 return .expandArray(allocatorFor!(T[]), array, range); 342 } 343 344 /** 345 Shrinks an array by `delta` elements using `allocatorFor!(T[])`. 346 347 If $(D arr.length < delta), does nothing and returns `false`. Otherwise, 348 destroys the last $(D arr.length - delta) elements in the array and then 349 reallocates the array's buffer. If reallocation fails, fills the array with 350 default-initialized data. 351 352 Params: 353 T = element type of the array being created 354 arr = a reference to the array being shrunk 355 delta = number of elements to remove (upon success the new length of 356 `arr` is $(D arr.length - delta)) 357 358 Returns: 359 `true` upon success, `false` if memory could not be reallocated. In the 360 latter case $(D arr[$ - delta .. $]) is left with default-initialized 361 elements. 362 363 Throws: 364 The first two overloads throw only if the used allocator's primitives do. 365 The overloads that involve copy initialization deallocate memory and 366 propagate the exception if the copy operation throws. 367 */ 368 bool shrinkArray(T)(ref T[] arr, size_t delta) 369 { 370 return .shrinkArray(allocatorFor!(T[]), arr, delta); 371 } 372 373 /** 374 Destroys and then deallocates (using `allocatorFor!T`) the object pointed 375 to by a pointer, the class object referred to by a `class` or `interface` 376 reference, or an entire array. It is assumed the respective entities had 377 been allocated with the same allocator. 378 */ 379 void dispose(T)(T* p) 380 { 381 return .dispose(allocatorFor!T, p); 382 } 383 /// Ditto 384 void dispose(T)(T p) 385 if (is(T == class) || is(T == interface)) 386 { 387 return .dispose(allocatorFor!T, p); 388 } 389 /// Ditto 390 void dispose(T)(T[] array) 391 { 392 return .dispose(allocatorFor!(T[]), array); 393 } 394 } 395 396 /// 397 @system unittest 398 { 399 import std.experimental.allocator.gc_allocator : GCAllocator; 400 import std.experimental.allocator.mallocator : Mallocator; 401 import std.experimental.allocator.mmap_allocator : MmapAllocator; 402 alias MyAllocator = TypedAllocator!(GCAllocator, 403 AllocFlag.fixedSize | AllocFlag.threadLocal, Mallocator, 404 AllocFlag.fixedSize | AllocFlag.threadLocal 405 | AllocFlag.hasNoIndirections, 406 MmapAllocator, 407 ); 408 409 MyAllocator a; 410 auto b = &a.allocatorFor!0(); 411 static assert(is(typeof(*b) == shared const(GCAllocator))); 412 enum f1 = AllocFlag.fixedSize | AllocFlag.threadLocal; 413 auto c = &a.allocatorFor!f1(); 414 static assert(is(typeof(*c) == Mallocator)); 415 enum f2 = AllocFlag.fixedSize | AllocFlag.threadLocal; 416 static assert(is(typeof(a.allocatorFor!f2()) == Mallocator)); 417 // Partial match 418 enum f3 = AllocFlag.threadLocal; 419 static assert(is(typeof(a.allocatorFor!f3()) == Mallocator)); 420 421 int* p = a.make!int; 422 scope(exit) a.dispose(p); 423 int[] arr = a.makeArray!int(42); 424 scope(exit) a.dispose(arr); 425 assert(a.expandArray(arr, 3)); 426 assert(a.shrinkArray(arr, 4)); 427 }