// Written in the D programming language. /** Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/segregator.d) */ module std.experimental.allocator.building_blocks.segregator; import std.experimental.allocator.common; /** Dispatches allocations (and deallocations) between two allocators ($(D SmallAllocator) and `LargeAllocator`) depending on the size allocated, as follows. All allocations smaller than or equal to `threshold` will be dispatched to `SmallAllocator`. The others will go to `LargeAllocator`. If both allocators are `shared`, the `Segregator` will also offer $(D shared) methods. */ struct Segregator(size_t threshold, SmallAllocator, LargeAllocator) { import std.algorithm.comparison : min; import std.traits : hasMember, ReturnType; import std.typecons : Ternary; static if (stateSize!SmallAllocator) private SmallAllocator _small; else private alias _small = SmallAllocator.instance; static if (stateSize!LargeAllocator) private LargeAllocator _large; else private alias _large = LargeAllocator.instance; version (StdDdoc) { /** The alignment offered is the minimum of the two allocators' alignment. */ enum uint alignment; /** This method is defined only if at least one of the allocators defines it. The good allocation size is obtained from `SmallAllocator` if $(D s <= threshold), or `LargeAllocator` otherwise. (If one of the allocators does not define `goodAllocSize`, the default implementation in this module applies.) */ static size_t goodAllocSize(size_t s); /** The memory is obtained from `SmallAllocator` if $(D s <= threshold), or `LargeAllocator` otherwise. */ void[] allocate(size_t); /** This method is defined if both allocators define it, and forwards to `SmallAllocator` or `LargeAllocator` appropriately. */ void[] alignedAllocate(size_t, uint); /** This method is defined only if at least one of the allocators defines it. If `SmallAllocator` defines `expand` and $(D b.length + delta <= threshold), the call is forwarded to `SmallAllocator`. If $(D LargeAllocator) defines `expand` and $(D b.length > threshold), the call is forwarded to `LargeAllocator`. Otherwise, the call returns `false`. */ bool expand(ref void[] b, size_t delta); /** This method is defined only if at least one of the allocators defines it. If `SmallAllocator` defines `reallocate` and $(D b.length <= threshold && s <= threshold), the call is forwarded to $(D SmallAllocator). If `LargeAllocator` defines `expand` and $(D b.length > threshold && s > threshold), the call is forwarded to $(D LargeAllocator). Otherwise, the call returns `false`. */ bool reallocate(ref void[] b, size_t s); /** This method is defined only if at least one of the allocators defines it, and work similarly to `reallocate`. */ bool alignedReallocate(ref void[] b, size_t s, uint a); /** This method is defined only if both allocators define it. The call is forwarded to `SmallAllocator` if $(D b.length <= threshold), or $(D LargeAllocator) otherwise. */ Ternary owns(void[] b); /** This function is defined only if both allocators define it, and forwards appropriately depending on `b.length`. */ bool deallocate(void[] b); /** This function is defined only if both allocators define it, and calls `deallocateAll` for them in turn. */ bool deallocateAll(); /** This function is defined only if both allocators define it, and returns the conjunction of `empty` calls for the two. */ Ternary empty(); } /** Composite allocators involving nested instantiations of `Segregator` make it difficult to access individual sub-allocators stored within. $(D allocatorForSize) simplifies the task by supplying the allocator nested inside a `Segregator` that is responsible for a specific size `s`. Example: ---- alias A = Segregator!(300, Segregator!(200, A1, A2), A3); A a; static assert(typeof(a.allocatorForSize!10) == A1); static assert(typeof(a.allocatorForSize!250) == A2); static assert(typeof(a.allocatorForSize!301) == A3); ---- */ ref auto allocatorForSize(size_t s)() { static if (s <= threshold) static if (is(SmallAllocator == Segregator!(Args), Args...)) return _small.allocatorForSize!s; else return _small; else static if (is(LargeAllocator == Segregator!(Args), Args...)) return _large.allocatorForSize!s; else return _large; } enum uint alignment = min(SmallAllocator.alignment, LargeAllocator.alignment); private template Impl() { size_t goodAllocSize(size_t s) { return s <= threshold ? _small.goodAllocSize(s) : _large.goodAllocSize(s); } void[] allocate(size_t s) { return s <= threshold ? _small.allocate(s) : _large.allocate(s); } static if (hasMember!(SmallAllocator, "alignedAllocate") && hasMember!(LargeAllocator, "alignedAllocate")) void[] alignedAllocate(size_t s, uint a) { return s <= threshold ? _small.alignedAllocate(s, a) : _large.alignedAllocate(s, a); } static if (hasMember!(SmallAllocator, "expand") || hasMember!(LargeAllocator, "expand")) bool expand(ref void[] b, size_t delta) { if (!delta) return true; if (b.length + delta <= threshold) { // Old and new allocations handled by _small static if (hasMember!(SmallAllocator, "expand")) return _small.expand(b, delta); else return false; } if (b.length > threshold) { // Old and new allocations handled by _large static if (hasMember!(LargeAllocator, "expand")) return _large.expand(b, delta); else return false; } // Oops, cross-allocator transgression return false; } static if (hasMember!(SmallAllocator, "reallocate") || hasMember!(LargeAllocator, "reallocate")) bool reallocate(ref void[] b, size_t s) { static if (hasMember!(SmallAllocator, "reallocate")) if (b.length <= threshold && s <= threshold) { // Old and new allocations handled by _small return _small.reallocate(b, s); } static if (hasMember!(LargeAllocator, "reallocate")) if (b.length > threshold && s > threshold) { // Old and new allocations handled by _large return _large.reallocate(b, s); } // Cross-allocator transgression return .reallocate(this, b, s); } static if (hasMember!(SmallAllocator, "alignedReallocate") || hasMember!(LargeAllocator, "alignedReallocate")) bool alignedReallocate(ref void[] b, size_t s, uint a) { static if (hasMember!(SmallAllocator, "alignedReallocate")) if (b.length <= threshold && s <= threshold) { // Old and new allocations handled by _small return _small.alignedReallocate(b, s, a); } static if (hasMember!(LargeAllocator, "alignedReallocate")) if (b.length > threshold && s > threshold) { // Old and new allocations handled by _large return _large.alignedReallocate(b, s, a); } // Cross-allocator transgression return .alignedReallocate(this, b, s, a); } static if (hasMember!(SmallAllocator, "allocateZeroed") || hasMember!(LargeAllocator, "allocateZeroed")) package(std) void[] allocateZeroed()(size_t s) { if (s <= threshold) { static if (hasMember!(SmallAllocator, "allocateZeroed")) return _small.allocateZeroed(s); else { auto b = _small.allocate(s); (() @trusted => (cast(ubyte[]) b)[] = 0)(); // OK even if b is null. return b; } } else { static if (hasMember!(LargeAllocator, "allocateZeroed")) return _large.allocateZeroed(s); else { auto b = _large.allocate(s); (() @trusted => (cast(ubyte[]) b)[] = 0)(); // OK even if b is null. return b; } } } static if (hasMember!(SmallAllocator, "owns") && hasMember!(LargeAllocator, "owns")) Ternary owns(void[] b) { return Ternary(b.length <= threshold ? _small.owns(b) : _large.owns(b)); } static if (hasMember!(SmallAllocator, "deallocate") && hasMember!(LargeAllocator, "deallocate")) bool deallocate(void[] data) { return data.length <= threshold ? _small.deallocate(data) : _large.deallocate(data); } static if (hasMember!(SmallAllocator, "deallocateAll") && hasMember!(LargeAllocator, "deallocateAll")) bool deallocateAll() { // Use & insted of && to evaluate both return _small.deallocateAll() & _large.deallocateAll(); } static if (hasMember!(SmallAllocator, "empty") && hasMember!(LargeAllocator, "empty")) Ternary empty() { return _small.empty & _large.empty; } static if (hasMember!(SmallAllocator, "resolveInternalPointer") && hasMember!(LargeAllocator, "resolveInternalPointer")) Ternary resolveInternalPointer(const void* p, ref void[] result) { Ternary r = _small.resolveInternalPointer(p, result); return r == Ternary.no ? _large.resolveInternalPointer(p, result) : r; } } private enum sharedMethods = !stateSize!SmallAllocator && !stateSize!LargeAllocator && is(typeof(SmallAllocator.instance) == shared) && is(typeof(LargeAllocator.instance) == shared); static if (sharedMethods) { static shared Segregator instance; shared { mixin Impl!(); } } else { static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator) __gshared Segregator instance; mixin Impl!(); } } /// @system unittest { import std.experimental.allocator.building_blocks.free_list : FreeList; import std.experimental.allocator.gc_allocator : GCAllocator; import std.experimental.allocator.mallocator : Mallocator; alias A = Segregator!( 1024 * 4, Segregator!( 128, FreeList!(Mallocator, 0, 128), GCAllocator), Segregator!( 1024 * 1024, Mallocator, GCAllocator) ); A a; auto b = a.allocate(200); assert(b.length == 200); a.deallocate(b); } /** A `Segregator` with more than three arguments expands to a composition of elemental `Segregator`s, as illustrated by the following example: ---- alias A = Segregator!( n1, A1, n2, A2, n3, A3, A4 ); ---- With this definition, allocation requests for `n1` bytes or less are directed to `A1`; requests between $(D n1 + 1) and `n2` bytes (inclusive) are directed to `A2`; requests between $(D n2 + 1) and `n3` bytes (inclusive) are directed to `A3`; and requests for more than `n3` bytes are directed to `A4`. If some particular range should not be handled, `NullAllocator` may be used appropriately. */ template Segregator(Args...) if (Args.length > 3) { // Binary search private enum cutPoint = ((Args.length - 2) / 4) * 2; static if (cutPoint >= 2) { alias Segregator = .Segregator!( Args[cutPoint], .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]), .Segregator!(Args[cutPoint + 2 .. $]) ); } else { // Favor small sizes alias Segregator = .Segregator!( Args[0], Args[1], .Segregator!(Args[2 .. $]) ); } } /// @system unittest { import std.experimental.allocator.building_blocks.free_list : FreeList; import std.experimental.allocator.gc_allocator : GCAllocator; import std.experimental.allocator.mallocator : Mallocator; alias A = Segregator!( 128, FreeList!(Mallocator, 0, 128), 1024 * 4, GCAllocator, 1024 * 1024, Mallocator, GCAllocator ); A a; auto b = a.allocate(201); assert(b.length == 201); a.deallocate(b); } @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; import std.experimental.allocator.building_blocks.kernighan_ritchie : KRRegion; Segregator!(128, GCAllocator, KRRegion!GCAllocator) alloc; assert((() nothrow @safe @nogc => alloc.goodAllocSize(1))() == GCAllocator.instance.goodAllocSize(1)); // Note: we infer `shared` from GCAllocator.goodAllocSize so we need a // shared object in order to be able to use the function shared Segregator!(128, GCAllocator, GCAllocator) sharedAlloc; assert((() nothrow @safe @nogc => sharedAlloc.goodAllocSize(1))() == GCAllocator.instance.goodAllocSize(1)); } @system unittest { import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; import std.typecons : Ternary; alias A = Segregator!( 128, BitmappedBlock!(4096), BitmappedBlock!(4096) ); A a = A( BitmappedBlock!(4096)(new ubyte[4096 * 1024]), BitmappedBlock!(4096)(new ubyte[4096 * 1024]) ); assert(a.empty == Ternary.yes); auto b = a.allocate(42); assert(b.length == 42); assert(a.empty == Ternary.no); assert(a.alignedReallocate(b, 256, 512)); assert(b.length == 256); assert(a.alignedReallocate(b, 42, 512)); assert(b.length == 42); assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes); assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no); // Ensure deallocate inherits from parent allocators assert((() nothrow @nogc => a.deallocate(b))()); assert(a.empty == Ternary.yes); // Test that deallocateAll inherits from parents auto c = a.allocate(42); assert(c.length == 42); assert((() pure nothrow @safe @nogc => a.expand(c, 58))()); assert(c.length == 100); assert(a.empty == Ternary.no); assert((() nothrow @nogc => a.deallocateAll())()); assert(a.empty == Ternary.yes); } @system unittest { import std.experimental.allocator.gc_allocator : GCAllocator; import std.typecons : Ternary; shared Segregator!(1024 * 4, GCAllocator, GCAllocator) a; auto b = a.allocate(201); assert(b.length == 201); void[] p; assert((() nothrow @safe @nogc => a.resolveInternalPointer(&b[0], p))() == Ternary.yes); assert((() nothrow @safe @nogc => a.resolveInternalPointer(null, p))() == Ternary.no); // Ensure deallocate inherits from parent allocators assert((() nothrow @nogc => a.deallocate(b))()); } @system unittest { import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlockWithInternalPointers; import std.typecons : Ternary; alias A = Segregator!( 10_240, BitmappedBlockWithInternalPointers!(4096), BitmappedBlockWithInternalPointers!(4096) ); A a = A( BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]), BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]) ); assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); auto b = a.allocate(201); assert(b.length == 201); assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); assert((() nothrow @nogc => a.deallocate(b))()); } // Test that reallocate infers from parent @system unittest { import std.experimental.allocator.mallocator : Mallocator; alias a = Segregator!(10_240, Mallocator, Mallocator).instance; auto b = a.allocate(42); assert(b.length == 42); assert((() nothrow @nogc => a.reallocate(b, 100))()); assert(b.length == 100); assert((() nothrow @nogc => a.deallocate(b))()); } @system unittest { import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.typecons : Ternary; auto a = Segregator!(10_240, BorrowedRegion!(), BorrowedRegion!())( BorrowedRegion!()(new ubyte[4096 * 1024]), BorrowedRegion!()(new ubyte[4096 * 1024])); assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); auto b = a.alignedAllocate(42, 8); assert(b.length == 42); assert((() nothrow @nogc => a.alignedReallocate(b, 100, 8))()); assert(b.length == 100); assert((() nothrow @safe @nogc => a.empty)() == Ternary.no); assert((() nothrow @nogc => a.deallocate(b))()); }