| /// |
| module std.experimental.allocator.building_blocks.bitmapped_block; |
| |
| import std.experimental.allocator.building_blocks.null_allocator; |
| import std.experimental.allocator.common; |
| |
| /** |
| |
| $(D BitmappedBlock) implements a simple heap consisting of one contiguous area |
| of memory organized in blocks, each of size $(D 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 $(D NullAllocator) as $(D ParentAllocator) (the default) means user code |
| manages allocation of the memory block from the outside; in that case |
| $(D BitmappedBlock) must be constructed with a $(D void[]) preallocated block and |
| has no responsibility regarding the lifetime of its support underlying storage. |
| If another allocator type is passed, $(D BitmappedBlock) defines a destructor that |
| uses the parent allocator to release the memory block. That makes the combination of $(D AllocatorList), $(D BitmappedBlock), and a back-end allocator such as $(D 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 $(D 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 $(D BitmappedBlock) does |
| not need to maintain the allocated size, freeing memory implicitly coalesces |
| free blocks together. Also, tuning $(D blockSize) has a considerable impact on |
| both internal and external fragmentation. |
| |
| 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 $(D |
| blockSize) parameter as in $(D BitmappedBlock!(Allocator, 4096)). To choose a block |
| size parameter, use $(D BitmappedBlock!(Allocator, chooseAtRuntime)) and pass the |
| block size to the constructor. |
| |
| */ |
| struct BitmappedBlock(size_t theBlockSize, uint theAlignment = platformAlignment, |
| ParentAllocator = NullAllocator) |
| { |
| import std.conv : text; |
| import std.traits : hasMember; |
| import std.typecons : Ternary; |
| import std.typecons : tuple, Tuple; |
| |
| @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) AlignedMallocator.instance.deallocate(m); |
| testAllocator!(() => BitmappedBlock(m)); |
| } |
| static assert(theBlockSize > 0 && theAlignment.isGoodStaticAlignment); |
| static assert(theBlockSize == chooseAtRuntime |
| || theBlockSize % theAlignment == 0, |
| "Block size must be a multiple of the alignment"); |
| |
| /** |
| If $(D blockSize == chooseAtRuntime), $(D BitmappedBlock) offers a read/write |
| property $(D blockSize). It must be set before any use of the allocator. |
| Otherwise (i.e. $(D theBlockSize) is a legit constant), $(D blockSize) is |
| an alias for $(D theBlockSize). Whether constant or variable, must also be |
| a multiple of $(D alignment). This constraint is $(D assert)ed statically |
| and dynamically. |
| */ |
| static if (theBlockSize != chooseAtRuntime) |
| { |
| alias blockSize = theBlockSize; |
| } |
| else |
| { |
| @property uint blockSize() { return _blockSize; } |
| @property void blockSize(uint s) |
| { |
| assert(!_control && 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); |
| } |
| |
| /** |
| The _alignment offered is user-configurable statically through parameter |
| $(D theAlignment), defaulted to $(D platformAlignment). |
| */ |
| alias alignment = theAlignment; |
| |
| // state { |
| /** |
| The _parent allocator. Depending on whether $(D ParentAllocator) holds state |
| or not, this is a member variable or an alias for |
| `ParentAllocator.instance`. |
| */ |
| static if (stateSize!ParentAllocator) |
| { |
| ParentAllocator parent; |
| } |
| else |
| { |
| alias parent = ParentAllocator.instance; |
| } |
| private uint _blocks; |
| private BitVector _control; |
| private void[] _payload; |
| private size_t _startIdx; |
| // } |
| |
| 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; |
| //writeln(maxSlack); |
| return leadingUlongs * 8 + maxSlack + blockSize * blocks; |
| } |
| |
| /** |
| Constructs a block allocator given a hunk of memory, or a desired capacity |
| in bytes. |
| |
| $(UL |
| $(LI If $(D ParentAllocator) is $(D NullAllocator), only the constructor |
| taking $(D data) is defined and the user is responsible for freeing $(D |
| data) if desired.) |
| $(LI Otherwise, both constructors are defined. The $(D data)-based |
| constructor assumes memory has been allocated with the parent allocator. |
| The $(D capacity)-based constructor uses $(D ParentAllocator) to allocate |
| an appropriate contiguous hunk of memory. Regardless of the constructor |
| used, the destructor releases the memory by using $(D |
| ParentAllocator.deallocate).) |
| ) |
| */ |
| 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; |
| // Get a first estimate |
| import std.conv : to; |
| _blocks = to!uint(totalBits / bitsPerBlock); |
| |
| // Reality is a bit more complicated, iterate until a good number of |
| // blocks found. |
| for (; _blocks; --_blocks) |
| { |
| immutable controlWords = _blocks.divideRoundUp(64); |
| auto payload = data[controlWords * 8 .. $].roundStartToMultipleOf( |
| alignment); |
| if (payload.length < _blocks * blockSize) |
| { |
| // Overestimated |
| continue; |
| } |
| _control = BitVector((cast(ulong*) data.ptr)[0 .. controlWords]); |
| _control[] = 0; |
| _payload = payload; |
| break; |
| } |
| } |
| |
| /// Ditto |
| 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); |
| } |
| |
| /** |
| If $(D ParentAllocator) is not $(D NullAllocator) and defines $(D |
| deallocate), the destructor is defined to deallocate the block held. |
| */ |
| static if (!is(ParentAllocator == NullAllocator) |
| && hasMember!(ParentAllocator, "deallocate")) |
| ~this() |
| { |
| auto start = _control.rep.ptr, end = _payload.ptr + _payload.length; |
| parent.deallocate(start[0 .. end - start]); |
| } |
| |
| /* |
| 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) |
| { |
| ++_startIdx; |
| } |
| } |
| |
| /* |
| Returns the blocks corresponding to the control bits starting at word index |
| wordIdx and bit index msbIdx (MSB=0) for a total of howManyBlocks. |
| */ |
| private void[] blocksFor(size_t wordIdx, uint msbIdx, size_t howManyBlocks) |
| { |
| assert(msbIdx <= 63); |
| const start = (wordIdx * 64 + msbIdx) * blockSize; |
| const end = start + blockSize * howManyBlocks; |
| if (end <= _payload.length) return _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; |
| } |
| |
| /** |
| Returns the actual bytes allocated when $(D n) bytes are requested, i.e. |
| $(D n.roundUpToMultipleOf(blockSize)). |
| */ |
| size_t goodAllocSize(size_t n) |
| { |
| return n.roundUpToMultipleOf(blockSize); |
| } |
| |
| /** |
| Allocates $(D s) bytes of memory and returns it, or $(D 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 $(D ulong)s in the metadata. |
| Allocations greater than 64 blocks require a multiword search through the |
| metadata. |
| */ |
| @trusted void[] allocate(const size_t s) |
| { |
| const blocks = s.divideRoundUp(blockSize); |
| void[] result = void; |
| |
| switcharoo: |
| 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); |
| assert((_control.rep[i] & ((1UL << 63) >> j)) == 0); |
| _control.rep[i] |= (1UL << 63) >> j; |
| if (i == _startIdx) |
| { |
| adjustStartIdx(); |
| } |
| result = blocksFor(i, j, 1); |
| break switcharoo; |
| } |
| goto case 0; // fall through |
| case 0: |
| return null; |
| case 2: .. case 64: |
| result = smallAlloc(cast(uint) blocks); |
| break; |
| default: |
| result = hugeAlloc(blocks); |
| break; |
| } |
| return result.ptr ? result.ptr[0 .. s] : null; |
| } |
| |
| /** |
| Allocates a block with specified alignment $(D a). The alignment must be a |
| power of 2. If $(D a <= alignment), function forwards to $(D 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) |
| { |
| 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 = allocate((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) |
| { |
| deallocate(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) |
| { |
| deallocate(b[$ - slackTrailingBlocks * blockSize .. $]); |
| } |
| |
| return result; |
| } |
| |
| /** |
| If the $(D BitmappedBlock) object is empty (has no active allocation), allocates |
| all memory within and returns a slice to it. Otherwise, returns $(D null) |
| (i.e. no attempt is made to allocate the largest available block). |
| */ |
| void[] allocateAll() |
| { |
| if (empty != Ternary.yes) return null; |
| _control[] = 1; |
| return _payload; |
| } |
| |
| /** |
| 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.) |
| */ |
| Ternary owns(void[] b) const |
| { |
| //if (!b.ptr) return Ternary.no; |
| assert(b.ptr !is null || b.length == 0, "Corrupt block."); |
| return Ternary(b.ptr >= _payload.ptr |
| && b.ptr + b.length <= _payload.ptr + _payload.length); |
| } |
| |
| /* |
| 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); |
| if (msbIdx + blocks <= 64) |
| { |
| // Allocation should fit this control word |
| if (setBitsIfZero(_control.rep[wordIdx], |
| cast(uint) (64 - msbIdx - blocks), 63 - msbIdx)) |
| { |
| // Success |
| result = blocksFor(wordIdx, msbIdx, blocks); |
| 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) |
| { |
| // We did it! |
| _control.rep[wordIdx] |= mask; |
| result = blocksFor(wordIdx, msbIdx, blocks); |
| 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]); |
| _control.rep[wordIdx] |= ulong.max >> available; |
| return available; |
| } |
| |
| private void[] smallAlloc(uint blocks) |
| { |
| assert(blocks >= 2 && blocks <= 64, text(blocks)); |
| 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 |
| setBits(_control.rep[i], 64 - j - blocks, 63 - j); |
| return blocksFor(i, j, blocks); |
| } |
| // 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); |
| if (allocateAtFront(i + 1, needed)) |
| { |
| // yay, found a block crossing two words |
| _control.rep[i] |= (1UL << available) - 1; |
| return blocksFor(i, 64 - available, blocks); |
| } |
| } |
| return null; |
| } |
| |
| private void[] hugeAlloc(size_t blocks) |
| { |
| assert(blocks > 64); |
| if (_startIdx == _control._rep.length) |
| { |
| assert(_control.allAre1); |
| return null; |
| } |
| auto i = _control.findZeros(blocks, _startIdx * 64); |
| if (i == i.max) return null; |
| // Allocate those bits |
| _control[i .. i + blocks] = 1; |
| return _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; |
| // yay, works |
| _control.rep[wordIdx] |= ~mask; |
| return true; |
| } |
| |
| /** |
| Expands an allocated block in place. |
| */ |
| @trusted bool expand(ref void[] b, immutable size_t delta) |
| { |
| // Dispose with trivial corner cases |
| if (delta == 0) return true; |
| if (b is null) return false; |
| |
| /* 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, |
| text(p.ptr, " != ", b.ptr + blocksOld * blockSize)); |
| b = b.ptr[0 .. b.length + delta]; |
| return true; |
| } |
| |
| /** |
| Reallocates a previously-allocated block. Contractions occur in place. |
| */ |
| @system bool reallocate(ref void[] b, size_t newSize) |
| { |
| if (!b.ptr) |
| { |
| b = allocate(newSize); |
| return b.length == newSize; |
| } |
| if (newSize == 0) |
| { |
| deallocate(b); |
| b = null; |
| return true; |
| } |
| if (newSize < b.length) |
| { |
| // Shrink. Will shrink in place by deallocating the trailing part. |
| auto newCapacity = bytes2blocks(newSize) * blockSize; |
| deallocate(b[newCapacity .. $]); |
| b = b[0 .. newSize]; |
| return true; |
| } |
| // Go the slow route |
| return .reallocate(this, b, newSize); |
| } |
| |
| /** |
| Reallocates a block previously allocated with $(D alignedAllocate). Contractions do not occur in place. |
| */ |
| @system bool alignedReallocate(ref void[] b, size_t newSize, uint a) |
| { |
| if (newSize == 0) |
| { |
| deallocate(b); |
| b = null; |
| return true; |
| } |
| // Go the slow route |
| return .alignedReallocate(this, b, newSize, a); |
| } |
| |
| /** |
| Deallocates a block previously allocated with this allocator. |
| */ |
| bool deallocate(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 |
| auto begin = _payload.ptr + blockIdx * blockSize, |
| end = 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) |
| { |
| resetBits(_control.rep[wordIdx], |
| cast(uint) (64 - msbIdx - blocks), |
| 63 - msbIdx); |
| return true; |
| } |
| 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) |
| { |
| _control.rep[wordIdx] &= ulong.max >> blocks; |
| } |
| return true; |
| } |
| |
| /** |
| Forcibly deallocates all memory allocated by this allocator, making it |
| available for further allocations. Does not return memory to $(D |
| ParentAllocator). |
| */ |
| bool deallocateAll() |
| { |
| _control[] = 0; |
| _startIdx = 0; |
| return true; |
| } |
| |
| /** |
| Returns `Ternary.yes` if no memory is currently allocated with this |
| allocator, otherwise `Ternary.no`. This method never returns |
| `Ternary.unknown`. |
| */ |
| Ternary empty() |
| { |
| return Ternary(_control.allAre0()); |
| } |
| |
| void dump() |
| { |
| import std.stdio : writefln, writeln; |
| writefln("%s @ %s {", typeid(this), cast(void*) _control._rep.ptr); |
| scope(exit) writeln("}"); |
| assert(_payload.length == blockSize * _blocks); |
| assert(_control.length >= _blocks); |
| writefln(" _startIdx=%s; blockSize=%s; blocks=%s", |
| _startIdx, blockSize, _blocks); |
| if (!_control.length) return; |
| uint blockCount = 1; |
| bool inAllocatedStore = _control[0]; |
| void* start = _payload.ptr; |
| for (size_t i = 1;; ++i) |
| { |
| if (i >= _blocks || _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 < _control.length); |
| inAllocatedStore = _control[i]; |
| start = _payload.ptr + blockCount * blockSize; |
| blockCount = 1; |
| } |
| else |
| { |
| ++blockCount; |
| } |
| } |
| } |
| } |
| |
| /// |
| @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.gc_allocator : GCAllocator; |
| testAllocator!(() => BitmappedBlock!(64, 8, GCAllocator)(1024 * 64)); |
| } |
| |
| @system unittest |
| { |
| static void testAllocateAll(size_t bs)(uint blocks, uint blocksAtATime) |
| { |
| import std.algorithm.comparison : min; |
| assert(bs); |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| auto a = BitmappedBlock!(bs, min(bs, platformAlignment))( |
| 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 = a.allocate(0); |
| assert(x is null); |
| // test allocation of 1 byte |
| x = a.allocate(1); |
| assert(x.length == 1 || blocks == 0, |
| text(x.ptr, " ", x.length, " ", a)); |
| a.deallocateAll(); |
| |
| bool twice = true; |
| |
| begin: |
| foreach (i; 0 .. blocks / blocksAtATime) |
| { |
| auto b = a.allocate(bs * blocksAtATime); |
| assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); |
| } |
| assert(a.allocate(bs * blocksAtATime) is null); |
| assert(a.allocate(1) is null); |
| |
| // Now deallocate all and do it again! |
| a.deallocateAll(); |
| |
| // Test deallocation |
| |
| auto v = new void[][blocks / blocksAtATime]; |
| foreach (i; 0 .. blocks / blocksAtATime) |
| { |
| auto b = a.allocate(bs * blocksAtATime); |
| assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); |
| v[i] = b; |
| } |
| assert(a.allocate(bs * blocksAtATime) is null); |
| assert(a.allocate(1) is null); |
| |
| foreach (i; 0 .. blocks / blocksAtATime) |
| { |
| a.deallocate(v[i]); |
| } |
| |
| foreach (i; 0 .. blocks / blocksAtATime) |
| { |
| auto b = a.allocate(bs * blocksAtATime); |
| assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); |
| v[i] = b; |
| } |
| |
| foreach (i; 0 .. v.length) |
| { |
| a.deallocate(v[i]); |
| } |
| |
| if (twice) |
| { |
| twice = false; |
| goto begin; |
| } |
| |
| a.deallocateAll; |
| |
| // test expansion |
| if (blocks >= blocksAtATime) |
| { |
| foreach (i; 0 .. blocks / blocksAtATime - 1) |
| { |
| auto b = a.allocate(bs * blocksAtATime); |
| assert(b.length == bs * blocksAtATime, text(i, ": ", b.length)); |
| (cast(ubyte[]) b)[] = 0xff; |
| a.expand(b, blocksAtATime * bs) |
| || assert(0, 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)(8, 1); |
| testAllocateAll!(4096)(128, 1); |
| |
| testAllocateAll!(1)(0, 2); |
| testAllocateAll!(1)(128, 2); |
| testAllocateAll!(4096)(128, 2); |
| |
| testAllocateAll!(1)(0, 4); |
| testAllocateAll!(1)(128, 4); |
| testAllocateAll!(4096)(128, 4); |
| |
| testAllocateAll!(1)(0, 3); |
| testAllocateAll!(1)(24, 3); |
| testAllocateAll!(3008)(100, 1); |
| testAllocateAll!(3008)(100, 3); |
| |
| testAllocateAll!(1)(0, 128); |
| testAllocateAll!(1)(128 * 1, 128); |
| testAllocateAll!(128 * 20)(13 * 128, 128); |
| } |
| |
| // Test totalAllocation |
| @safe unittest |
| { |
| BitmappedBlock!(8, 8, NullAllocator) h1; |
| 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.totalAllocation(1) >= 64); |
| assert(h2.totalAllocation(64 * 64) >= 64 * 64); |
| |
| BitmappedBlock!(4096, 4096, NullAllocator) h3; |
| assert(h3.totalAllocation(1) >= 4096); |
| assert(h3.totalAllocation(64 * 4096) >= 64 * 4096); |
| assert(h3.totalAllocation(64 * 4096 + 1) >= 65 * 4096); |
| } |
| |
| // BitmappedBlockWithInternalPointers |
| /** |
| |
| A $(D BitmappedBlock) with additional structure for supporting $(D |
| resolveInternalPointer). To that end, $(D 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 $(D resolveInternalPointer) is $(BIGOH k), where $(D 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) 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 $(D 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 |
| 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 (!_heap.reallocate(b, bits / 8)) return false; |
| assert(b.length * 8 == bits, text(b.length * 8, " != ", bits)); |
| _allocStart = BitVector(cast(ulong[]) b); |
| assert(_allocStart.rep.length * 64 == bits); |
| _allocStart.rep[oldLength .. $] = ulong.max; |
| return true; |
| } |
| |
| /** |
| Allocator primitives. |
| */ |
| alias alignment = theAlignment; |
| |
| /// Ditto |
| 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 = (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 |
| _heap.deallocate(r); |
| 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 = (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 |
| Ternary resolveInternalPointer(const void* p, ref void[] result) |
| { |
| if (p < _heap._payload.ptr |
| || p >= _heap._payload.ptr + _heap._payload.length) |
| { |
| return Ternary.no; |
| } |
| // Find block start |
| auto block = (p - _heap._payload.ptr) / _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 = _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]); |
| auto b = h.allocate(123); |
| assert(b.length == 123); |
| |
| void[] p; |
| Ternary r = h.resolveInternalPointer(b.ptr + 17, p); |
| assert(p.ptr is b.ptr); |
| assert(p.length >= b.length); |
| b = h.allocate(4096); |
| |
| h.resolveInternalPointer(b.ptr, p); |
| assert(p is b); |
| |
| h.resolveInternalPointer(b.ptr + 11, p); |
| assert(p is b); |
| |
| void[] unchanged = p; |
| h.resolveInternalPointer(b.ptr - 40_970, p); |
| assert(p is unchanged); |
| |
| assert(h.expand(b, 1)); |
| assert(b.length == 4097); |
| h.resolveInternalPointer(b.ptr + 4096, p); |
| assert(p.ptr is b.ptr); |
| } |
| |
| /** |
| Returns the number of most significant ones before a zero can be found in $(D |
| x). If $(D x) contains no zeros (i.e. is equal to $(D ulong.max)), returns 64. |
| */ |
| private uint leadingOnes(ulong x) |
| { |
| uint result = 0; |
| while (cast(long) x < 0) |
| { |
| ++result; |
| x <<= 1; |
| } |
| return result; |
| } |
| |
| @system 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 $(D x) of length at least $(D n). |
| */ |
| private uint findContigOnes(ulong x, uint n) |
| { |
| while (n > 1) |
| { |
| immutable s = n >> 1; |
| x &= x << s; |
| n -= s; |
| } |
| return leadingOnes(~x); |
| } |
| |
| @system 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. |
| */ |
| 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; |
| } |
| |
| @system 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. |
| */ |
| 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. |
| 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() { return _rep; } |
| |
| this(ulong[] data) { _rep = data; } |
| |
| void opSliceAssign(bool b) { _rep[] = b ? ulong.max : 0; } |
| |
| 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; |
| if (b) setBits(_rep[i2], b2, 63); |
| else resetBits(_rep[i2], b2, 63); |
| } |
| } |
| |
| bool opIndex(ulong x) |
| { |
| assert(x < length); |
| return (_rep[cast(size_t) (x / 64)] |
| & (0x8000_0000_0000_0000UL >> (x % 64))) != 0; |
| } |
| |
| 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; |
| } |
| |
| 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. |
| */ |
| 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. |
| */ |
| 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? |
| bool allAre0() const |
| { |
| foreach (w; _rep) if (w) return false; |
| return true; |
| } |
| |
| /// Are all bits one? |
| bool allAre1() const |
| { |
| foreach (w; _rep) if (w != ulong.max) return false; |
| return true; |
| } |
| |
| 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); |
| } |
| } |
| |
| @system 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); |
| } |