1 /// 2 module stdx.allocator.building_blocks.fallback_allocator; 3 4 import stdx.allocator.common; 5 6 /** 7 $(D FallbackAllocator) is the allocator equivalent of an "or" operator in 8 algebra. An allocation request is first attempted with the $(D Primary) 9 allocator. If that returns $(D null), the request is forwarded to the $(D 10 Fallback) allocator. All other requests are dispatched appropriately to one of 11 the two allocators. 12 13 In order to work, $(D FallbackAllocator) requires that $(D Primary) defines the 14 $(D owns) method. This is needed in order to decide which allocator was 15 responsible for a given allocation. 16 17 $(D FallbackAllocator) is useful for fast, special-purpose allocators backed up 18 by general-purpose allocators. The example below features a stack region backed 19 up by the $(D GCAllocator). 20 */ 21 struct FallbackAllocator(Primary, Fallback) 22 { 23 import mir.utility : min; 24 import stdx.allocator.internal : Ternary; 25 26 @system unittest 27 { 28 testAllocator!(() => FallbackAllocator()); 29 } 30 31 /// The primary allocator. 32 static if (stateSize!Primary) Primary primary; 33 else alias primary = Primary.instance; 34 35 /// The fallback allocator. 36 static if (stateSize!Fallback) Fallback fallback; 37 else alias fallback = Fallback.instance; 38 39 /** 40 If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator) 41 defines a static instance called `instance`. 42 */ 43 static if (!stateSize!Primary && !stateSize!Fallback) 44 { 45 enum FallbackAllocator instance = FallbackAllocator(); 46 } 47 48 /** 49 The alignment offered is the minimum of the two allocators' alignment. 50 */ 51 enum uint alignment = min(Primary.alignment, Fallback.alignment); 52 53 /** 54 Allocates memory trying the primary allocator first. If it returns $(D 55 null), the fallback allocator is tried. 56 */ 57 void[] allocate(size_t s) 58 { 59 auto result = primary.allocate(s); 60 return result.length == s ? result : fallback.allocate(s); 61 } 62 63 /** 64 $(D FallbackAllocator) offers $(D alignedAllocate) iff at least one of the 65 allocators also offers it. It attempts to allocate using either or both. 66 */ 67 static if (__traits(hasMember, Primary, "alignedAllocate") 68 || __traits(hasMember, Fallback, "alignedAllocate")) 69 void[] alignedAllocate(size_t s, uint a) 70 { 71 static if (__traits(hasMember, Primary, "alignedAllocate")) 72 {{ 73 auto result = primary.alignedAllocate(s, a); 74 if (result.length == s) return result; 75 }} 76 static if (__traits(hasMember, Fallback, "alignedAllocate")) 77 {{ 78 auto result = fallback.alignedAllocate(s, a); 79 if (result.length == s) return result; 80 }} 81 return null; 82 } 83 84 /** 85 86 $(D expand) is defined if and only if at least one of the allocators 87 defines $(D expand). It works as follows. If $(D primary.owns(b)), then the 88 request is forwarded to $(D primary.expand) if it is defined, or fails 89 (returning $(D false)) otherwise. If $(D primary) does not own $(D b), then 90 the request is forwarded to $(D fallback.expand) if it is defined, or fails 91 (returning $(D false)) otherwise. 92 93 */ 94 static if (__traits(hasMember, Primary, "owns") 95 && (__traits(hasMember, Primary, "expand") || __traits(hasMember, Fallback, "expand"))) 96 bool expand(ref void[] b, size_t delta) 97 { 98 if (!delta) return true; 99 if (!b.ptr) return false; 100 if (primary.owns(b) == Ternary.yes) 101 { 102 static if (__traits(hasMember, Primary, "expand")) 103 return primary.expand(b, delta); 104 else 105 return false; 106 } 107 static if (__traits(hasMember, Fallback, "expand")) 108 return fallback.expand(b, delta); 109 else 110 return false; 111 } 112 113 /** 114 115 $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D 116 primary.reallocate(b, newSize)) is attempted. If it fails, an attempt is 117 made to move the allocation from $(D primary) to $(D fallback). 118 119 If $(D primary) does not own $(D b), then $(D fallback.reallocate(b, 120 newSize)) is attempted. If that fails, an attempt is made to move the 121 allocation from $(D fallback) to $(D primary). 122 123 */ 124 static if (__traits(hasMember, Primary, "owns")) 125 bool reallocate(ref void[] b, size_t newSize) 126 { 127 bool crossAllocatorMove(From, To)(auto ref From from, auto ref To to) 128 { 129 auto b1 = to.allocate(newSize); 130 if (b1.length != newSize) return false; 131 if (b.length < newSize) b1[0 .. b.length] = b[]; 132 else b1[] = b[0 .. newSize]; 133 static if (__traits(hasMember, From, "deallocate")) 134 from.deallocate(b); 135 b = b1; 136 return true; 137 } 138 139 if (b is null || primary.owns(b) == Ternary.yes) 140 { 141 return primary.reallocate(b, newSize) 142 // Move from primary to fallback 143 || crossAllocatorMove(primary, fallback); 144 } 145 return fallback.reallocate(b, newSize) 146 // Interesting. Move from fallback to primary. 147 || crossAllocatorMove(fallback, primary); 148 } 149 150 static if (__traits(hasMember, Primary, "owns") 151 && (__traits(hasMember, Primary, "alignedAllocate") 152 || __traits(hasMember, Fallback, "alignedAllocate"))) 153 bool alignedReallocate(ref void[] b, size_t newSize, uint a) 154 { 155 bool crossAllocatorMove(From, To)(auto ref From from, auto ref To to) 156 { 157 static if (!__traits(hasMember, To, "alignedAllocate")) 158 { 159 return false; 160 } 161 else 162 { 163 auto b1 = to.alignedAllocate(newSize, a); 164 if (b1.length != newSize) return false; 165 if (b.length < newSize) b1[0 .. b.length] = b[]; 166 else b1[] = b[0 .. newSize]; 167 static if (__traits(hasMember, From, "deallocate")) 168 from.deallocate(b); 169 b = b1; 170 return true; 171 } 172 } 173 174 static if (__traits(hasMember, Primary, "alignedAllocate")) 175 { 176 if (b is null || primary.owns(b) == Ternary.yes) 177 { 178 return primary.alignedReallocate(b, newSize, a) 179 || crossAllocatorMove(primary, fallback); 180 } 181 } 182 static if (__traits(hasMember, Fallback, "alignedAllocate")) 183 { 184 return fallback.alignedReallocate(b, newSize, a) 185 || crossAllocatorMove(fallback, primary); 186 } 187 else 188 { 189 return false; 190 } 191 } 192 193 /** 194 $(D owns) is defined if and only if both allocators define $(D owns). 195 Returns $(D primary.owns(b) | fallback.owns(b)). 196 */ 197 static if (__traits(hasMember, Primary, "owns") && __traits(hasMember, Fallback, "owns")) 198 Ternary owns(void[] b) 199 { 200 return primary.owns(b) | fallback.owns(b); 201 } 202 203 /** 204 $(D resolveInternalPointer) is defined if and only if both allocators 205 define it. 206 */ 207 static if (__traits(hasMember, Primary, "resolveInternalPointer") 208 && __traits(hasMember, Fallback, "resolveInternalPointer")) 209 Ternary resolveInternalPointer(const void* p, ref void[] result) 210 { 211 Ternary r = primary.resolveInternalPointer(p, result); 212 return r == Ternary.no ? fallback.resolveInternalPointer(p, result) : r; 213 } 214 215 /** 216 $(D deallocate) is defined if and only if at least one of the allocators 217 define $(D deallocate). It works as follows. If $(D primary.owns(b)), 218 then the request is forwarded to $(D primary.deallocate) if it is defined, 219 or is a no-op otherwise. If $(D primary) does not own $(D b), then the 220 request is forwarded to $(D fallback.deallocate) if it is defined, or is a 221 no-op otherwise. 222 */ 223 static if (__traits(hasMember, Primary, "owns") && 224 (__traits(hasMember, Primary, "deallocate") 225 || __traits(hasMember, Fallback, "deallocate"))) 226 bool deallocate(void[] b) 227 { 228 if (primary.owns(b) == Ternary.yes) 229 { 230 static if (__traits(hasMember, Primary, "deallocate")) 231 return primary.deallocate(b); 232 else 233 return false; 234 } 235 else 236 { 237 static if (__traits(hasMember, Fallback, "deallocate")) 238 return fallback.deallocate(b); 239 else 240 return false; 241 } 242 } 243 244 /** 245 $(D empty) is defined if both allocators also define it. 246 247 Returns: $(D primary.empty & fallback.empty) 248 */ 249 static if (__traits(hasMember, Primary, "empty") && __traits(hasMember, Fallback, "empty")) 250 Ternary empty() 251 { 252 return primary.empty & fallback.empty; 253 } 254 } 255 256 @system unittest 257 { 258 import std.conv : text; 259 import stdx.allocator.building_blocks.region : InSituRegion; 260 import stdx.allocator.gc_allocator : GCAllocator; 261 import stdx.allocator.internal : Ternary; 262 FallbackAllocator!(InSituRegion!16_384, GCAllocator) a; 263 // This allocation uses the stack 264 auto b1 = a.allocate(1024); 265 assert(b1.length == 1024, text(b1.length)); 266 assert(a.primary.owns(b1) == Ternary.yes); 267 // This large allocation will go to the Mallocator 268 auto b2 = a.allocate(1024 * 1024); 269 assert(a.primary.owns(b2) == Ternary.no); 270 a.deallocate(b1); 271 a.deallocate(b2); 272 } 273 274 /** 275 Convenience function that uses type deduction to return the appropriate 276 $(D FallbackAllocator) instance. To initialize with allocators that don't have 277 state, use their $(D it) static member. 278 */ 279 FallbackAllocator!(Primary, Fallback) 280 fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f) 281 { 282 import mir.functional: forward; 283 284 alias R = FallbackAllocator!(Primary, Fallback); 285 286 static if (stateSize!Primary) 287 static if (stateSize!Fallback) 288 return R(forward!p, forward!f); 289 else 290 return R(forward!p); 291 else 292 static if (stateSize!Fallback) 293 return R(forward!f); 294 else 295 return R(); 296 } 297 298 /// 299 @system unittest 300 { 301 import stdx.allocator.building_blocks.region : Region; 302 import stdx.allocator.gc_allocator : GCAllocator; 303 import stdx.allocator.internal : Ternary; 304 auto a = fallbackAllocator(Region!GCAllocator(1024), GCAllocator.instance); 305 auto b1 = a.allocate(1020); 306 assert(b1.length == 1020); 307 assert(a.primary.owns(b1) == Ternary.yes); 308 auto b2 = a.allocate(10); 309 assert(b2.length == 10); 310 assert(a.primary.owns(b2) == Ternary.no); 311 }