// Written in the D programming language.
/**
Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/_bitmapped_block.d)
*/
module std.experimental.allocator.building_blocks.bitmapped_block;

import std.experimental.allocator.building_blocks.null_allocator;
import std.experimental.allocator.common;
import std.typecons : Flag, Yes, No;


// Common implementation for shared and non-shared versions of the BitmappedBlock
private mixin template BitmappedBlockImpl(bool isShared, bool multiBlock)
{
    import std.conv : text;
    import std.traits : hasMember;
    import std.typecons : Ternary;
    import std.typecons : tuple, Tuple;

    static if (isShared && multiBlock)
    import core.internal.spinlock : SpinLock;

    static assert(theBlockSize > 0 && theAlignment.isGoodStaticAlignment);
    static assert(theBlockSize == chooseAtRuntime ||
        theBlockSize % theAlignment == 0, "Block size must be a multiple of the alignment");

    static if (theBlockSize != chooseAtRuntime)
    {
        alias blockSize = theBlockSize;
    }
    else
    {
        // It is the caller's responsibilty to synchronize this with
        // allocate/deallocate in shared environments
        @property uint blockSize() { return _blockSize; }
        @property void blockSize(uint s)
        {
            static if (multiBlock)
            {
                assert((cast(BitVector) _control).length == 0 && s % alignment == 0);
            }
            else
            {
                assert(_control.length == 0 && s % alignment == 0);
            }
            _blockSize = s;
        }
        private uint _blockSize;
    }

    static if (is(ParentAllocator == NullAllocator))
    {
        private enum parentAlignment = platformAlignment;
    }
    else
    {
        private alias parentAlignment = ParentAllocator.alignment;
        static assert(parentAlignment >= ulong.alignof);
    }

    alias alignment = theAlignment;

    static if (stateSize!ParentAllocator)
    {
        ParentAllocator parent;
    }
    else
    {
        alias parent = ParentAllocator.instance;
    }

    private size_t _blocks;
    private void[] _payload;
    private size_t _startIdx;

    // For multiblock, '_control' is a BitVector, otherwise just a regular ulong[]
    static if (multiBlock)
    {
        // Keeps track of first block which has never been used in an allocation.
        // All blocks which are located right to the '_freshBit', should have never been
        // allocated
        private ulong _freshBit;
        private BitVector _control;
    }
    else
    {
        private ulong[] _control;
    }

    static if (multiBlock && isShared)
    {
        SpinLock lock = SpinLock(SpinLock.Contention.brief);
    }

    pure nothrow @safe @nogc
    private size_t totalAllocation(size_t capacity)
    {
        auto blocks = capacity.divideRoundUp(blockSize);
        auto leadingUlongs = blocks.divideRoundUp(64);
        import std.algorithm.comparison : min;
        immutable initialAlignment = min(parentAlignment,
            1U << trailingZeros(leadingUlongs * 8));
        auto maxSlack = alignment <= initialAlignment
            ? 0
            : alignment - initialAlignment;
        return leadingUlongs * 8 + maxSlack + blockSize * blocks;
    }

    this(ubyte[] data)
    {
        immutable a = data.ptr.effectiveAlignment;
        assert(a >= size_t.alignof || !data.ptr,
            "Data must be aligned properly");

        immutable ulong totalBits = data.length * 8;
        immutable ulong bitsPerBlock = blockSize * 8 + 1;
        _blocks = totalBits / bitsPerBlock;

        // Reality is a bit more complicated, iterate until a good number of
        // blocks found.
        size_t localBlocks;
        for (localBlocks = _blocks; localBlocks; --localBlocks)
        {
            immutable controlWords = localBlocks.divideRoundUp(64);
            auto payload = data[controlWords * 8 .. $].roundStartToMultipleOf(
                alignment);
            if (payload.length < localBlocks * blockSize)
            {
                // Overestimated
                continue;
            }

            // Need the casts for shared versions
            static if (multiBlock)
            {
                _control = cast(typeof(_control)) BitVector((cast(ulong*) data.ptr)[0 .. controlWords]);
                (cast(BitVector) _control)[] = 0;
            }
            else
            {
                _control = (cast(typeof(_control.ptr)) data.ptr)[0 .. controlWords];
                _control[] = 0;
            }

            _payload = cast(typeof(_payload)) payload;
            break;
        }

        _blocks = cast(typeof(_blocks)) localBlocks;
    }

    static if (chooseAtRuntime == theBlockSize)
    this(ubyte[] data, uint blockSize)
    {
        this._blockSize = blockSize;
        this(data);
    }

    static if (!is(ParentAllocator == NullAllocator))
    this(size_t capacity)
    {
        size_t toAllocate = totalAllocation(capacity);
        auto data = cast(ubyte[])(parent.allocate(toAllocate));
        this(data);
        assert(_blocks * blockSize >= capacity);
    }

    static if (!is(ParentAllocator == NullAllocator) &&
        chooseAtRuntime == theBlockSize)
    this(size_t capacity, uint blockSize)
    {
        this._blockSize = blockSize;
        this(capacity);
    }

    static if (!is(ParentAllocator == NullAllocator)
        && hasMember!(ParentAllocator, "deallocate"))
    ~this()
    {
        // multiblock bitmapped blocks use a BitVector
        static if (multiBlock)
        {
            void* start = cast(void*) _control.rep.ptr;
        }
        else
        {
            void* start = cast(void*) _control.ptr;
        }
        void* end = cast(void*) (_payload.ptr + _payload.length);
        parent.deallocate(start[0 .. end - start]);
    }

    pure nothrow @safe @nogc
    size_t goodAllocSize(size_t n)
    {
        return n.roundUpToMultipleOf(blockSize);
    }

    // Implementation of the 'multiBlock' BitmappedBlock
    // For the shared version, the methods are protected by a common lock
    static if (multiBlock)
    {
        /*
        Adjusts the memoized _startIdx to the leftmost control word that has at
        least one zero bit. Assumes all control words to the left of $(D
        _control[_startIdx]) are already occupied.
        */
        private void adjustStartIdx()
        {
            while (_startIdx < _control.rep.length && _control.rep[_startIdx] == ulong.max)
            {
                static if (isShared)
                {
                    // Shared demands atomic increment, however this is protected
                    // by a lock. Regular increment is fine
                    auto localStart = _startIdx + 1;
                    _startIdx = localStart;
                }
                else
                {
                    ++_startIdx;
                }
            }
        }

        /*
        Based on the latest allocated bit, 'newBit', it adjusts '_freshBit'
        */
        pure nothrow @safe @nogc
        private void adjustFreshBit(const ulong newBit)
        {
            import std.algorithm.comparison : max;
            static if (isShared)
            {
                auto localFreshBit = max(newBit, _freshBit);
                _freshBit = localFreshBit;
            }
            else
            {
                _freshBit = max(newBit, _freshBit);
            }
        }

        /*
        Returns the blocks corresponding to the control bits starting at word index
        wordIdx and bit index msbIdx (MSB=0) for a total of howManyBlocks.
        */
        @trusted
        private void[] blocksFor(this _)(size_t wordIdx, uint msbIdx, size_t howManyBlocks)
        {
            assert(msbIdx <= 63);
            const start = (wordIdx * 64 + msbIdx) * blockSize;
            const end = start + blockSize * howManyBlocks;
            if (start == end) return null;
            if (end <= _payload.length) return cast(void[]) _payload[start .. end];
            // This could happen if we have more control bits than available memory.
            // That's possible because the control bits are rounded up to fit in
            // 64-bit words.
            return null;
        }

        static if (isShared)
        nothrow @safe @nogc
        void[] allocate(const size_t s)
        {
            lock.lock();
            scope(exit) lock.unlock();

            return allocateImpl(s);
        }

        static if (!isShared)
        pure nothrow @safe @nogc
        void[] allocate(const size_t s)
        {
            return allocateImpl(s);
        }


        // If shared, this is protected by a lock inside 'allocate'
        pure nothrow @trusted @nogc
        private void[] allocateImpl(const size_t s)
        {
            const blocks = s.divideRoundUp(blockSize);
            void[] result;

        Lswitch:
            switch (blocks)
            {
            case 1:
                // inline code here for speed
                // find the next available block
                foreach (i; _startIdx .. _control.rep.length)
                {
                    const w = _control.rep[i];
                    if (w == ulong.max) continue;
                    uint j = leadingOnes(w);
                    assert(j < 64, "Invalid number of blocks");
                    assert((_control.rep[i] & ((1UL << 63) >> j)) == 0, "Corrupted bitmap");
                    static if (isShared)
                    {
                        // Need the cast because shared does not recognize the lock
                        *(cast(ulong*) &_control._rep[i]) |= (1UL << 63) >> j;
                    }
                    else
                    {
                        _control.rep[i] |= (1UL << 63) >> j;
                    }
                    if (i == _startIdx)
                    {
                        adjustStartIdx();
                    }
                    result = blocksFor(i, j, 1);
                    break Lswitch;
                }
                goto case 0; // fall through
            case 0:
                return null;
            case 2: .. case 64:
                result = smallAlloc(cast(uint) blocks);
                break;
            default:
                result = hugeAlloc(blocks);
                break;
            }
            if (result)
            {
                adjustFreshBit((result.ptr - _payload.ptr) / blockSize + blocks);
            }
            return result.ptr ? result.ptr[0 .. s] : null;
        }

        @trusted void[] allocateFresh(const size_t s)
        {
            static if (isShared)
            {
                lock.lock();
                scope(exit) lock.unlock();
            }

            const blocks = s.divideRoundUp(blockSize);

            void[] result = blocksFor(cast(size_t) (_freshBit / 64),
                cast(uint) (_freshBit % 64), blocks);
            if (result)
            {
                (cast(BitVector) _control)[_freshBit .. _freshBit + blocks] = 1;
                static if (isShared)
                {
                    ulong localFreshBit = _freshBit;
                    localFreshBit += blocks;
                    _freshBit = localFreshBit;
                }
                else
                {
                    _freshBit += blocks;
                }
            }
            return result;
        }

        void[] alignedAllocate(size_t n, uint a)
        {
            static if (isShared)
            {
                lock.lock();
                scope(exit) lock.unlock();
            }

            return alignedAllocateImpl(n, a);
        }

        // If shared, this is protected by a lock inside 'alignedAllocate'
        private void[] alignedAllocateImpl(size_t n, uint a)
        {
            import std.math : isPowerOf2;
            assert(a.isPowerOf2);
            if (a <= alignment) return allocate(n);

            // Overallocate to make sure we can get an aligned block
            auto b = allocateImpl((n + a - alignment).roundUpToMultipleOf(blockSize));
            if (!b.ptr) return null;
            auto result = b.roundStartToMultipleOf(a);
            assert(result.length >= n);
            result = result.ptr[0 .. n]; // final result

            // Free any blocks that might be slack at the beginning
            auto slackHeadingBlocks = (result.ptr - b.ptr) / blockSize;
            if (slackHeadingBlocks)
            {
                deallocateImpl(b[0 .. slackHeadingBlocks * blockSize]);
            }

            // Free any blocks that might be slack at the end
            auto slackTrailingBlocks = ((b.ptr + b.length)
                - (result.ptr + result.length)) / blockSize;
            if (slackTrailingBlocks)
            {
                deallocateImpl(b[$ - slackTrailingBlocks * blockSize .. $]);
            }

            return result;
        }

        /*
        Tries to allocate "blocks" blocks at the exact position indicated by the
        position wordIdx/msbIdx (msbIdx counts from MSB, i.e. MSB has index 0). If
        it succeeds, fills "result" with the result and returns tuple(size_t.max,
        0). Otherwise, returns a tuple with the next position to search.
        */
        private Tuple!(size_t, uint) allocateAt(size_t wordIdx, uint msbIdx,
                size_t blocks, ref void[] result)
        {
            assert(blocks > 0);
            assert(wordIdx < _control.rep.length);
            assert(msbIdx <= 63);
            void[] tmpResult;
            result = null;
            if (msbIdx + blocks <= 64)
            {
                // Allocation should fit this control word
                static if (isShared)
                {
                    ulong localControl = _control.rep[wordIdx];
                    bool didSetBit = setBitsIfZero(localControl,
                        cast(uint) (64 - msbIdx - blocks), 63 - msbIdx);
                    _control.rep[wordIdx] = localControl;
                }
                else
                {
                    bool didSetBit = setBitsIfZero(_control.rep[wordIdx],
                        cast(uint) (64 - msbIdx - blocks), 63 - msbIdx);
                }
                if (didSetBit)
                {
                    tmpResult = blocksFor(wordIdx, msbIdx, blocks);
                    if (!tmpResult)
                    {
                        static if (isShared)
                        {
                            localControl = _control.rep[wordIdx];
                            resetBits(localControl,
                                cast(uint) (64 - msbIdx - blocks), 63 - msbIdx);
                            _control.rep[wordIdx] = localControl;
                        }
                        else
                        {
                            resetBits(_control.rep[wordIdx],
                                cast(uint) (64 - msbIdx - blocks), 63 - msbIdx);
                        }
                        return tuple(size_t.max - 1, 0u);
                    }
                    result = tmpResult;
                    tmpResult = null;
                    return tuple(size_t.max, 0u);
                }
                // Can't allocate, make a suggestion
                return msbIdx + blocks == 64
                    ? tuple(wordIdx + 1, 0u)
                    : tuple(wordIdx, cast(uint) (msbIdx + blocks));
            }
            // Allocation spans two control words or more
            immutable mask = ulong.max >> msbIdx;
            if (_control.rep[wordIdx] & mask)
            {
                // We can't allocate the rest of this control word,
                // return a suggestion.
                return tuple(wordIdx + 1, 0u);
            }
            // We can allocate the rest of this control word, but we first need to
            // make sure we can allocate the tail.
            if (wordIdx + 1 == _control.rep.length)
            {
                // No more memory
                return tuple(_control.rep.length, 0u);
            }
            auto hint = allocateAt(wordIdx + 1, 0, blocks - 64 + msbIdx, result);
            if (hint[0] == size_t.max)
            {
                tmpResult = blocksFor(wordIdx, msbIdx, blocks);
                if (!tmpResult)
                {
                    return tuple(size_t.max - 1, 0u);
                }
                static if (isShared)
                {
                    // Dont want atomics, because this is protected by 'lock'
                    ulong localControl = _control.rep[wordIdx];
                    localControl |= mask;
                    _control.rep[wordIdx] = localControl;
                }
                else
                {
                    _control.rep[wordIdx] |= mask;
                }
                result = tmpResult;
                tmpResult = null;
                return tuple(size_t.max, 0u);
            }
            // Failed, return a suggestion that skips this whole run.
            return hint;
        }

        /* Allocates as many blocks as possible at the end of the blocks indicated
        by wordIdx. Returns the number of blocks allocated. */
        private uint allocateAtTail(size_t wordIdx)
        {
            assert(wordIdx < _control.rep.length);
            const available = trailingZeros(_control.rep[wordIdx]);
            static if (isShared)
            {
                ulong localControl = _control.rep[wordIdx];
                localControl |= ulong.max >> available;
                _control.rep[wordIdx] = localControl;
            }
            else
            {
                _control.rep[wordIdx] |= ulong.max >> available;
            }
            return available;
        }

        pure nothrow @safe @nogc
        private void[] smallAlloc(uint blocks)
        {
            assert(blocks >= 2 && blocks <= 64);
            void[] result;
            foreach (i; _startIdx .. _control.rep.length)
            {
                // Test within the current 64-bit word
                const v = _control.rep[i];
                if (v == ulong.max) continue;
                auto j = findContigOnes(~v, blocks);
                if (j < 64)
                {
                    // yay, found stuff
                    result = blocksFor(i, j, blocks);
                    if (result)
                    {
                        static if (isShared)
                        {
                            ulong localControl = _control.rep[i];
                            setBits(localControl, 64 - j - blocks, 63 - j);
                            _control.rep[i] = localControl;
                        }
                        else
                        {
                            setBits(_control.rep[i], 64 - j - blocks, 63 - j);
                        }
                    }
                    return result;
                }
                // Next, try allocations that cross a word
                auto available = trailingZeros(v);
                if (available == 0) continue;
                if (i + 1 >= _control.rep.length) break;
                assert(available < blocks); // otherwise we should have found it
                auto needed = blocks - available;
                assert(needed > 0 && needed < 64);
                result = blocksFor(i, 64 - available, blocks);
                if (result && allocateAtFront(i + 1, needed))
                {
                    static if (isShared)
                    {
                        ulong localControl = _control.rep[i];
                        localControl |= (1UL << available) - 1;
                        _control.rep[i] = localControl;
                    }
                    else
                    {
                        _control.rep[i] |= (1UL << available) - 1;
                    }
                    return result;
                }
            }
            return null;
        }

        pure nothrow @trusted @nogc
        private void[] hugeAlloc(size_t blocks)
        {
            assert(blocks > 64);
            if (_startIdx == _control._rep.length)
            {
                assert((cast(BitVector) _control).allAre1);
                return null;
            }

            auto i = (cast(BitVector)_control).findZeros(blocks, _startIdx * 64);
            if (i == i.max || i + blocks > _blocks) return null;
            // Allocate those bits
            (cast(BitVector) _control)[i .. i + blocks] = 1;
            return cast(void[]) _payload[cast(size_t) (i * blockSize)
                .. cast(size_t) ((i + blocks) * blockSize)];
        }

        // Rounds sizeInBytes to a multiple of blockSize.
        private size_t bytes2blocks(size_t sizeInBytes)
        {
            return (sizeInBytes + blockSize - 1) / blockSize;
        }

        /* Allocates given blocks at the beginning blocks indicated by wordIdx.
        Returns true if allocation was possible, false otherwise. */
        private bool allocateAtFront(size_t wordIdx, uint blocks)
        {
            assert(wordIdx < _control.rep.length && blocks >= 1 && blocks <= 64);
            const mask = (1UL << (64 - blocks)) - 1;
            if (_control.rep[wordIdx] > mask) return false;
            static if (isShared)
            {
                ulong localControl = _control.rep[wordIdx];
                localControl |= ~mask;
                _control.rep[wordIdx] = localControl;
            }
            else
            {
                _control.rep[wordIdx] |= ~mask;
            }
            return true;
        }

        // Since the lock is not pure, only the single threaded 'expand' is pure
        static if (isShared)
        {
            nothrow @trusted @nogc
            bool expand(ref void[] b, immutable size_t delta)
            {
                lock.lock();
                scope(exit) lock.unlock();

                return expandImpl(b, delta);
            }
        }
        else
        {
            pure nothrow @trusted @nogc
            bool expand(ref void[] b, immutable size_t delta)
            {
                return expandImpl(b, delta);
            }
        }

        // If shared, this is protected by a lock inside 'expand'
        pure nothrow @trusted @nogc
        private bool expandImpl(ref void[] b, immutable size_t delta)
        {
            // Dispose with trivial corner cases
            if (b is null || delta == 0) return delta == 0;

            /* To simplify matters, refuse to expand buffers that don't start at a block start (this may be the case for blocks allocated with alignedAllocate).
            */
            if ((b.ptr - _payload.ptr) % blockSize) return false;

            const blocksOld = bytes2blocks(b.length);
            const blocksNew = bytes2blocks(b.length + delta);
            assert(blocksOld <= blocksNew);

            // Possibly we have enough slack at the end of the block!
            if (blocksOld == blocksNew)
            {
                b = b.ptr[0 .. b.length + delta];
                return true;
            }

            assert((b.ptr - _payload.ptr) % blockSize == 0);
            const blockIdx = (b.ptr - _payload.ptr) / blockSize;
            const blockIdxAfter = blockIdx + blocksOld;

            // Try the maximum
            const wordIdx = blockIdxAfter / 64,
                msbIdx = cast(uint) (blockIdxAfter % 64);
            void[] p;
            auto hint = allocateAt(wordIdx, msbIdx,  blocksNew - blocksOld, p);
            if (hint[0] != size_t.max)
            {
                return false;
            }
            // Expansion successful
            assert(p.ptr == b.ptr + blocksOld * blockSize);
            b = b.ptr[0 .. b.length + delta];
            adjustFreshBit(blockIdx + blocksNew);
            return true;
        }

        @system bool reallocate(ref void[] b, size_t newSize)
        {
            static if (isShared)
            {
                lock.lock();
                scope(exit) lock.unlock();
            }

            return reallocateImpl(b, newSize);
        }

        // If shared, this is protected by a lock inside 'reallocate'
        private @system bool reallocateImpl(ref void[] b, size_t newSize)
        {
            static bool slowReallocate(Allocator)(ref Allocator a, ref void[] b, size_t s)
            {
                if (b.length == s) return true;
                if (b.length <= s && a.expandImpl(b, s - b.length)) return true;
                auto newB = a.allocateImpl(s);
                if (newB.length != s) return false;
                if (newB.length <= b.length) newB[] = b[0 .. newB.length];
                else newB[0 .. b.length] = b[];
                a.deallocateImpl(b);
                b = newB;
                return true;
            }

            if (!b.ptr)
            {
                b = allocateImpl(newSize);
                return b.length == newSize;
            }
            if (newSize == 0)
            {
                deallocateImpl(b);
                b = null;
                return true;
            }
            if (newSize < b.length)
            {
                // Shrink. Will shrink in place by deallocating the trailing part.
                auto newCapacity = bytes2blocks(newSize) * blockSize;
                deallocateImpl(b[newCapacity .. $]);
                b = b[0 .. newSize];
                return true;
            }
            // Go the slow route
            return slowReallocate(this, b, newSize);
        }

        @system bool alignedReallocate(ref void[] b, size_t newSize, uint a)
        {
            static if (isShared)
            {
                lock.lock();
                scope(exit) lock.unlock();
            }

            return alignedReallocateImpl(b, newSize, a);
        }

        // If shared, this is protected by a lock inside 'alignedReallocate'
        private @system bool alignedReallocateImpl(ref void[] b, size_t newSize, uint a)
        {
            static bool slowAlignedReallocate(Allocator)(ref Allocator alloc,
                    ref void[] b, size_t s, uint a)
            {
                if (b.length <= s && b.ptr.alignedAt(a)
                    && alloc.expandImpl(b, s - b.length)) return true;

                auto newB = alloc.alignedAllocateImpl(s, a);
                if (newB.length != s) return false;
                if (newB.length <= b.length) newB[] = b[0 .. newB.length];
                else newB[0 .. b.length] = b[];
                alloc.deallocateImpl(b);
                b = newB;
                return true;
            }

            if (newSize == 0)
            {
                deallocateImpl(b);
                b = null;
                return true;
            }
            // Go the slow route
            return slowAlignedReallocate(this, b, newSize, a);
        }

        nothrow @nogc
        bool deallocate(void[] b)
        {
            static if (isShared)
            {
                lock.lock();
                scope(exit) lock.unlock();
            }

            return deallocateImpl(b);
        }

        // If shared, this is protected by a lock inside 'deallocate'
        nothrow @nogc
        private bool deallocateImpl(void[] b)
        {
            if (b is null) return true;

            // Locate position
            immutable pos = b.ptr - _payload.ptr;
            immutable blockIdx = pos / blockSize;

            // Adjust pointer, might be inside a block due to alignedAllocate
            void* begin = cast(void*) (_payload.ptr + blockIdx * blockSize),
                end = cast(void*) (b.ptr + b.length);
            b = begin[0 .. end - begin];
            // Round up size to multiple of block size
            auto blocks = b.length.divideRoundUp(blockSize);

            // Get into details
            auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64);
            if (_startIdx > wordIdx) _startIdx = wordIdx;

            // Three stages: heading bits, full words, leftover bits
            if (msbIdx)
            {
                if (blocks + msbIdx <= 64)
                {
                    static if (isShared)
                    {
                        ulong localControl = _control.rep[wordIdx];
                        resetBits(localControl,
                            cast(uint) (64 - msbIdx - blocks),
                            63 - msbIdx);
                        _control.rep[wordIdx] = localControl;
                    }
                    else
                    {
                        resetBits(_control.rep[wordIdx],
                            cast(uint) (64 - msbIdx - blocks),
                            63 - msbIdx);
                    }
                    return true;
                }
                else
                {
                    static if (isShared)
                    {
                        ulong localControl = _control.rep[wordIdx];
                        localControl &= ulong.max << (64 - msbIdx);
                        _control.rep[wordIdx] = localControl;
                    }
                    else
                    {
                        _control.rep[wordIdx] &= ulong.max << (64 - msbIdx);
                    }
                    blocks -= 64 - msbIdx;
                    ++wordIdx;
                    msbIdx = 0;
                }
            }

            // Stage 2: reset one word at a time
            for (; blocks >= 64; blocks -= 64)
            {
                _control.rep[wordIdx++] = 0;
            }

            // Stage 3: deal with leftover bits, if any
            assert(wordIdx <= _control.rep.length);
            if (blocks)
            {
                static if (isShared)
                {
                    ulong localControl = _control.rep[wordIdx];
                    localControl &= ulong.max >> blocks;
                    _control.rep[wordIdx] = localControl;
                }
                else
                {
                    _control.rep[wordIdx] &= ulong.max >> blocks;
                }
            }
            return true;
        }

        // Since the lock is not pure, only the single threaded version is pure
        static if (isShared)
        {
            nothrow @nogc
            bool deallocateAll()
            {
                lock.lock();
                scope(exit) lock.unlock();

                (cast(BitVector) _control)[] = 0;
                _startIdx = 0;
                return true;
            }
        }
        else
        {
            pure nothrow @nogc
            bool deallocateAll()
            {
                _control[] = 0;
                _startIdx = 0;
                return true;
            }
        }

        // Since the lock is not pure, only the single threaded version is pure
        static if (isShared)
        {
            nothrow @safe @nogc
            Ternary empty()
            {
                lock.lock();
                scope(exit) lock.unlock();

                return emptyImpl();
            }
        }
        else
        {
            pure nothrow @safe @nogc
            Ternary empty()
            {
                return Ternary(_control.allAre0());
            }
        }

        pure nothrow @trusted @nogc
        private Ternary emptyImpl()
        {
            return Ternary((cast(BitVector) _control).allAre0());
        }

        // Debug helper
        debug(StdBitmapped)
        private void dump()
        {
            import std.stdio : writefln, writeln;

            ulong controlLen = (cast(BitVector) _control).length;
            writefln("%s @ %s {", typeid(this), cast(void*) (cast(BitVector) _control)._rep.ptr);
            scope(exit) writeln("}");
            assert(_payload.length >= blockSize * _blocks);
            assert(controlLen >= _blocks);
            writefln("  _startIdx=%s; blockSize=%s; blocks=%s",
                _startIdx, blockSize, _blocks);
            if (!controlLen) return;
            uint blockCount = 1;
            bool inAllocatedStore = (cast(BitVector) _control)[0];
            void* start = cast(void*) _payload.ptr;
            for (size_t i = 1;; ++i)
            {
                if (i >= _blocks || (cast(BitVector) _control)[i] != inAllocatedStore)
                {
                    writefln("  %s block at 0x%s, length: %s (%s*%s)",
                        inAllocatedStore ? "Busy" : "Free",
                        cast(void*) start,
                        blockCount * blockSize,
                        blockCount, blockSize);
                    if (i >= _blocks) break;
                    assert(i < controlLen);
                    inAllocatedStore = (cast(BitVector) _control)[i];
                    start = cast(void*) (_payload.ptr + blockCount * blockSize);
                    blockCount = 1;
                }
                else
                {
                    ++blockCount;
                }
            }
        }

        void[] allocateAll()
        {
            static if (isShared)
            {
                lock.lock();
                scope(exit) lock.unlock();
            }

            if (emptyImpl != Ternary.yes) return null;
            (cast(BitVector) _control)[] = 1;
            return cast(void[]) _payload;
        }
    } // Finish Yes.multiblock implementation specifics
    else
    {
        static if (isShared)
        pure nothrow @trusted @nogc
        void[] allocate(const size_t s)
        {
            import core.atomic : cas, atomicLoad, atomicOp;
            import core.bitop : bsr;
            import std.range : iota;
            import std.algorithm.iteration : map;
            import std.array : array;

            if (s.divideRoundUp(blockSize) != 1)
                return null;

            // First zero bit position for all values in the 0 - 255 range
            // for fast lookup
            static immutable ubyte[255] firstZero = iota(255U).map!
                (x => (7 - (bsr((~x) & 0x000000ff)))).array;

            foreach (size_t i; 0 .. _control.length)
            {
                ulong controlVal, newControlVal, bitIndex;
                do
                {
                    bitIndex = 0;
                    newControlVal = 0;
                    controlVal = atomicLoad(_control[i]);

                    // skip all control words which have all bits set
                    if (controlVal == ulong.max)
                        break;

                    // fast lookup of first byte which has at least one zero bit
                    foreach (byteIndex; 0 .. 8)
                    {
                        ulong mask = (0xFFUL << (8 * (7 - byteIndex)));
                        if ((mask & controlVal) != mask)
                        {
                            ubyte byteVal = cast(ubyte) ((mask & controlVal) >> (8 * (7 - byteIndex)));
                            bitIndex += firstZero[byteVal];
                            newControlVal = controlVal | (1UL << (63 - bitIndex));
                            break;
                        }
                        bitIndex += 8;
                    }
                } while (!cas(&_control[i], controlVal, newControlVal));

                auto blockIndex = bitIndex + 64 * i;
                if (controlVal != ulong.max && blockIndex < _blocks)
                {
                    size_t payloadBlockStart = cast(size_t) blockIndex * blockSize;
                    return cast(void[]) _payload[payloadBlockStart .. payloadBlockStart + s];
                }
            }

            return null;
        }

        static if (!isShared)
        pure nothrow @trusted @nogc
        void[] allocate(const size_t s)
        {
            import core.bitop : bsr;
            import std.range : iota;
            import std.algorithm.iteration : map;
            import std.array : array;

            if (s.divideRoundUp(blockSize) != 1)
                return null;

            // First zero bit position for all values in the 0 - 255 range
            // for fast lookup
            static immutable ubyte[255] firstZero = iota(255U).map!
                (x => (7 - (bsr((~x) & 0x000000ff)))).array;

            _startIdx = (_startIdx + 1) % _control.length;
            foreach (size_t idx; 0 .. _control.length)
            {
                size_t i = (idx + _startIdx) % _control.length;
                size_t bitIndex = 0;
                // skip all control words which have all bits set
                if (_control[i] == ulong.max)
                    continue;

                // fast lookup of first byte which has at least one zero bit
                foreach (byteIndex; 0 .. 8)
                {
                    ulong mask = (0xFFUL << (8 * (7 - byteIndex)));
                    if ((mask & _control[i]) != mask)
                    {
                        ubyte byteVal = cast(ubyte) ((mask & _control[i]) >> (8 * (7 - byteIndex)));
                        bitIndex += firstZero[byteVal];
                        _control[i] |= (1UL << (63 - bitIndex));
                        break;
                    }
                    bitIndex += 8;
                }

                auto blockIndex = bitIndex + 64 * i;
                if (blockIndex < _blocks)
                {
                    size_t payloadBlockStart = cast(size_t) blockIndex * blockSize;
                    return cast(void[]) _payload[payloadBlockStart .. payloadBlockStart + s];
                }
            }

            return null;
        }

        nothrow @nogc
        bool deallocate(void[] b)
        {
            static if (isShared)
            import core.atomic : atomicOp;

            if (b is null)
                return true;

            auto blockIndex = (b.ptr - _payload.ptr) / blockSize;
            auto controlIndex = blockIndex / 64;
            auto bitIndex = blockIndex % 64;
            static if (isShared)
            {
                atomicOp!"&="(_control[controlIndex], ~(1UL << (63 - bitIndex)));
            }
            else
            {
                _control[controlIndex] &= ~(1UL << (63 - bitIndex));
            }

            return true;
        }

        pure nothrow @trusted @nogc
        bool expand(ref void[] b, immutable size_t delta)
        {
            if (delta == 0)
                return true;

            immutable newLength = delta + b.length;
            if (b is null || newLength > blockSize)
                return false;

            b = b.ptr[0 .. newLength];
            return true;
        }
    } // Finish No.multiblock implementation specifics

    pure nothrow @trusted @nogc
    Ternary owns(const void[] b) const
    {
        assert(b || b.length == 0, "Corrupt block.");
        return Ternary(b && _payload && (&b[0] >= &_payload[0])
               && (&b[0] + b.length) <= (&_payload[0] + _payload.length));
    }
}

/**
`BitmappedBlock` implements a simple heap consisting of one contiguous area
of memory organized in blocks, each of size `theBlockSize`. A block is a unit
of allocation. A bitmap serves as bookkeeping data, more precisely one bit per
block indicating whether that block is currently allocated or not.

Passing `NullAllocator` as `ParentAllocator` (the default) means user code
manages allocation of the memory block from the outside; in that case
`BitmappedBlock` must be constructed with a `ubyte[]` preallocated block and
has no responsibility regarding the lifetime of its support underlying storage.
If another allocator type is passed, `BitmappedBlock` defines a destructor that
uses the parent allocator to release the memory block. That makes the combination of `AllocatorList`,
`BitmappedBlock`, and a back-end allocator such as `MmapAllocator`
a simple and scalable solution for memory allocation.

There are advantages to storing bookkeeping data separated from the payload
(as opposed to e.g. using `AffixAllocator` to store metadata together with
each allocation). The layout is more compact (overhead is one bit per block),
searching for a free block during allocation enjoys better cache locality, and
deallocation does not touch memory around the payload being deallocated (which
is often cold).

Allocation requests are handled on a first-fit basis. Although linear in
complexity, allocation is in practice fast because of the compact bookkeeping
representation, use of simple and fast bitwise routines, and caching of the
first available block position. A known issue with this general approach is
fragmentation, partially mitigated by coalescing. Since `BitmappedBlock` does
not need to maintain the allocated size, freeing memory implicitly coalesces
free blocks together. Also, tuning `blockSize` has a considerable impact on
both internal and external fragmentation.

If the last template parameter is set to `No.multiblock`, the allocator will only serve
allocations which require at most `theBlockSize`. The `BitmappedBlock` has a specialized
implementation for single-block allocations which allows for greater performance,
at the cost of not being able to allocate more than one block at a time.

The size of each block can be selected either during compilation or at run
time. Statically-known block sizes are frequent in practice and yield slightly
better performance. To choose a block size statically, pass it as the `blockSize`
parameter as in `BitmappedBlock!(4096)`. To choose a block
size parameter, use `BitmappedBlock!(chooseAtRuntime)` and pass the
block size to the constructor.

Params:
    theBlockSize = the length of a block, which must be a multiple of `theAlignment`

    theAlignment = alignment of each block

    ParentAllocator = allocator from which the `BitmappedBlock` will draw memory.
        If set to `NullAllocator`, the storage must be passed via the constructor

    f = `Yes.multiblock` to support allocations spanning across multiple blocks and
        `No.multiblock` to support single block allocations.
        Although limited by single block allocations, `No.multiblock` will generally
        provide higher performance.
*/
struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
   ParentAllocator = NullAllocator, Flag!"multiblock" f = Yes.multiblock)
{
    version(StdDdoc)
    {
        /**
        Constructs a block allocator given a hunk of memory, or a desired capacity
        in bytes.
        $(UL
        $(LI If `ParentAllocator` is $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator),
        only the constructor taking `data` is defined and the user is responsible for freeing `data` if desired.)
        $(LI Otherwise, both constructors are defined. The `data`-based
        constructor assumes memory has been allocated with the parent allocator.
        The `capacity`-based constructor uses `ParentAllocator` to allocate
        an appropriate contiguous hunk of memory. Regardless of the constructor
        used, the destructor releases the memory by using `ParentAllocator.deallocate`.)
        )
        */
        this(ubyte[] data);

        /// Ditto
        this(ubyte[] data, uint blockSize);

        /// Ditto
        this(size_t capacity);

        /// Ditto
        this(size_t capacity, uint blockSize);

        /**
        If `blockSize == chooseAtRuntime`, `BitmappedBlock` offers a read/write
        property `blockSize`. It must be set before any use of the allocator.
        Otherwise (i.e. `theBlockSize` is a legit constant), `blockSize` is
        an alias for `theBlockSize`. Whether constant or variable, must also be
        a multiple of `alignment`. This constraint is `assert`ed statically
        and dynamically.
        */
        alias blockSize = theBlockSize;

        /**
        The _alignment offered is user-configurable statically through parameter
        `theAlignment`, defaulted to `platformAlignment`.
        */
        alias alignment = theAlignment;

        /**
        The _parent allocator. Depending on whether `ParentAllocator` holds state
        or not, this is a member variable or an alias for
        `ParentAllocator.instance`.
        */
        ParentAllocator parent;

        /**
        Returns the actual bytes allocated when `n` bytes are requested, i.e.
        `n.roundUpToMultipleOf(blockSize)`.
        */
        pure nothrow @safe @nogc
        size_t goodAllocSize(size_t n);

        /**
        Returns `Ternary.yes` if `b` belongs to the `BitmappedBlock` object,
        `Ternary.no` otherwise. Never returns `Ternary.unkown`. (This
        method is somewhat tolerant in that accepts an interior slice.)
        */
        pure nothrow @trusted @nogc
        Ternary owns(const void[] b) const;

        /**
        Expands in place a buffer previously allocated by `BitmappedBlock`.
        If instantiated with `No.multiblock`, the expansion fails if the new length
        exceeds `theBlockSize`.
        */
        pure nothrow @trusted @nogc
        bool expand(ref void[] b, immutable size_t delta);

        /**
        Deallocates a block previously allocated with this allocator.
        */
        nothrow @nogc
        bool deallocate(void[] b);

        /**
        Allocates `s` bytes of memory and returns it, or `null` if memory
        could not be allocated.

        The following information might be of help with choosing the appropriate
        block size. Actual allocation occurs in sizes multiple of the block size.
        Allocating one block is the fastest because only one 0 bit needs to be
        found in the metadata. Allocating 2 through 64 blocks is the next cheapest
        because it affects a maximum of two `ulong` in the metadata.
        Allocations greater than 64 blocks require a multiword search through the
        metadata.

        If instantiated with `No.multiblock`, it performs a search for the first zero
        bit in the bitmap and sets it.
        */
        pure nothrow @trusted @nogc
        void[] allocate(const size_t s);

        /**
        Allocates s bytes of memory and returns it, or `null` if memory could not be allocated.
        `allocateFresh` behaves just like allocate, the only difference being that this always
        returns unused(fresh) memory. Although there may still be available space in the `BitmappedBlock`,
        `allocateFresh` could still return null, because all the available blocks have been previously deallocated.
        */
        @trusted void[] allocateFresh(const size_t s);

        /**
        If the `BitmappedBlock` object is empty (has no active allocation), allocates
        all memory within and returns a slice to it. Otherwise, returns `null`
        (i.e. no attempt is made to allocate the largest available block).
        */
        void[] allocateAll();

        /**
        Returns `Ternary.yes` if no memory is currently allocated with this
        allocator, otherwise `Ternary.no`. This method never returns
        `Ternary.unknown`.
        */
        pure nothrow @safe @nogc
        Ternary empty();

        /**
        Forcibly deallocates all memory allocated by this allocator, making it
        available for further allocations. Does not return memory to `ParentAllocator`.
        */
        pure nothrow @nogc
        bool deallocateAll();

        /**
        Reallocates a block previously allocated with `alignedAllocate`. Contractions do not occur in place.
        */
        @system bool alignedReallocate(ref void[] b, size_t newSize, uint a);

        /**
        Reallocates a previously-allocated block. Contractions occur in place.
        */
        @system bool reallocate(ref void[] b, size_t newSize);

        /**
        Allocates a block with specified alignment `a`. The alignment must be a
        power of 2. If `a <= alignment`, function forwards to `allocate`.
        Otherwise, it attempts to overallocate and then adjust the result for
        proper alignment. In the worst case the slack memory is around two blocks.
        */
        void[] alignedAllocate(size_t n, uint a);

        /**
        If `ParentAllocator` is not `NullAllocator` and defines `deallocate`,
        the destructor is defined to deallocate the block held.
        */
        ~this();
    }
    else
    {
        @system unittest
        {
            import std.algorithm.comparison : max;
            import std.experimental.allocator.mallocator : AlignedMallocator;
            auto m = cast(ubyte[])(AlignedMallocator.instance.alignedAllocate(1024 * 64,
                                    max(theAlignment, cast(uint) size_t.sizeof)));
            scope(exit) () nothrow @nogc { AlignedMallocator.instance.deallocate(m); }();
            static if (theBlockSize == chooseAtRuntime)
            {
                testAllocator!(() => BitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m, 64));
            }
            else
            {
                testAllocator!(() => BitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m));
            }
        }
        mixin BitmappedBlockImpl!(false, f == Yes.multiblock);
    }
}

///
@system unittest
{
    // Create a block allocator on top of a 10KB stack region.
    import std.experimental.allocator.building_blocks.region : InSituRegion;
    import std.traits : hasMember;
    InSituRegion!(10_240, 64) r;
    auto a = BitmappedBlock!(64, 64)(cast(ubyte[])(r.allocateAll()));
    static assert(hasMember!(InSituRegion!(10_240, 64), "allocateAll"));
    const b = a.allocate(100);
    assert(b.length == 100);
}

///
@system unittest
{
    import std.experimental.allocator.mallocator : Mallocator;
    import std.typecons : Flag, Yes;

    enum blockSize = 64;
    enum numBlocks = 10;

    // The 'BitmappedBlock' is implicitly instantiated with Yes.multiblock
    auto a = BitmappedBlock!(blockSize, 8, Mallocator, Yes.multiblock)(numBlocks * blockSize);

    // Instantiated with Yes.multiblock, can allocate more than one block at a time
    void[] buf = a.allocate(2 * blockSize);
    assert(buf.length == 2 * blockSize);
    assert(a.deallocate(buf));

    // Can also allocate less than one block
    buf = a.allocate(blockSize / 2);
    assert(buf.length == blockSize / 2);

    // Expands inside the same block
    assert(a.expand(buf, blockSize / 2));
    assert(buf.length == blockSize);

    // If Yes.multiblock, can expand past the size of a single block
    assert(a.expand(buf, 3 * blockSize));
    assert(buf.length == 4 * blockSize);
    assert(a.deallocate(buf));
}

///
@system unittest
{
    import std.experimental.allocator.mallocator : Mallocator;
    import std.typecons : Flag, No;

    enum blockSize = 64;
    auto a = BitmappedBlock!(blockSize, 8, Mallocator, No.multiblock)(1024 * blockSize);

    // Since instantiated with No.multiblock, can only allocate at most the block size
    void[] buf = a.allocate(blockSize + 1);
    assert(buf is null);

    buf = a.allocate(blockSize);
    assert(buf.length == blockSize);
    assert(a.deallocate(buf));

    // This is also fine, because it's less than the block size
    buf = a.allocate(blockSize / 2);
    assert(buf.length == blockSize / 2);

    // Can expand the buffer until its length is at most 64
    assert(a.expand(buf, blockSize / 2));
    assert(buf.length == blockSize);

    // Cannot expand anymore
    assert(!a.expand(buf, 1));
    assert(a.deallocate(buf));
}

/**
The threadsafe version of the $(LREF BitmappedBlock).
The semantics of the `SharedBitmappedBlock` are identical to the regular $(LREF BitmappedBlock).

Params:
    theBlockSize = the length of a block, which must be a multiple of `theAlignment`

    theAlignment = alignment of each block

    ParentAllocator = allocator from which the `BitmappedBlock` will draw memory.
        If set to `NullAllocator`, the storage must be passed via the constructor

    f = `Yes.multiblock` to support allocations spanning across multiple blocks and
        `No.multiblock` to support single block allocations.
        Although limited by single block allocations, `No.multiblock` will generally
        provide higher performance.
*/
shared struct SharedBitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment,
   ParentAllocator = NullAllocator, Flag!"multiblock" f = Yes.multiblock)
{
    version(StdDdoc)
    {
        /**
        Constructs a block allocator given a hunk of memory, or a desired capacity
        in bytes.
        $(UL
        $(LI If `ParentAllocator` is $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator),
        only the constructor taking `data` is defined and the user is responsible for freeing `data` if desired.)
        $(LI Otherwise, both constructors are defined. The `data`-based
        constructor assumes memory has been allocated with the parent allocator.
        The `capacity`-based constructor uses `ParentAllocator` to allocate
        an appropriate contiguous hunk of memory. Regardless of the constructor
        used, the destructor releases the memory by using `ParentAllocator.deallocate`.)
        )
        */
        this(ubyte[] data);

        /// Ditto
        this(ubyte[] data, uint blockSize);

        /// Ditto
        this(size_t capacity);

        /// Ditto
        this(size_t capacity, uint blockSize);

        /**
        If `blockSize == chooseAtRuntime`, `SharedBitmappedBlock` offers a read/write
        property `blockSize`. It must be set before any use of the allocator.
        Otherwise (i.e. `theBlockSize` is a legit constant), `blockSize` is
        an alias for `theBlockSize`. Whether constant or variable, must also be
        a multiple of `alignment`. This constraint is `assert`ed statically
        and dynamically.
        */
        alias blockSize = theBlockSize;

        /**
        The _alignment offered is user-configurable statically through parameter
        `theAlignment`, defaulted to `platformAlignment`.
        */
        alias alignment = theAlignment;

        /**
        The _parent allocator. Depending on whether `ParentAllocator` holds state
        or not, this is a member variable or an alias for
        `ParentAllocator.instance`.
        */
        ParentAllocator parent;

        /**
        Returns the actual bytes allocated when `n` bytes are requested, i.e.
        `n.roundUpToMultipleOf(blockSize)`.
        */
        pure nothrow @safe @nogc
        size_t goodAllocSize(size_t n);

        /**
        Returns `Ternary.yes` if `b` belongs to the `SharedBitmappedBlock` object,
        `Ternary.no` otherwise. Never returns `Ternary.unkown`. (This
        method is somewhat tolerant in that accepts an interior slice.)
        */
        pure nothrow @trusted @nogc
        Ternary owns(const void[] b) const;

        /**
        Expands in place a buffer previously allocated by `SharedBitmappedBlock`.
        Expansion fails if the new length exceeds the block size.
        */
        bool expand(ref void[] b, immutable size_t delta);

        /**
        Deallocates the given buffer `b`, by atomically setting the corresponding
        bit to `0`. `b` must be valid, and cannot contain multiple adjacent `blocks`.
        */
        nothrow @nogc
        bool deallocate(void[] b);

        /**
        Allocates `s` bytes of memory and returns it, or `null` if memory
        could not be allocated.

        The `SharedBitmappedBlock` cannot allocate more than the given block size.
        Allocations are satisfied by searching the first unset bit in the bitmap,
        and atomically setting it.
        In rare memory pressure scenarios, the allocation could fail.
        */
        nothrow @trusted @nogc
        void[] allocate(const size_t s);

        /**
        Allocates s bytes of memory and returns it, or `null` if memory could not be allocated.
        `allocateFresh` behaves just like allocate, the only difference being that this always
        returns unused(fresh) memory. Although there may still be available space in the `SharedBitmappedBlock`,
        `allocateFresh` could still return null, because all the available blocks have been previously deallocated.
        */
        @trusted void[] allocateFresh(const size_t s);

        /**
        If the `SharedBitmappedBlock` object is empty (has no active allocation), allocates
        all memory within and returns a slice to it. Otherwise, returns `null`
        (i.e. no attempt is made to allocate the largest available block).
        */
        void[] allocateAll();

        /**
        Returns `Ternary.yes` if no memory is currently allocated with this
        allocator, otherwise `Ternary.no`. This method never returns
        `Ternary.unknown`.
        */
        nothrow @safe @nogc
        Ternary empty();

        /**
        Forcibly deallocates all memory allocated by this allocator, making it
        available for further allocations. Does not return memory to `ParentAllocator`.
        */
        nothrow @nogc
        bool deallocateAll();

        /**
        Reallocates a block previously allocated with `alignedAllocate`. Contractions do not occur in place.
        */
        @system bool alignedReallocate(ref void[] b, size_t newSize, uint a);

        /**
        Reallocates a previously-allocated block. Contractions occur in place.
        */
        @system bool reallocate(ref void[] b, size_t newSize);

        /**
        Allocates a block with specified alignment `a`. The alignment must be a
        power of 2. If `a <= alignment`, function forwards to `allocate`.
        Otherwise, it attempts to overallocate and then adjust the result for
        proper alignment. In the worst case the slack memory is around two blocks.
        */
        void[] alignedAllocate(size_t n, uint a);

        /**
        If `ParentAllocator` is not `NullAllocator` and defines `deallocate`,
        the destructor is defined to deallocate the block held.
        */
        ~this();
    }
    else
    {
        version(unittest)
        @system unittest
        {
            import std.algorithm.comparison : max;
            import std.experimental.allocator.mallocator : AlignedMallocator;
            auto m = cast(ubyte[])(AlignedMallocator.instance.alignedAllocate(1024 * 64,
                                    max(theAlignment, cast(uint) size_t.sizeof)));
            scope(exit) () nothrow @nogc { AlignedMallocator.instance.deallocate(m); }();
            static if (theBlockSize == chooseAtRuntime)
            {
                testAllocator!(() => SharedBitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m, 64));
            }
            else
            {
                testAllocator!(() => SharedBitmappedBlock!(theBlockSize, theAlignment, NullAllocator)(m));
            }
        }
        mixin BitmappedBlockImpl!(true, f == Yes.multiblock);
    }
}

///
@system unittest
{
    import std.experimental.allocator.mallocator : Mallocator;
    import std.experimental.allocator.common : platformAlignment;
    import std.typecons : Flag, Yes, No;

    // Create 'numThreads' threads, each allocating in parallel a chunk of memory
    static void testAlloc(Allocator)(ref Allocator a, size_t allocSize)
    {
        import core.thread : ThreadGroup;
        import std.algorithm.sorting : sort;
        import core.internal.spinlock : SpinLock;

        SpinLock lock = SpinLock(SpinLock.Contention.brief);
        enum numThreads = 10;
        void[][numThreads] buf;
        size_t count = 0;

        // Each threads allocates 'allocSize'
        void fun()
        {
            void[] b = a.allocate(allocSize);
            assert(b.length == allocSize);

            lock.lock();
            scope(exit) lock.unlock();

            buf[count] = b;
            count++;
        }

        auto tg = new ThreadGroup;
        foreach (i; 0 .. numThreads)
        {
            tg.create(&fun);
        }
        tg.joinAll();

        // Sorting the allocations made by each thread, we expect the buffers to be
        // adjacent inside the SharedBitmappedBlock
        sort!((a, b) => a.ptr < b.ptr)(buf[0 .. numThreads]);
        foreach (i; 0 .. numThreads - 1)
        {
            assert(buf[i].ptr + a.goodAllocSize(buf[i].length) <= buf[i + 1].ptr);
        }

        // Deallocate everything
        foreach (i; 0 .. numThreads)
        {
            assert(a.deallocate(buf[i]));
        }
    }

    enum blockSize = 64;
    auto alloc1 = SharedBitmappedBlock!(blockSize, platformAlignment, Mallocator, Yes.multiblock)(1024 * 1024);
    auto alloc2 = SharedBitmappedBlock!(blockSize, platformAlignment, Mallocator, No.multiblock)(1024 * 1024);
    testAlloc(alloc1, 2 * blockSize);
    testAlloc(alloc2, blockSize);
}

@system unittest
{
    // Test chooseAtRuntime
    // Create a block allocator on top of a 10KB stack region.
    import std.experimental.allocator.building_blocks.region : InSituRegion;
    import std.traits : hasMember;
    InSituRegion!(10_240, 64) r;
    uint blockSize = 64;
    auto a = BitmappedBlock!(chooseAtRuntime, 64)(cast(ubyte[])(r.allocateAll()), blockSize);
    static assert(hasMember!(InSituRegion!(10_240, 64), "allocateAll"));
    const b = (() pure nothrow @safe @nogc => a.allocate(100))();
    assert(b.length == 100);
}

pure @safe unittest
{
    import std.typecons : Ternary;

    auto a = (() @trusted => BitmappedBlock!(64, 64, NullAllocator, Yes.multiblock)(new ubyte[10_240]))();
    () nothrow @nogc {
        assert(a.empty == Ternary.yes);
        const b = a.allocate(100);
        assert(b.length == 100);
        assert(a.empty == Ternary.no);
    }();
}

@safe unittest
{
    import std.typecons : Ternary;

    auto a = (() @trusted => SharedBitmappedBlock!(64, 64, NullAllocator, Yes.multiblock)(new ubyte[10_240]))();
    assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes);
    const b = a.allocate(100);
    assert(b.length == 100);
}

@system unittest
{
    import std.experimental.allocator.gc_allocator : GCAllocator;
    testAllocator!(() => BitmappedBlock!(64, 8, GCAllocator)(1024 * 64));
}

@system unittest
{
    // Test chooseAtRuntime
    import std.experimental.allocator.gc_allocator : GCAllocator;
    uint blockSize = 64;
    testAllocator!(() => BitmappedBlock!(chooseAtRuntime, 8, GCAllocator, Yes.multiblock)(1024 * 64, blockSize));
    testAllocator!(() => BitmappedBlock!(chooseAtRuntime, 8, GCAllocator, No.multiblock)(1024 * 64, blockSize));
}

@system unittest
{
    import std.experimental.allocator.mallocator : Mallocator;
    testAllocator!(() => SharedBitmappedBlock!(64, 8, Mallocator, Yes.multiblock)(1024 * 64));
    testAllocator!(() => SharedBitmappedBlock!(64, 8, Mallocator, No.multiblock)(1024 * 64));
}

@system unittest
{
    // Test chooseAtRuntime
    import std.experimental.allocator.mallocator : Mallocator;
    uint blockSize = 64;
    testAllocator!(() => SharedBitmappedBlock!(chooseAtRuntime, 8, Mallocator, Yes.multiblock)(1024 * 64, blockSize));
    testAllocator!(() => SharedBitmappedBlock!(chooseAtRuntime, 8, Mallocator, No.multiblock)(1024 * 64, blockSize));
}

@system unittest
{
    static void testAllocateAll(size_t bs, bool isShared = true)(size_t blocks, uint blocksAtATime)
    {
        template attribAllocate(string size)
        {
            static if (isShared)
            {
                const char[] attribAllocate = "(() nothrow @safe @nogc => a.allocate(" ~ size ~ "))()";
            }
            else
            {
                const char[] attribAllocate = "(() pure nothrow @safe @nogc => a.allocate(" ~ size ~ "))()";
            }
        }

        assert(bs);
        import std.typecons : Ternary;
        import std.algorithm.comparison : min;
        import std.experimental.allocator.gc_allocator : GCAllocator;

        static if (isShared)
        {
            auto a = SharedBitmappedBlock!(bs, min(bs, platformAlignment), NullAllocator)(
                cast(ubyte[])(GCAllocator.instance.allocate((blocks * bs * 8 + blocks) / 8)));
        }
        else
        {
            auto a = BitmappedBlock!(bs, min(bs, platformAlignment), NullAllocator)(
                cast(ubyte[])(GCAllocator.instance.allocate((blocks * bs * 8 + blocks) / 8)));
        }

        import std.conv : text;
        assert(blocks >= a._blocks, text(blocks, " < ", a._blocks));
        blocks = a._blocks;

        // test allocation of 0 bytes
        auto x = mixin(attribAllocate!("0"));
        assert(x is null);
        // test allocation of 1 byte
        x = mixin(attribAllocate!("1"));
        assert(x.length == 1 || blocks == 0);
        assert((() nothrow @nogc => a.deallocateAll())());
        assert(a.empty() == Ternary.yes);
        bool twice = true;

    begin:
        foreach (i; 0 .. blocks / blocksAtATime)
        {
            auto b = mixin(attribAllocate!("bs * blocksAtATime"));
            assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
        }

        assert(mixin(attribAllocate!("bs * blocksAtATime")) is null);
        if (a._blocks % blocksAtATime == 0)
        {
            assert(mixin(attribAllocate!("1")) is null);
        }

        // Now deallocate all and do it again!
        assert((() nothrow @nogc => a.deallocateAll())());

        // Test deallocation

        auto v = new void[][blocks / blocksAtATime];
        foreach (i; 0 .. blocks / blocksAtATime)
        {
            auto b = mixin(attribAllocate!("bs * blocksAtATime"));
            assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
            v[i] = b;
        }
        assert(mixin(attribAllocate!("bs * blocksAtATime")) is null);
        if (a._blocks % blocksAtATime == 0)
        {
            assert(mixin(attribAllocate!("1")) is null);
        }

        foreach (i; 0 .. blocks / blocksAtATime)
        {
            () nothrow @nogc { a.deallocate(v[i]); }();
        }

        foreach (i; 0 .. blocks / blocksAtATime)
        {
            auto b = mixin(attribAllocate!("bs * blocksAtATime"));
            assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
            v[i] = b;
        }

        foreach (i; 0 .. v.length)
        {
            () nothrow @nogc { a.deallocate(v[i]); }();
        }

        if (twice)
        {
            twice = false;
            goto begin;
        }

        assert((() nothrow @nogc => a.deallocateAll())());

        // test expansion
        if (blocks >= blocksAtATime)
        {
            foreach (i; 0 .. blocks / blocksAtATime - 1)
            {
                auto b = mixin(attribAllocate!("bs * blocksAtATime"));
                assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
                (cast(ubyte[]) b)[] = 0xff;
                static if (isShared)
                {
                    assert((() nothrow @safe @nogc => a.expand(b, blocksAtATime * bs))()
                            , text(i));
                }
                else
                {
                    assert((() pure nothrow @safe @nogc => a.expand(b, blocksAtATime * bs))()
                            , text(i));
                }
                (cast(ubyte[]) b)[] = 0xfe;
                assert(b.length == bs * blocksAtATime * 2, text(i, ": ", b.length));
                a.reallocate(b, blocksAtATime * bs) || assert(0);
                assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
            }
        }
    }

    testAllocateAll!(1)(0, 1);
    testAllocateAll!(1, false)(0, 1);
    testAllocateAll!(1)(8, 1);
    testAllocateAll!(1, false)(8, 1);

    testAllocateAll!(4096)(128, 1);
    testAllocateAll!(4096, false)(128, 1);

    testAllocateAll!(1)(0, 2);
    testAllocateAll!(1)(128, 2);
    testAllocateAll!(4096)(128, 2);

    testAllocateAll!(1, false)(0, 2);
    testAllocateAll!(1, false)(128, 2);
    testAllocateAll!(4096, false)(128, 2);

    testAllocateAll!(1)(0, 4);
    testAllocateAll!(1)(128, 4);
    testAllocateAll!(4096)(128, 4);

    testAllocateAll!(1, false)(0, 4);
    testAllocateAll!(1, false)(128, 4);
    testAllocateAll!(4096, false)(128, 4);

    testAllocateAll!(1)(0, 3);
    testAllocateAll!(1)(24, 3);
    testAllocateAll!(3008)(100, 1);
    testAllocateAll!(3008)(100, 3);

    testAllocateAll!(1, false)(0, 3);
    testAllocateAll!(1, false)(24, 3);
    testAllocateAll!(3008, false)(100, 1);
    testAllocateAll!(3008, false)(100, 3);

    testAllocateAll!(1)(0, 128);
    testAllocateAll!(1)(128 * 1, 128);
    testAllocateAll!(128 * 20)(13 * 128, 128);

    testAllocateAll!(1, false)(0, 128);
    testAllocateAll!(1, false)(128 * 1, 128);
    testAllocateAll!(128 * 20, false)(13 * 128, 128);
}

@system unittest
{
    import std.experimental.allocator.mallocator : Mallocator;

    enum blocks = 10000;
    int count = 0;

    ubyte[] payload = cast(ubyte[]) Mallocator.instance.allocate(blocks * 16);
    auto a = BitmappedBlock!(16, 16)(payload);
    void[][] buf = cast(void[][]) Mallocator.instance.allocate((void[]).sizeof * blocks);

    assert(!a.allocateFresh(0));
    assert(!a._control[0]);

    void[] b = a.allocate(256 * 16);
    assert(b.length == 256 * 16);
    count += 256;

    assert(!a._control[count]);
    b = a.allocateFresh(16);
    assert(b.length == 16);
    count++;
    assert(a._control[count - 1]);

    b = a.allocateFresh(16 * 300);
    assert(b.length == 16 * 300);
    count += 300;

    for (int i = 0; i < count; i++)
        assert(a._control[i]);
    assert(!a._control[count]);

    assert(a.expand(b, 313 * 16));
    count += 313;

    for (int i = 0; i < count; i++)
        assert(a._control[i]);
    assert(!a._control[count]);

    b = a.allocate(64 * 16);
    assert(b.length == 64 * 16);
    count += 64;

    b = a.allocateFresh(16);
    assert(b.length == 16);
    count++;

    for (int i = 0; i < count; i++)
        assert(a._control[i]);
    assert(!a._control[count]);

    assert(a.deallocateAll());
    for (int i = 0; i < a._blocks; i++)
        assert(!a._control[i]);

    b = a.allocateFresh(257 * 16);
    assert(b.length == 257 * 16);
    for (int i = 0; i < count; i++)
        assert(!a._control[i]);
    for (int i = count; i < count + 257; i++)
        assert(a._control[i]);
    count += 257;
    assert(!a._control[count]);

    while (true)
    {
        b = a.allocate(16);
        if (!b)
            break;
        assert(b.length == 16);
    }

    assert(!a.allocateFresh(16));
    assert(a.deallocateAll());

    assert(a.allocate(16).length == 16);
    assert(!a.allocateFresh(16));
}


@system unittest
{
    import std.experimental.allocator.mallocator : Mallocator;
    import std.random;

    static void testAlloc(Allocator)()
    {
        auto numBlocks = [1, 64, 256];
        enum blocks = 10000;
        int iter = 0;

        ubyte[] payload = cast(ubyte[]) Mallocator.instance.allocate(blocks * 16);
        auto a = Allocator(payload);
        void[][] buf = cast(void[][]) Mallocator.instance.allocate((void[]).sizeof * blocks);

        auto rnd = Random();
        while (iter < blocks)
        {
            int event = uniform(0, 2, rnd);
            int doExpand = uniform(0, 2, rnd);
            int allocSize = numBlocks[uniform(0, 3, rnd)] * 16;
            int expandSize = numBlocks[uniform(0, 3, rnd)] * 16;
            int doDeallocate = uniform(0, 2, rnd);

            if (event) buf[iter] = a.allocate(allocSize);
            else buf[iter] = a.allocateFresh(allocSize);

            if (!buf[iter])
                break;
            assert(buf[iter].length == allocSize);

            auto oldSize = buf[iter].length;
            if (doExpand && a.expand(buf[iter], expandSize))
                assert(buf[iter].length == expandSize + oldSize);

            if (doDeallocate)
            {
                assert(a.deallocate(buf[iter]));
                buf[iter] = null;
            }

            iter++;
        }

        while (iter < blocks)
        {
            buf[iter++] = a.allocate(16);
            if (!buf[iter - 1])
                break;
            assert(buf[iter - 1].length == 16);
        }

        for (size_t i = 0; i < a._blocks; i++)
            assert((cast(BitVector) a._control)[i]);

        assert(!a.allocate(16));
        for (size_t i = 0; i < iter; i++)
        {
            if (buf[i])
                assert(a.deallocate(buf[i]));
        }

        for (size_t i = 0; i < a._blocks; i++)
            assert(!(cast(BitVector) a._control)[i]);
    }

    testAlloc!(BitmappedBlock!(16, 16))();
    testAlloc!(SharedBitmappedBlock!(16, 16))();
}

// Test totalAllocation and goodAllocSize
nothrow @safe @nogc unittest
{
    BitmappedBlock!(8, 8, NullAllocator) h1;
    assert(h1.goodAllocSize(1) == 8);
    assert(h1.totalAllocation(1) >= 8);
    assert(h1.totalAllocation(64) >= 64);
    assert(h1.totalAllocation(8 * 64) >= 8 * 64);
    assert(h1.totalAllocation(8 * 63) >= 8 * 63);
    assert(h1.totalAllocation(8 * 64 + 1) >= 8 * 65);

    BitmappedBlock!(64, 8, NullAllocator) h2;
    assert(h2.goodAllocSize(1) == 64);
    assert(h2.totalAllocation(1) >= 64);
    assert(h2.totalAllocation(64 * 64) >= 64 * 64);

    BitmappedBlock!(4096, 4096, NullAllocator) h3;
    assert(h3.goodAllocSize(1) == 4096);
    assert(h3.totalAllocation(1) >= 4096);
    assert(h3.totalAllocation(64 * 4096) >= 64 * 4096);
    assert(h3.totalAllocation(64 * 4096 + 1) >= 65 * 4096);
}

// Test owns
@system unittest
{
    import std.experimental.allocator.gc_allocator : GCAllocator;
    import std.typecons : Ternary;

    auto a = BitmappedBlock!(64, 8, GCAllocator)(1024 * 64);
    const void[] buff = (() pure nothrow @safe @nogc => a.allocate(42))();

    assert((() nothrow @safe @nogc => a.owns(buff))() == Ternary.yes);
    assert((() nothrow @safe @nogc => a.owns(null))() == Ternary.no);
}

// BitmappedBlockWithInternalPointers
/**

A `BitmappedBlock` with additional structure for supporting `resolveInternalPointer`.
To that end, `BitmappedBlockWithInternalPointers` adds a
bitmap (one bit per block) that marks object starts. The bitmap itself has
variable size and is allocated together with regular allocations.

The time complexity of `resolveInternalPointer` is $(BIGOH k), where `k`
is the size of the object within which the internal pointer is looked up.

*/
struct BitmappedBlockWithInternalPointers(
    size_t theBlockSize, uint theAlignment = platformAlignment,
    ParentAllocator = NullAllocator)
{
    import std.conv : text;
    import std.typecons : Ternary;
    @system unittest
    {
        import std.experimental.allocator.mallocator : AlignedMallocator;
        auto m = cast(ubyte[])(AlignedMallocator.instance.alignedAllocate(1024 * 64,
            theAlignment));
        scope(exit) () nothrow @nogc { AlignedMallocator.instance.deallocate(m); }();
        testAllocator!(() => BitmappedBlockWithInternalPointers(m));
    }

    // state {
    private BitmappedBlock!(theBlockSize, theAlignment, NullAllocator) _heap;
    private BitVector _allocStart;
    // }

    /**
    Constructors accepting desired capacity or a preallocated buffer, similar
    in semantics to those of `BitmappedBlock`.
    */
    this(ubyte[] data)
    {
        _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)(data);
    }

    /// Ditto
    static if (!is(ParentAllocator == NullAllocator))
    this(size_t capacity)
    {
        // Add room for the _allocStart vector
        _heap = BitmappedBlock!(theBlockSize, theAlignment, ParentAllocator)
            (capacity + capacity.divideRoundUp(64));
    }

    // Makes sure there's enough room for _allocStart
    @safe
    private bool ensureRoomForAllocStart(size_t len)
    {
        if (_allocStart.length >= len) return true;
        // Must ensure there's room
        immutable oldLength = _allocStart.rep.length;
        immutable bits = len.roundUpToMultipleOf(64);
        void[] b = _allocStart.rep;
        if ((() @trusted => !_heap.reallocate(b, bits / 8))()) return false;
        assert(b.length * 8 == bits);
        _allocStart = BitVector((() @trusted => cast(ulong[]) b)());
        assert(_allocStart.rep.length * 64 == bits);
        _allocStart.rep[oldLength .. $] = ulong.max;
        return true;
    }

    /**
    Allocator primitives.
    */
    alias alignment = theAlignment;

    /// Ditto
    pure nothrow @safe @nogc
    size_t goodAllocSize(size_t n)
    {
        return n.roundUpToMultipleOf(_heap.blockSize);
    }

    /// Ditto
    void[] allocate(size_t bytes)
    {
        auto r = _heap.allocate(bytes);
        if (!r.ptr) return r;
        immutable block = (() @trusted => (r.ptr - _heap._payload.ptr) / _heap.blockSize)();
        immutable blocks =
            (r.length + _heap.blockSize - 1) / _heap.blockSize;
        if (!ensureRoomForAllocStart(block + blocks))
        {
            // Failed, free r and bailout
            () @trusted { _heap.deallocate(r); r = null; }();
            return null;
        }
        assert(block < _allocStart.length);
        assert(block + blocks <= _allocStart.length);
        // Mark the _allocStart bits
        assert(blocks > 0);
        _allocStart[block] = 1;
        _allocStart[block + 1 .. block + blocks] = 0;
        assert(block + blocks == _allocStart.length
            || _allocStart[block + blocks] == 1);
        return r;
    }

    /// Ditto
    void[] allocateAll()
    {
        auto r = _heap.allocateAll();
        if (!r.ptr) return r;
        // Carve space at the end for _allocStart
        auto p = alignDownTo(r.ptr + r.length - 8, ulong.alignof);
        r = r[0 .. p - r.ptr];
        // Initialize _allocStart
        _allocStart = BitVector(cast(ulong[]) p[0 .. 8]);
        _allocStart[] = 0;
        immutable block = (r.ptr - _heap._payload.ptr) / _heap.blockSize;
        assert(block < _allocStart.length);
        _allocStart[block] = 1;
        return r;
    }

    /// Ditto
    bool expand(ref void[] b, size_t bytes)
    {
        if (!bytes) return true;
        if (b is null) return false;
        immutable oldBlocks =
            (b.length + _heap.blockSize - 1) / _heap.blockSize;
        assert(oldBlocks);
        immutable newBlocks =
            (b.length + bytes + _heap.blockSize - 1) / _heap.blockSize;
        assert(newBlocks >= oldBlocks);
        immutable block = (() @trusted => (b.ptr - _heap._payload.ptr) / _heap.blockSize)();
        assert(_allocStart[block]);
        if (!ensureRoomForAllocStart(block + newBlocks)
                || !_heap.expand(b, bytes))
        {
            return false;
        }
        // Zero only the expanded bits
        _allocStart[block + oldBlocks .. block + newBlocks] = 0;
        assert(_allocStart[block]);
        return true;
    }

    /// Ditto
    bool deallocate(void[] b)
    {
        // No need to touch _allocStart here - except for the first bit, it's
        // meaningless in freed memory. The first bit is already 1.
        return _heap.deallocate(b);
        // TODO: one smart thing to do is reduce memory occupied by
        // _allocStart if we're freeing the rightmost block.
    }

    /// Ditto
    nothrow @safe @nogc
    Ternary resolveInternalPointer(const void* p, ref void[] result)
    {
        if ((() @trusted => _heap._payload
                    && (p < &_heap._payload[0]
                        || p >= &_heap._payload[0] + _heap._payload.length))())
        {
            return Ternary.no;
        }
        // Find block start
        auto block = (() @trusted => (p - &_heap._payload[0]) / _heap.blockSize)();
        if (block >= _allocStart.length) return Ternary.no;
        // Within an allocation, must find the 1 just to the left of it
        auto i = _allocStart.find1Backward(block);
        if (i == i.max) return Ternary.no;
        auto j = _allocStart.find1(i + 1);
        result = (() @trusted => _heap._payload.ptr[cast(size_t) (_heap.blockSize * i)
                                                    .. cast(size_t) (_heap.blockSize * j)])();
        return Ternary.yes;
    }

    /// Ditto
    Ternary empty()
    {
        return _heap.empty;
    }

    // Currently unused
    private void markAllAsUnused()
    {
        // Mark all deallocated memory with 1 so we minimize damage created by
        // false pointers. TODO: improve speed.
        foreach (i, ref e; _allocStart.rep)
        {
            // Set to 1 all bits in _allocStart[i] that were 0 in control, and
            // leave the others unchanged.
            // (0, 0) => 1; (0, 1) => 0; (1, 0) => 1; (1, 1) => 1
            e |= ~_heap._control.rep[i];
        }
        // Now zero all control bits
        _heap._control[] = 0;
        // EXCEPT for the _allocStart block itself
        markAsUsed(_allocStart.rep);
    }

    // Currently unused
    private bool markAsUsed(void[] b)
    {
        // Locate position
        immutable pos = b.ptr - _heap._payload.ptr;
        assert(pos % _heap.blockSize == 0);
        auto blockIdx = pos / _heap.blockSize;
        if (_heap._control[blockIdx]) return false;
        // Round up size to multiple of block size
        auto blocks = b.length.divideRoundUp(_heap.blockSize);
        _heap._control[blockIdx .. blockIdx + blocks] = 1;
        return true;
    }

    // Currently unused
    private void doneMarking()
    {
        // Nothing to do, what's free stays free.
    }
}

@system unittest
{
    import std.typecons : Ternary;

    auto h = BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]);
    assert((() nothrow @safe @nogc => h.empty)() == Ternary.yes);
    auto b = (() pure nothrow @safe @nogc => h.allocate(123))();
    assert(b.length == 123);
    assert((() nothrow @safe @nogc => h.empty)() == Ternary.no);

    void[] p;
    void* offset = &b[0] + 17;
    assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes);
    assert(p.ptr is b.ptr);
    assert(p.length >= b.length);
    b = (() pure nothrow @safe @nogc => h.allocate(4096))();

    offset = &b[0];
    assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes);
    assert(p is b);

    offset = &b[0] + 11;
    assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes);
    assert(p is b);

    void[] unchanged = p;
    offset = &b[0] - 40_970;
    assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.no);
    assert(p is unchanged);

    assert((() @safe => h.expand(b, 1))());
    assert(b.length == 4097);
    offset = &b[0] + 4096;
    assert((() nothrow @safe @nogc => h.resolveInternalPointer(offset, p))() == Ternary.yes);
    assert(p.ptr is b.ptr);

    // Ensure deallocate inherits from parent
    () nothrow @nogc { h.deallocate(b); }();
}

@system unittest
{
    auto h = BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]);
    assert((() pure nothrow @safe @nogc => h.goodAllocSize(1))() == 4096);
}

/**
Returns the number of most significant ones before a zero can be found in `x`.
If `x` contains no zeros (i.e. is equal to `ulong.max`), returns 64.
*/
pure nothrow @safe @nogc
private uint leadingOnes(ulong x)
{
    uint result = 0;
    while (cast(long) x < 0)
    {
        ++result;
        x <<= 1;
    }
    return result;
}

@safe unittest
{
    assert(leadingOnes(0) == 0);
    assert(leadingOnes(~0UL) == 64);
    assert(leadingOnes(0xF000_0000_0000_0000) == 4);
    assert(leadingOnes(0xE400_0000_0000_0000) == 3);
    assert(leadingOnes(0xC700_0200_0000_0000) == 2);
    assert(leadingOnes(0x8000_0030_0000_0000) == 1);
    assert(leadingOnes(0x2000_0000_0000_0000) == 0);
}

/**
Finds a run of contiguous ones in `x` of length at least `n`.
*/
pure nothrow @safe @nogc
private uint findContigOnes(ulong x, uint n)
{
    while (n > 1)
    {
        immutable s = n >> 1;
        x &= x << s;
        n -= s;
    }
    return leadingOnes(~x);
}

@safe unittest
{
    assert(findContigOnes(0x0000_0000_0000_0300, 2) == 54);

    assert(findContigOnes(~0UL, 1) == 0);
    assert(findContigOnes(~0UL, 2) == 0);
    assert(findContigOnes(~0UL, 32) == 0);
    assert(findContigOnes(~0UL, 64) == 0);
    assert(findContigOnes(0UL, 1) == 64);

    assert(findContigOnes(0x4000_0000_0000_0000, 1) == 1);
    assert(findContigOnes(0x0000_0F00_0000_0000, 4) == 20);
}

/*
Unconditionally sets the bits from lsb through msb in w to zero.
*/
pure nothrow @safe @nogc
private void setBits(ref ulong w, uint lsb, uint msb)
{
    assert(lsb <= msb && msb < 64);
    const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
    w |= mask;
}

@safe unittest
{
    ulong w;
    w = 0; setBits(w, 0, 63); assert(w == ulong.max);
    w = 0; setBits(w, 1, 63); assert(w == ulong.max - 1);
    w = 6; setBits(w, 0, 1); assert(w == 7);
    w = 6; setBits(w, 3, 3); assert(w == 14);
}

/* Are bits from lsb through msb in w zero? If so, make then 1
and return the resulting w. Otherwise, just return 0.
*/
pure nothrow @safe @nogc
private bool setBitsIfZero(ref ulong w, uint lsb, uint msb)
{
    assert(lsb <= msb && msb < 64);
    const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
    if (w & mask) return false;
    w |= mask;
    return true;
}

// Assigns bits in w from lsb through msb to zero.
pure nothrow @safe @nogc
private void resetBits(ref ulong w, uint lsb, uint msb)
{
    assert(lsb <= msb && msb < 64);
    const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
    w &= ~mask;
}

/*
Bit disposition is MSB=0 (leftmost, big endian).
*/
private struct BitVector
{
    ulong[] _rep;

    auto rep(this _)() { return _rep; }

    pure nothrow @safe @nogc
    this(ulong[] data) { _rep = data; }

    pure nothrow @safe @nogc
    void opSliceAssign(bool b) { _rep[] = b ? ulong.max : 0; }

    pure nothrow @safe @nogc
    void opSliceAssign(bool b, ulong x, ulong y)
    {
        assert(x <= y && y <= _rep.length * 64);
        if (x == y) return;
        --y;
        assert(x / 64 <= size_t.max);
        immutable i1 = cast(size_t) (x / 64);
        immutable uint b1 = 63 - x % 64;
        assert(y / 64 <= size_t.max);
        immutable i2 = cast(size_t) (y / 64);
        immutable uint b2 = 63 - y % 64;
        assert(i1 <= i2 && i2 < _rep.length);
        if (i1 == i2)
        {
            // Inside the same word
            assert(b1 >= b2);
            if (b) setBits(_rep[i1], b2, b1);
            else resetBits(_rep[i1], b2, b1);
        }
        else
        {
            // Spans multiple words
            assert(i1 < i2);
            if (b) setBits(_rep[i1], 0, b1);
            else resetBits(_rep[i1], 0, b1);
            _rep[i1 + 1 .. i2] = (b ? ulong.max : 0);
            if (b) setBits(_rep[i2], b2, 63);
            else resetBits(_rep[i2], b2, 63);
        }
    }

    pure nothrow @safe @nogc
    bool opIndex(ulong x)
    {
        assert(x < length);
        return (_rep[cast(size_t) (x / 64)]
            & (0x8000_0000_0000_0000UL >> (x % 64))) != 0;
    }

    pure nothrow @safe @nogc
    void opIndexAssign(bool b, ulong x)
    {
        assert(x / 64 <= size_t.max);
        immutable i = cast(size_t) (x / 64);
        immutable j = 0x8000_0000_0000_0000UL >> (x % 64);
        if (b) _rep[i] |= j;
        else _rep[i] &= ~j;
    }

    pure nothrow @safe @nogc
    ulong length() const
    {
        return _rep.length * 64;
    }

    /* Returns the index of the first 1 to the right of i (including i itself),
    or length if not found.
    */
    pure nothrow @safe @nogc
    ulong find1(ulong i)
    {
        assert(i < length);
        assert(i / 64 <= size_t.max);
        auto w = cast(size_t) (i / 64);
        immutable b = i % 64; // 0 through 63, 0 when i == 0
        immutable mask = ulong.max >> b;
        if (auto current = _rep[w] & mask)
        {
            // Great, found
            return w * 64 + leadingOnes(~current);
        }
        // The current word doesn't have the solution, find the leftmost 1
        // going to the right.
        for (++w; w < _rep.length; ++w)
        {
            if (auto current = _rep[w])
            {
                return w * 64 + leadingOnes(~current);
            }
        }
        return length;
    }

    /* Returns the index of the first 1 to the left of i (including i itself),
    or ulong.max if not found.
    */
    pure nothrow @safe @nogc
    ulong find1Backward(ulong i)
    {
        assert(i < length);
        auto w = cast(size_t) (i / 64);
        immutable b = 63 - (i % 64); // 0 through 63, 63 when i == 0
        immutable mask = ~((1UL << b) - 1);
        assert(mask != 0);
        // First, let's see if the current word has a bit larger than ours.
        if (auto currentWord = _rep[w] & mask)
        {
            // Great, this word contains the result.
            return w * 64 + 63 - currentWord.trailingZeros;
        }
        // The current word doesn't have the solution, find the rightmost 1
        // going to the left.
        while (w >= 1)
        {
            --w;
            if (auto currentWord = _rep[w])
                return w * 64 + (63 - currentWord.trailingZeros);
        }
        return ulong.max;
    }

    /// Are all bits zero?
    pure nothrow @safe @nogc
    bool allAre0() const
    {
        foreach (w; _rep) if (w) return false;
        return true;
    }

    /// Are all bits one?
    pure nothrow @safe @nogc
    bool allAre1() const
    {
        foreach (w; _rep) if (w != ulong.max) return false;
        return true;
    }

    pure nothrow @safe @nogc
    ulong findZeros(immutable size_t howMany, ulong start)
    {
        assert(start < length);
        assert(howMany > 64);
        auto i = cast(size_t) (start / 64);
        while (_rep[i] & 1)
        {
            // No trailing zeros in this word, try the next one
            if (++i == _rep.length) return ulong.max;
            start = i * 64;
        }
        // Adjust start to have only trailing zeros after it
        auto prefixLength = 64;
        while (_rep[i] & (ulong.max >> (64 - prefixLength)))
        {
            assert(prefixLength > 0);
            --prefixLength;
            ++start;
        }

        assert(howMany > prefixLength);
        auto needed = howMany - prefixLength;
        for (++i; needed >= 64; needed -= 64, ++i)
        {
            if (i >= _rep.length) return ulong.max;
            if (_rep[i] != 0) return findZeros(howMany, i * 64);
        }
        // Leftover < 64 bits
        assert(needed < 64);
        if (!needed) return start;
        if (i >= _rep.length) return ulong.max;
        if (leadingOnes(~_rep[i]) >= needed) return start;
        return findZeros(howMany, i * 64);
    }
}

@safe unittest
{
    auto v = BitVector(new ulong[10]);
    assert(v.length == 640);

    v[] = 0;
    v[53] = 1;
    assert(v[52] == 0);
    assert(v[53] == 1);
    assert(v[54] == 0);

    v[] = 0;
    v[53 .. 55] = 1;
    assert(v[52] == 0);
    assert(v[53] == 1);
    assert(v[54] == 1);
    assert(v[55] == 0);

    v[] = 0;
    v[2 .. 65] = 1;
    assert(v.rep[0] == 0x3FFF_FFFF_FFFF_FFFF);
    assert(v.rep[1] == 0x8000_0000_0000_0000);
    assert(v.rep[2] == 0);

    v[] = 0;
    assert(v.find1Backward(0) == ulong.max);
    assert(v.find1Backward(43) == ulong.max);
    assert(v.find1Backward(83) == ulong.max);

    v[0] = 1;
    assert(v.find1Backward(0) == 0);
    assert(v.find1Backward(43) == 0);
    import std.conv : text;
    assert(v.find1Backward(83) == 0, text(v.find1Backward(83)));

    v[0] = 0;
    v[101] = 1;
    assert(v.find1Backward(0) == ulong.max);
    assert(v.find1Backward(43) == ulong.max);
    assert(v.find1Backward(83) == ulong.max);
    assert(v.find1Backward(100) == ulong.max);
    assert(v.find1Backward(101) == 101);
    assert(v.find1Backward(553) == 101);

    v[0 .. v.length] = 0;
    v[v.length .. v.length] = 0;
    v[0 .. 0] = 0;

    v[] = 0;
    assert(v.find1(0) == v.length);
    v[139] = 1;
    assert(v.find1(0) == 139);
    assert(v.find1(100) == 139);
    assert(v.find1(138) == 139);
    assert(v.find1(139) == 139);
    assert(v.find1(140) == v.length);

    v[] = 0;
    assert(v.findZeros(100, 0) == 0);
    foreach (i; 0 .. 500)
        assert(v.findZeros(100, i) == i, text(v.findZeros(100, i), " != ", i));
    assert(v.findZeros(540, 99) == 99);
    assert(v.findZeros(99, 540) == 540);
    assert(v.findZeros(540, 100) == 100);
    assert(v.findZeros(640, 0) == 0);
    assert(v.findZeros(641, 1) == ulong.max);
    assert(v.findZeros(641, 100) == ulong.max);
}
