/* * Copyright (C) 2013 Omry Yadan <omry@yadan.net> * All rights reserved. * * See https://github.com/omry/banana/blob/master/BSD-LICENSE for licensing information */ package net.yadan.banana.memory.malloc; import net.yadan.banana.memory.IBlockAllocator; import net.yadan.banana.memory.IBuffer; import net.yadan.banana.memory.IMemAllocator; import net.yadan.banana.memory.MemInitializer; import net.yadan.banana.memory.OutOfBoundsAccess; import net.yadan.banana.memory.OutOfMemoryException; import net.yadan.banana.memory.block.BlockAllocator; public class ChainedAllocator implements IMemAllocator { private static final int NEXT_OFFSET = 0; private static final int DATA_OFFSET = 1; private IBlockAllocator m_blocks; private int m_blockSize; public ChainedAllocator(int maxBlocks, int blockSize) { this(maxBlocks, blockSize, null); } public ChainedAllocator(int maxBlocks, int blockSize, MemInitializer initializer) { this(maxBlocks, blockSize, 0, initializer); } public ChainedAllocator(int maxBlocks, int blockSize, double growthFactor) { this(maxBlocks, blockSize, growthFactor, null); } public ChainedAllocator(int maxBlocks, int blockSize, double growthFactor, MemInitializer initializer) { if (blockSize < 2) { throw new IllegalArgumentException("Minimum block size is 2"); } m_blocks = new BlockAllocator(maxBlocks, blockSize, growthFactor, initializer); m_blockSize = blockSize; } public ChainedAllocator(IBlockAllocator blocks) { m_blocks = blocks; m_blockSize = blocks.blockSize(); } @Override public int malloc(int size) throws OutOfMemoryException { if (size < 0) { throw new IllegalArgumentException("malloc size must be non-negative"); } if (size <= m_blockSize) { return m_blocks.malloc(); } else { return ~multiBlockMalloc(size); } } private int multiBlockMalloc(int size) { int dataSize = m_blockSize - DATA_OFFSET; int remains = size - dataSize; int head = m_blocks.malloc(); m_blocks.setInt(head, NEXT_OFFSET, -1); int link = head; try { while (remains > 0) { int next = m_blocks.malloc(); m_blocks.setInt(next, NEXT_OFFSET, -1); m_blocks.setInt(link, NEXT_OFFSET, next); link = next; remains -= dataSize; } } catch (OutOfMemoryException e) { free(~head); throw e; } return head; } @Override public int realloc(int pointer, int newSize) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (newSize < 0) { throw new IllegalArgumentException("malloc size must be non-negative"); } if (pointer < 0) { if (newSize <= m_blockSize) { int np = m_blocks.malloc(); m_blocks.memCopy(~pointer, DATA_OFFSET, np , 0, m_blockSize - DATA_OFFSET); int next = m_blocks.getInt(~pointer, NEXT_OFFSET); if (next != -1) { m_blocks.memCopy(next, DATA_OFFSET, np, m_blockSize - DATA_OFFSET, DATA_OFFSET); } free(pointer); return np; } else { int next = ~pointer; int last = -1; int blocksRemaining = 1 + ((newSize - 1) / (m_blockSize - DATA_OFFSET)); // ceil(a/b) do { blocksRemaining--; last = next; next = m_blocks.getInt(next, NEXT_OFFSET); } while (next != -1 && blocksRemaining != 0); if (blocksRemaining > 0) { int link = last; try { while(blocksRemaining > 0) { next = m_blocks.malloc(); m_blocks.setInt(next, NEXT_OFFSET, -1); m_blocks.setInt(link, NEXT_OFFSET, next); link = next; blocksRemaining--; } } catch (OutOfMemoryException e) { // TODO : is it possible to implement this handling with a realloc to shrink back? int before_last = last; last = m_blocks.getInt(last, NEXT_OFFSET); while(last != -1) { int f = m_blocks.getInt(last, NEXT_OFFSET); m_blocks.free(last); last = f; } m_blocks.setInt(before_last, NEXT_OFFSET, -1); throw e; } } else { // free remains if any int link = next; while (link != -1) { int p = link; link = m_blocks.getInt(link, NEXT_OFFSET); free(p); } m_blocks.setInt(last, NEXT_OFFSET, -1); } return pointer; } } else { // current memory is a single block if (newSize <= m_blockSize) { // nothing to do return pointer; } else { // new size > block size // allocate new multiblock, copy data int p = malloc(newSize); assert p < 0; // p must be multiblock int dst = ~p; m_blocks.memCopy(pointer, 0, dst, DATA_OFFSET, m_blockSize - DATA_OFFSET); dst = m_blocks.getInt(dst, NEXT_OFFSET); m_blocks.memCopy(pointer, m_blockSize - DATA_OFFSET, dst, DATA_OFFSET, DATA_OFFSET); free(pointer); return p; } } } @Override public void free(int pointer) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int directPointer = ~pointer; int next; do { next = m_blocks.getInt(directPointer, NEXT_OFFSET); m_blocks.free(directPointer); directPointer = next; } while (next != -1); } else { m_blocks.free(pointer); } } @Override public final void setInt(int pointer, int offset_in_data, int data) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { setIntDataMultiBlock(pointer, data, offset_in_data); } else { m_blocks.setInt(pointer, offset_in_data, data); } } protected void setIntDataMultiBlock(int pointer, int data, int offset_in_data) { int dataSize = m_blockSize - DATA_OFFSET; int current = ~pointer; while (offset_in_data >= dataSize) { current = m_blocks.getInt(current, NEXT_OFFSET); if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } offset_in_data -= dataSize; } m_blocks.setInt(current, DATA_OFFSET + offset_in_data, data); } @Override public int getInt(int pointer, int offset_in_data) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { return getIntDataMultiBlock(pointer, offset_in_data); } else { return m_blocks.getInt(pointer, offset_in_data); } } public int getIntDataMultiBlock(int pointer, int offset_in_data) { int dataSize = m_blockSize - DATA_OFFSET; int current = ~pointer; while (offset_in_data >= dataSize) { current = m_blocks.getInt(current, NEXT_OFFSET); if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } offset_in_data -= dataSize; } return m_blocks.getInt(current, DATA_OFFSET + offset_in_data); } @Override public void setInts(int pointer, int dst_offset_in_record, int src_data[], int src_pos, int length) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int dataSizePerBlock = m_blockSize - DATA_OFFSET; int current = ~pointer; while (dst_offset_in_record > dataSizePerBlock) { dst_offset_in_record -= dataSizePerBlock; current = m_blocks.getInt(current, NEXT_OFFSET); } while (length > 0) { if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } int copy_length; int dst_offset = 0; if (length > dataSizePerBlock - dst_offset_in_record) { copy_length = dataSizePerBlock - dst_offset_in_record; dst_offset = dst_offset_in_record; dst_offset_in_record = 0; } else { copy_length = length - dst_offset_in_record; dst_offset = dst_offset_in_record; } m_blocks.setInts(current, DATA_OFFSET + dst_offset, src_data, src_pos, copy_length); length -= copy_length; src_pos += copy_length; current = m_blocks.getInt(current, NEXT_OFFSET); } } else { m_blocks.setInts(pointer, dst_offset_in_record, src_data, src_pos, length); } } @Override public void getInts(int pointer, int src_offset_in_record, int dst_data[], int dst_pos, int length) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int dataSize = m_blockSize - DATA_OFFSET; int current = ~pointer; while (src_offset_in_record >= dataSize) { src_offset_in_record -= dataSize; current = m_blocks.getInt(current, NEXT_OFFSET); } while (length > 0) { if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } int copy_length; int src_offset = 0; if (length > dataSize - src_offset_in_record) { copy_length = dataSize - src_offset_in_record; src_offset = src_offset_in_record; src_offset_in_record = 0; } else { copy_length = length - src_offset_in_record; src_offset = src_offset_in_record; } m_blocks.getInts(current, DATA_OFFSET + src_offset, dst_data, dst_pos, copy_length); length -= copy_length; dst_pos += copy_length; current = m_blocks.getInt(current, NEXT_OFFSET); } } else { m_blocks.getInts(pointer, src_offset_in_record, dst_data, dst_pos, length); } } @Override public void memSet(int pointer, int srcPos, int length, int value) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int dataSize = m_blockSize - DATA_OFFSET; int current = ~pointer; while (srcPos >= dataSize) { srcPos -= dataSize; current = m_blocks.getInt(current, NEXT_OFFSET); } while (length > 0) { if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } int set_length; int src_offset = 0; if (length > dataSize - srcPos) { set_length = dataSize - srcPos; src_offset = srcPos; srcPos = 0; } else { set_length = length - srcPos; src_offset = srcPos; } m_blocks.memSet(current, DATA_OFFSET + src_offset, set_length, value); length -= set_length; current = m_blocks.getInt(current, NEXT_OFFSET); } } else { m_blocks.memSet(pointer, srcPos, length, value); } } @Override public void getBuffer(int pointer, int src_offset_in_record, IBuffer dst, int length) { getInts(pointer, src_offset_in_record, dst.array(), 0, length); dst.setUsed(length); } @Override public long getLong(int pointer, int offset_in_data) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer;; if (pointer < 0) { int ilower = getInt(pointer, offset_in_data + 1); int iupper = getInt(pointer, offset_in_data); long lower = 0x00000000FFFFFFFFL & ilower; long upper = ((long) iupper) << 32; return upper | lower; } else { return m_blocks.getLong(pointer, offset_in_data); } } @Override public void setLong(int pointer, int offset_in_data, long data) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { // upper int int int1 = (int) (data >>> 32); // lower int int int2 = (int) (data); setInt(pointer, offset_in_data, int1); setInt(pointer, offset_in_data + 1, int2); } else { m_blocks.setLong(pointer, offset_in_data, data); } } @Override public int computeMemoryUsageFor(int size) { if (size <= m_blockSize) { return 4 * m_blockSize; } else { return 4 * m_blockSize * (1 + (size - 1) / (m_blockSize - DATA_OFFSET)); } } @Override public int blockSize() { return m_blocks.blockSize(); } @Override public boolean isDebug() { return m_blocks.isDebug(); } @Override public void setDebug(boolean debug) { m_blocks.setDebug(debug); } @Override public void setInitializer(MemInitializer initializer) { m_blocks.setInitializer(initializer); } @Override public int usedBlocks() { return m_blocks.usedBlocks(); } @Override public int maxBlocks() { return m_blocks.maxBlocks(); } @Override public int freeBlocks() { return m_blocks.freeBlocks(); } @Override public void clear() { m_blocks.clear(); } @Override public long computeMemoryUsage() { return m_blocks.computeMemoryUsage(); } @Override public String toString() { return m_blocks.toString(); } @Override public int maximumCapacityFor(int pointer) { int capacity = 0; if (pointer < 0) { int directPointer = ~pointer; int next; do { next = m_blocks.getInt(directPointer, NEXT_OFFSET); capacity += (m_blockSize - DATA_OFFSET); directPointer = next; } while (next != -1); } else { capacity = m_blockSize; } return capacity; } @Override public void setGrowthFactor(double d) { m_blocks.setGrowthFactor(d); } @Override public double getGrowthFactor() { return m_blocks.getGrowthFactor(); } @Override public IBlockAllocator getBlocks() { return m_blocks; } @Override public String pointerDebugString(int pointer) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer;; StringBuilder sb = new StringBuilder(); if (pointer < 0) { int directPointer = ~pointer; int next; do { next = m_blocks.getInt(directPointer, NEXT_OFFSET); sb.append("["); for (int i = 1; i < m_blockSize; i++) { sb.append(getInt(directPointer, i)); if (i + 1 < m_blockSize) { sb.append(","); } } sb.append("]"); if (next != -1) { sb.append("->"); } directPointer = next; } while (next != -1); } else { sb.append("["); for (int i = 0; i < m_blockSize; i++) { sb.append(getInt(pointer, i)); if (i + 1 < m_blockSize) { sb.append(","); } } sb.append("]"); } return sb.toString(); } @Override public void initialize(int pointer) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int directPointer = ~pointer; int next; do { next = m_blocks.getInt(directPointer, NEXT_OFFSET); m_blocks.initialize(directPointer); m_blocks.setInt(directPointer, NEXT_OFFSET, next); directPointer = next; } while (next != -1); } else { m_blocks.initialize(pointer); } } @Override public short getUpperShort(int pointer, int offset) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { pointer = getDataBlockPointerFor(pointer, offset); offset = retOffset; } return m_blocks.getUpperShort(pointer, offset); } @Override public short getLowerShort(int pointer, int offset) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { pointer = getDataBlockPointerFor(pointer, offset); offset = retOffset; } return m_blocks.getLowerShort(pointer, offset); } @Override public void setUpperShort(int pointer, int offset, int s) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { pointer = getDataBlockPointerFor(pointer, offset); offset = retOffset; } m_blocks.setUpperShort(pointer, offset, s); } @Override public void setLowerShort(int pointer, int offset, int s) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { pointer = getDataBlockPointerFor(pointer, offset); offset = retOffset; } m_blocks.setLowerShort(pointer, offset, s); } // this is super ugly, but since nothing here is thread safe anyway it's okay. int retOffset; protected int getDataBlockPointerFor(int pointer, int offset) { assert pointer < 0; int dataSize = m_blockSize - DATA_OFFSET; int current = ~pointer; while (offset >= dataSize) { current = m_blocks.getInt(current, NEXT_OFFSET); if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } offset -= dataSize; } retOffset = DATA_OFFSET + offset; return current; } @Override public float getFloat(int pointer, int offset) { return Float.intBitsToFloat(getInt(pointer, offset)); } @Override public void setFloat(int pointer, int offset, float f) { setInt(pointer, offset, Float.floatToIntBits(f)); } @Override public double getDouble(int pointer, int offset_in_data) { return Double.longBitsToDouble(getLong(pointer, offset_in_data)); } @Override public void setDouble(int pointer, int offset_in_data, double data) { setLong(pointer, offset_in_data, Double.doubleToLongBits(data)); } @Override public void setChars(int pointer, int dst_offset_in_record, char[] src_data, int src_pos, int num_chars) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int dataSizePerBlock = m_blockSize - DATA_OFFSET; int current = ~pointer; while (dst_offset_in_record > dataSizePerBlock) { dst_offset_in_record -= dataSizePerBlock; current = m_blocks.getInt(current, NEXT_OFFSET); } int num_ints = 1 + (num_chars - 1) / 2; int chars_remaining = num_chars; while (num_ints > 0) { if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } int copy_length; int num_chars_to_copy; int dst_offset = 0; if (num_ints > dataSizePerBlock - dst_offset_in_record) { copy_length = dataSizePerBlock - dst_offset_in_record; dst_offset = dst_offset_in_record; dst_offset_in_record = 0; } else { copy_length = num_ints - dst_offset_in_record; dst_offset = dst_offset_in_record; } num_chars_to_copy = copy_length * 2; if (num_chars_to_copy > chars_remaining) { num_chars_to_copy = chars_remaining; } m_blocks.setChars(current, DATA_OFFSET + dst_offset, src_data, src_pos * 2, num_chars_to_copy); num_ints -= copy_length; src_pos += copy_length; chars_remaining -= num_chars_to_copy; current = m_blocks.getInt(current, NEXT_OFFSET); } } else { m_blocks.setChars(pointer, dst_offset_in_record, src_data, src_pos, num_chars); } } @Override public void getChars(int pointer, int src_offset_in_record, char[] dst_data, int dst_pos, int num_chars) { assert pointer != 0 : "Invalid pointer " + pointer; assert pointer != -1 : "Invalid pointer " + pointer; if (pointer < 0) { int dataSize = m_blockSize - DATA_OFFSET; int current = ~pointer; while (src_offset_in_record >= dataSize) { src_offset_in_record -= dataSize; current = m_blocks.getInt(current, NEXT_OFFSET); } int num_ints = 1 + (num_chars - 1) / 2; int chars_remaining = num_chars; while (num_ints > 0) { if (current == -1) { throw new OutOfBoundsAccess("Accessing pointer beyond allocation size"); } int copy_length; int num_chars_to_copy; int src_offset = 0; if (num_ints > dataSize - src_offset_in_record) { copy_length = dataSize - src_offset_in_record; src_offset = src_offset_in_record; src_offset_in_record = 0; } else { copy_length = num_ints - src_offset_in_record; src_offset = src_offset_in_record; } num_chars_to_copy = copy_length * 2; if (num_chars_to_copy > chars_remaining) { num_chars_to_copy = chars_remaining; } m_blocks.getChars(current, DATA_OFFSET + src_offset, dst_data, dst_pos * 2, num_chars_to_copy); num_ints -= copy_length; dst_pos += copy_length; chars_remaining -= num_chars_to_copy; current = m_blocks.getInt(current, NEXT_OFFSET); } } else { m_blocks.getChars(pointer, src_offset_in_record, dst_data, dst_pos, num_chars); } } }