// Number of headers (smallest blocks) per target block.
private int allocateWithSplit(int arenaIx, int freeListIx,
        MemoryBuffer[] dest, int ix, int allocationSize) {
      if (data == null) return -1; // not allocated yet
      FreeList freeList = freeLists[freeListIx];
      int remaining = -1;
      freeList.lock.lock();
      try {
        // Try to allocate from target-sized free list, maybe we'll get lucky.
        ix = allocateFromFreeListUnderLock(
            arenaIx, freeList, freeListIx, dest, ix, allocationSize);
        remaining = dest.length - ix;
        if (remaining == 0) return ix;
      } finally {
        freeList.lock.unlock();
      }
      byte headerData = makeHeader(freeListIx, true); // Header for newly allocated used blocks.
      int headerStep = 1 << freeListIx; // Number of headers (smallest blocks) per target block.
      int splitListIx = freeListIx + 1; // Next free list from which we will be splitting.
      // Each iteration of this loop tries to split blocks from one level of the free list into
      // target size blocks; if we cannot satisfy the allocation from the free list containing the
      // blocks of a particular size, we'll try to split yet larger blocks, until we run out.
      while (remaining > 0 && splitListIx < freeLists.length) {
        int splitWaysLog2 = (splitListIx - freeListIx);
        assert splitWaysLog2 > 0;
        int splitWays = 1 << splitWaysLog2; // How many ways each block splits into target size.
        int lastSplitBlocksRemaining = -1; // How many target-sized blocks remain from last split.
        int lastSplitNextHeader = -1; // The header index for the beginning of the remainder.
        FreeList splitList = freeLists[splitListIx];
        splitList.lock.lock();
        try {
          int headerIx = splitList.listHead; // Index of the next free block to split.
          while (headerIx >= 0 && remaining > 0) {
            int origOffset = offsetFromHeaderIndex(headerIx), offset = origOffset;
            // We will split the block at headerIx [splitWays] ways, and take [toTake] blocks,
            // which will leave [lastSplitBlocksRemaining] free blocks of target size.
            int toTake = Math.min(splitWays, remaining);
            remaining -= toTake;
            lastSplitBlocksRemaining = splitWays - toTake; // Whatever remains.
            // Take toTake blocks by splitting the block at offset.
            for (; toTake > 0; ++ix, --toTake, headerIx += headerStep, offset += allocationSize) {
              headers[headerIx] = headerData;
              // TODO: this could be done out of the lock, we only need to take the blocks out.
              ((LlapDataBuffer)dest[ix]).initialize(arenaIx, data, offset, allocationSize);
            }
            lastSplitNextHeader = headerIx; // If anything remains, this is where it starts.
            headerIx = getNextFreeListItem(origOffset);
          }
          replaceListHeadUnderLock(splitList, headerIx); // In the end, update free list head.
        } finally {
          splitList.lock.unlock();
        }
        if (remaining == 0) {
          // We have just obtained all we needed by splitting some block; now we need
          // to put the space remaining from that block into lower free lists.
          // We'll put at most one block into each list, since 2 blocks can always be combined
          // to make a larger-level block. Each bit in the remaining target-sized blocks count
          // is one block in a list offset from target-sized list by bit index.
          int newListIndex = freeListIx;
          while (lastSplitBlocksRemaining > 0) {
            if ((lastSplitBlocksRemaining & 1) == 1) {
              FreeList newFreeList = freeLists[newListIndex];
              newFreeList.lock.lock();
              headers[lastSplitNextHeader] = makeHeader(newListIndex, false);
              try {
                addBlockToFreeListUnderLock(newFreeList, lastSplitNextHeader);
              } finally {
                newFreeList.lock.unlock();
              }
              lastSplitNextHeader += (1 << newListIndex);
            }
            lastSplitBlocksRemaining >>>= 1;
            ++newListIndex;
            continue;
          }
        }
        ++splitListIx;
      }
      return ix;
    }
}
byte headerData = makeHeader(freeListIx, true); // Header for newly allocated used blocks.
int headerStep = 1 << freeListIx; // Number of headers (smallest blocks) per target block.
int splitListIx = freeListIx + 1; // Next free list from which we will be splitting.
// Each iteration of this loop tries to split blocks from one level of the free list into
try {
// Try to allocate from target-sized free list, maybe we'll get lucky.
ix = allocateFromFreeListUnderLock(
arenaIx, freeList, freeListIx, dest, ix, allocationSize);
remaining = dest.length - ix;
if (remaining == 0) return ix;
} finally {
freeList.lock.unlock();
}
byte headerData = makeHeader(freeListIx, true); // Header for newly allocated used blocks.
int headerStep = 1 << freeListIx; // Number of headers (smallest blocks) per target block.
int splitListIx = freeListIx + 1; // Next free list from which we will be splitting.
// Each iteration of this loop tries to split blocks from one level of the free list into
// target size blocks; if we cannot satisfy the allocation from the free list containing the
// blocks of a particular size, we'll try to split yet larger blocks, until we run out.
while (remaining > 0 && splitListIx < freeLists.length) {
int splitWaysLog2 = (splitListIx - freeListIx);
assert splitWaysLog2 > 0;
int splitWays = 1 << splitWaysLog2; // How many ways each block splits into target size.
int lastSplitBlocksRemaining = -1; // How many target-sized blocks remain from last split.
int lastSplitNextHeader = -1; // The header index for the beginning of the remainder.
private int allocateWithSplit(int arenaIx, int freeListIx,
MemoryBuffer[] dest, int ix, int allocationSize) {
if (data == null) return -1; // not allocated yet
FreeList freeList = freeLists[freeListIx];
int remaining = -1;
freeList.lock.lock();
try {
// Try to allocate from target-sized free list, maybe we'll get lucky.
ix = allocateFromFreeListUnderLock(
arenaIx, freeList, freeListIx, dest, ix, allocationSize);
remaining = dest.length - ix;
if (remaining == 0) return ix;
} finally {
freeList.lock.unlock();
}
byte headerData = makeHeader(freeListIx, true); // Header for newly allocated used blocks.
int headerStep = 1 << freeListIx; // Number of headers (smallest blocks) per target block.
int splitListIx = freeListIx + 1; // Next free list from which we will be splitting.
// Each iteration of this loop tries to split blocks from one level of the free list into
// target size blocks; if we cannot satisfy the allocation from the free list containing the
// blocks of a particular size, we'll try to split yet larger blocks, until we run out.
while (remaining > 0 && splitListIx < freeLists.length) {
int splitWaysLog2 = (splitListIx - freeListIx);
assert splitWaysLog2 > 0;
int splitWays = 1 << splitWaysLog2; // How many ways each block splits into target size.
int lastSplitBlocksRemaining = -1; // How many target-sized blocks remain from last split.
int lastSplitNextHeader = -1; // The header index for the beginning of the remainder.
FreeList splitList = freeLists[splitListIx];
splitList.lock.lock();
try {
int headerIx = splitList.listHead; // Index of the next free block to split.
while (headerIx >= 0 && remaining > 0) {
int origOffset = offsetFromHeaderIndex(headerIx), offset = origOffset;
// We will split the block at headerIx [splitWays] ways, and take [toTake] blocks,
// which will leave [lastSplitBlocksRemaining] free blocks of target size.
int toTake = Math.min(splitWays, remaining);
remaining -= toTake;