module std.experimental.allocator.fallback_allocator; import std.experimental.allocator.common; /** $(D FallbackAllocator) is the allocator equivalent of an "or" operator in algebra. An allocation request is first attempted with the $(D Primary) allocator. If that returns $(D null), the request is forwarded to the $(D Fallback) allocator. All other requests are dispatched appropriately to one of the two allocators. In order to work, $(D FallbackAllocator) requires that $(D Primary) defines the $(D owns) method. This is needed in order to decide which allocator was responsible for a given allocation. $(D FallbackAllocator) is useful for fast, special-purpose allocators backed up by general-purpose allocators. The example below features a stack region backed up by the $(D GCAllocator). */ struct FallbackAllocator(Primary, Fallback) { import std.algorithm.comparison : min; import std.traits : hasMember; unittest { testAllocator!(() => FallbackAllocator()); } /// The primary allocator. static if (stateSize!Primary) Primary primary; else alias primary = Primary.it; /// The fallback allocator. static if (stateSize!Fallback) Fallback fallback; else alias fallback = Fallback.it; /** If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator) defines a static instance $(D it). */ static if (!stateSize!Primary && !stateSize!Fallback) { static FallbackAllocator it; } /** The alignment offered is the minimum of the two allocators' alignment. */ enum uint alignment = min(Primary.alignment, Fallback.alignment); /** Allocates memory trying the primary allocator first. If it returns $(D null), the fallback allocator is tried. */ void[] allocate(size_t s) { auto result = primary.allocate(s); return result.length == s ? result : fallback.allocate(s); } /** $(D FallbackAllocator) offers $(D alignedAllocate) iff at least one of the allocators also offers it. It attempts to allocate using either or both. */ static if (hasMember!(Primary, "alignedAllocate") || hasMember!(Fallback, "alignedAllocate")) void[] alignedAllocate(size_t s, uint a) { static if (hasMember!(Primary, "alignedAllocate")) {{ auto result = primary.alignedAllocate(s, a); if (result.length == s) return result; }} static if (hasMember!(Fallback, "alignedAllocate")) {{ auto result = fallback.alignedAllocate(s, a); if (result.length == s) return result; }} return null; } /** $(D expand) is defined if and only if at least one of the allocators defines $(D expand). It works as follows. If $(D primary.owns(b)), then the request is forwarded to $(D primary.expand) if it is defined, or fails (returning $(D false)) otherwise. If $(D primary) does not own $(D b), then the request is forwarded to $(D fallback.expand) if it is defined, or fails (returning $(D false)) otherwise. */ static if (hasMember!(Primary, "owns") && (hasMember!(Primary, "expand") || hasMember!(Fallback, "expand"))) bool expand(ref void[] b, size_t delta) { if (!delta) return true; if (!b.ptr) { b = allocate(delta); return b.length == delta; } if (primary.owns(b) == Ternary.yes) { static if (hasMember!(Primary, "expand")) return primary.expand(b, delta); else return false; } static if (hasMember!(Fallback, "expand")) return fallback.expand(b, delta); else return false; } /** $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D primary.reallocate(b, newSize)) is attempted. If it fails, an attempt is made to move the allocation from $(D primary) to $(D fallback). If $(D primary) does not own $(D b), then $(D fallback.reallocate(b, newSize)) is attempted. If that fails, an attempt is made to move the allocation from $(D fallback) to $(D primary). */ static if (hasMember!(Primary, "owns")) bool reallocate(ref void[] b, size_t newSize) { bool crossAllocatorMove(From, To)(ref From from, ref To to) { auto b1 = to.allocate(newSize); if (b1.length != newSize) return false; if (b.length < newSize) b1[0 .. b.length] = b[]; else b1[] = b[0 .. newSize]; static if (hasMember!(From, "deallocate")) from.deallocate(b); b = b1; return true; } if (b is null || primary.owns(b) == Ternary.yes) { return primary.reallocate(b, newSize) // Move from primary to fallback || crossAllocatorMove(primary, fallback); } return fallback.reallocate(b, newSize) // Interesting. Move from fallback to primary. || crossAllocatorMove(fallback, primary); } static if (hasMember!(Primary, "owns") && (hasMember!(Primary, "alignedAllocate") || hasMember!(Fallback, "alignedAllocate"))) bool alignedReallocate(ref void[] b, size_t newSize, uint a) { bool crossAllocatorMove(From, To)(ref From from, ref To to) { static if (!hasMember!(To, "alignedAllocate")) { return false; } else { auto b1 = to.alignedAllocate(newSize, a); if (b1.length != newSize) return false; if (b.length < newSize) b1[0 .. b.length] = b[]; else b1[] = b[0 .. newSize]; static if (hasMember!(From, "deallocate")) from.deallocate(b); b = b1; return true; } } static if (hasMember!(Primary, "alignedAllocate")) { if (b is null || primary.owns(b) == Ternary.yes) { return primary.alignedReallocate(b, newSize, a) || crossAllocatorMove(primary, fallback); } } static if (hasMember!(Fallback, "alignedAllocate")) { return fallback.alignedReallocate(b, newSize, a) || crossAllocatorMove(fallback, primary); } else { return false; } } /** $(D owns) is defined if and only if both allocators define $(D owns). Returns $(D primary.owns(b) || fallback.owns(b)). */ static if (hasMember!(Primary, "owns") && hasMember!(Fallback, "owns")) Ternary owns(void[] b) { return primary.owns(b) | fallback.owns(b); } /** $(D resolveInternalPointer) is defined if and only if both allocators define it. */ static if (hasMember!(Primary, "resolveInternalPointer") && hasMember!(Fallback, "resolveInternalPointer")) void[] resolveInternalPointer(void* p) { if (auto r = primary.resolveInternalPointer(p)) return r; if (auto r = fallback.resolveInternalPointer(p)) return r; return null; } /** $(D deallocate) is defined if and only if at least one of the allocators define $(D deallocate). It works as follows. If $(D primary.owns(b)), then the request is forwarded to $(D primary.deallocate) if it is defined, or is a no-op otherwise. If $(D primary) does not own $(D b), then the request is forwarded to $(D fallback.deallocate) if it is defined, or is a no-op otherwise. */ static if (hasMember!(Primary, "owns") && (hasMember!(Primary, "deallocate") || hasMember!(Fallback, "deallocate"))) bool deallocate(void[] b) { if (primary.owns(b) == Ternary.yes) { static if (hasMember!(Primary, "deallocate")) return primary.deallocate(b); else return false; } else { static if (hasMember!(Fallback, "deallocate")) return fallback.deallocate(b); else return false; } } /** $(D empty) is defined if both allocators also define it. */ static if (hasMember!(Primary, "empty") && hasMember!(Fallback, "empty")) Ternary empty() { return primary.empty && fallback.empty; } } unittest { import std.experimental.allocator.region : InSituRegion; import std.experimental.allocator.gc_allocator : GCAllocator; import std.conv : text; FallbackAllocator!(InSituRegion!16384, GCAllocator) a; // This allocation uses the stack auto b1 = a.allocate(1024); assert(b1.length == 1024, text(b1.length)); assert(a.primary.owns(b1) == Ternary.yes); // This large allocation will go to the Mallocator auto b2 = a.allocate(1024 * 1024); assert(a.primary.owns(b2) == Ternary.no); a.deallocate(b1); a.deallocate(b2); } /* Forwards an argument from one function to another */ private auto ref forward(alias arg)() { static if (__traits(isRef, arg)) { return arg; } else { import std.algorithm : move; return move(arg); } } unittest { void fun(T)(auto ref T a, string b) { /* ... */ } void gun(T...)(auto ref T args) { fun(forward!(args[0]), forward!(args[1])); } gun(42, "hello"); int x; gun(x, "hello"); } unittest { static void checkByRef(T)(auto ref T value) { static assert(__traits(isRef, value)); } static void checkByVal(T)(auto ref T value) { static assert(!__traits(isRef, value)); } static void test1(ref int a) { checkByRef(forward!a); } static void test2(int a) { checkByVal(forward!a); } static void test3() { int a; checkByVal(forward!a); } } /** Convenience function that uses type deduction to return the appropriate $(D FallbackAllocator) instance. To initialize with allocators that don't have state, use their $(D it) static member. */ FallbackAllocator!(Primary, Fallback) fallbackAllocator(Primary, Fallback)(auto ref Primary p, auto ref Fallback f) { alias R = FallbackAllocator!(Primary, Fallback); import std.algorithm : move; static if (stateSize!Primary) static if (stateSize!Fallback) return R(forward!p, forward!f); else return R(forward!p); else static if (stateSize!Fallback) return R(forward!f); else return R(); } /// unittest { import std.experimental.allocator.region; import std.experimental.allocator.gc_allocator; auto a = fallbackAllocator(Region!GCAllocator(1024), GCAllocator.it); auto b1 = a.allocate(1020); assert(b1.length == 1020); assert(a.primary.owns(b1) == Ternary.yes); auto b2 = a.allocate(10); assert(b2.length == 10); assert(a.primary.owns(b2) == Ternary.no); }