| /* |
| * This file is part of the Jikes RVM project (http://jikesrvm.org). |
| * |
| * This file is licensed to You under the Eclipse Public License (EPL); |
| * You may not use this file except in compliance with the License. You |
| * may obtain a copy of the License at |
| * |
| * http://www.opensource.org/licenses/eclipse-1.0.php |
| * |
| * See the COPYRIGHT.txt file distributed with this work for information |
| * regarding copyright ownership. |
| */ |
| package org.mmtk.policy; |
| |
| import org.mmtk.utility.alloc.BlockAllocator; |
| import org.mmtk.utility.alloc.EmbeddedMetaData; |
| import org.mmtk.utility.heap.FreeListPageResource; |
| import org.mmtk.utility.heap.VMRequest; |
| import org.mmtk.utility.Constants; |
| import org.mmtk.utility.Conversions; |
| import org.mmtk.utility.Memory; |
| |
| import org.mmtk.vm.Lock; |
| import org.mmtk.vm.VM; |
| |
| import org.vmmagic.pragma.*; |
| import org.vmmagic.unboxed.*; |
| |
| /** |
| * Each instance of this class corresponds to one mark-sweep *space*. |
| * Each of the instance methods of this class may be called by any |
| * thread (i.e. synchronization must be explicit in any instance or |
| * class method). This contrasts with the MarkSweepLocal, where |
| * instances correspond to *plan* instances and therefore to kernel |
| * threads. Thus unlike this class, synchronization is not necessary |
| * in the instance methods of MarkSweepLocal. |
| */ |
| @Uninterruptible |
| public abstract class SegregatedFreeListSpace extends Space implements Constants { |
| |
| /**************************************************************************** |
| * |
| * Class variables |
| */ |
| protected static final boolean LAZY_SWEEP = true; |
| private static final boolean COMPACT_SIZE_CLASSES = false; |
| protected static final int MIN_CELLS = 6; |
| protected static final int MAX_CELLS = 99; // (1<<(INUSE_BITS-1))-1; |
| protected static final int MAX_CELL_SIZE = 8<<10; |
| public static final int MAX_FREELIST_OBJECT_BYTES = MAX_CELL_SIZE; |
| |
| // live bits etc |
| private static final int OBJECT_LIVE_SHIFT = LOG_MIN_ALIGNMENT; // 4 byte resolution |
| private static final int LOG_BIT_COVERAGE = OBJECT_LIVE_SHIFT; |
| private static final int LOG_LIVE_COVERAGE = LOG_BIT_COVERAGE + LOG_BITS_IN_BYTE; |
| private static final int LIVE_BYTES_PER_REGION = 1 << (EmbeddedMetaData.LOG_BYTES_IN_REGION - LOG_LIVE_COVERAGE); |
| private static final Word WORD_SHIFT_MASK = Word.one().lsh(LOG_BITS_IN_WORD).minus(Extent.one()); |
| private static final int LOG_LIVE_WORD_STRIDE = LOG_LIVE_COVERAGE + LOG_BYTES_IN_WORD; |
| private static final Extent LIVE_WORD_STRIDE = Extent.fromIntSignExtend(1<<LOG_LIVE_WORD_STRIDE); |
| private static final Word LIVE_WORD_STRIDE_MASK = LIVE_WORD_STRIDE.minus(1).toWord().not(); |
| private static final int NET_META_DATA_BYTES_PER_REGION = BlockAllocator.META_DATA_BYTES_PER_REGION + LIVE_BYTES_PER_REGION; |
| protected static final int META_DATA_PAGES_PER_REGION_WITH_BITMAP = Conversions.bytesToPages(Extent.fromIntSignExtend(NET_META_DATA_BYTES_PER_REGION)); |
| protected static final int META_DATA_PAGES_PER_REGION_NO_BITMAP = Conversions.bytesToPages(Extent.fromIntSignExtend(BlockAllocator.META_DATA_BYTES_PER_REGION)); |
| private static final Extent META_DATA_OFFSET = BlockAllocator.META_DATA_EXTENT; |
| |
| |
| // calculate worst case fragmentation very conservatively |
| private static final int NEW_SIZECLASS_OVERHEAD = sizeClassCount(); // one page wasted per size class |
| private static final int METADATA_OVERHEAD = META_DATA_PAGES_PER_REGION_WITH_BITMAP; // worst case scenario |
| public static final float WORST_CASE_FRAGMENTATION = 1 + ((NEW_SIZECLASS_OVERHEAD + METADATA_OVERHEAD)/(float) EmbeddedMetaData.BYTES_IN_REGION); |
| |
| /**************************************************************************** |
| * |
| * Instance variables |
| */ |
| protected final Lock lock = VM.newLock("SegregatedFreeListGlobal"); |
| protected final AddressArray consumedBlockHead = AddressArray.create(sizeClassCount()); |
| protected final AddressArray flushedBlockHead = AddressArray.create(sizeClassCount()); |
| protected final AddressArray availableBlockHead = AddressArray.create(sizeClassCount()); |
| |
| private final int[] cellSize = new int[sizeClassCount()]; |
| private final byte[] blockSizeClass = new byte[sizeClassCount()]; |
| private final int[] blockHeaderSize = new int[sizeClassCount()]; |
| |
| /** |
| * The caller specifies the region of virtual memory to be used for |
| * this space. If this region conflicts with an existing space, |
| * then the constructor will fail. |
| * |
| * @param name The name of this space (used when printing error messages etc) |
| * @param pageBudget The number of pages this space may consume before consulting the plan |
| * @param additionalMetadata The number of meta data bytes per region for the subclass. |
| * @param vmRequest An object describing the virtual memory requested. |
| */ |
| public SegregatedFreeListSpace(String name, int pageBudget, int additionalMetadata, VMRequest vmRequest) { |
| super(name, false, false, vmRequest); |
| initSizeClasses(); |
| int totalMetadata = additionalMetadata; |
| if (maintainSideBitmap()) { |
| totalMetadata += META_DATA_PAGES_PER_REGION_WITH_BITMAP; |
| } else { |
| totalMetadata += META_DATA_PAGES_PER_REGION_NO_BITMAP; |
| } |
| if (vmRequest.isDiscontiguous()) { |
| pr = new FreeListPageResource(pageBudget, this, totalMetadata); |
| } else { |
| pr = new FreeListPageResource(pageBudget, this, start, extent, totalMetadata); |
| } |
| } |
| |
| /** |
| * Should SegregatedFreeListSpace manage a side bitmap to keep track of live objects? |
| */ |
| protected abstract boolean maintainSideBitmap(); |
| |
| /** |
| * Do we need to preserve free lists as we move blocks around. |
| */ |
| protected abstract boolean preserveFreeList(); |
| |
| /**************************************************************************** |
| * |
| * Allocation |
| */ |
| |
| /** |
| * Return a block to the global pool. |
| * |
| * @param block The block to return |
| * @param sizeClass The size class |
| */ |
| public void returnConsumedBlock(Address block, int sizeClass) { |
| returnBlock(block, sizeClass, Address.zero()); |
| } |
| |
| /** |
| * Return a block to the global pool. |
| * |
| * @param block The block to return |
| * @param sizeClass The size class |
| * @param freeCell The first free cell in the block. |
| */ |
| public void returnBlock(Address block, int sizeClass, Address freeCell) { |
| if (VM.VERIFY_ASSERTIONS) { |
| VM.assertions._assert(BlockAllocator.getNext(block).isZero()); |
| } |
| if (preserveFreeList()) { |
| setFreeList(block, freeCell); |
| } |
| lock.acquire(); |
| BlockAllocator.setNext(block, consumedBlockHead.get(sizeClass)); |
| consumedBlockHead.set(sizeClass, block); |
| lock.release(); |
| } |
| |
| /** |
| * Acquire a new block from the global pool to allocate into. This method |
| * with either return a non-empty free list, or zero when allocation |
| * fails. |
| * |
| * This method will populate the passed in free list for the given size |
| * class and return the address of the block. |
| * |
| * @param sizeClass The size class to allocate into |
| * @param freeList The free list to populate |
| * @return The address of the block |
| */ |
| public Address getAllocationBlock(int sizeClass, AddressArray freeList) { |
| lock.acquire(); |
| Address block; |
| while(!(block = availableBlockHead.get(sizeClass)).isZero()) { |
| availableBlockHead.set(sizeClass, BlockAllocator.getNext(block)); |
| lock.release(); |
| |
| /* This block is no longer on any list */ |
| BlockAllocator.setNext(block, Address.zero()); |
| |
| /* Can we allocate into this block? */ |
| Address cell = advanceToBlock(block, sizeClass); |
| if (!cell.isZero()) { |
| freeList.set(sizeClass, cell); |
| return block; |
| } |
| |
| /* Block was full */ |
| lock.acquire(); |
| BlockAllocator.setNext(block, consumedBlockHead.get(sizeClass)); |
| consumedBlockHead.set(sizeClass, block); |
| } |
| lock.release(); |
| return expandSizeClass(sizeClass, freeList); |
| } |
| |
| /** |
| * Expand a particular size class, allocating a new block, breaking |
| * the block into cells and placing those cells on a free list for |
| * that block. The block becomes the current head for this size |
| * class and the address of the first available cell is returned.<p> |
| * |
| * <b>This is guaranteed to return pre-zeroed cells</b> |
| * |
| * @param sizeClass The size class to be expanded |
| * @param freeList The free list to populate. |
| * @return The block that was just allocated. |
| */ |
| @Inline |
| private Address expandSizeClass(int sizeClass, AddressArray freeList) { |
| Address block = BlockAllocator.alloc(this, blockSizeClass[sizeClass]); |
| |
| if (block.isZero()) { |
| return Address.zero(); |
| } |
| |
| BlockAllocator.setNext(block, Address.zero()); |
| BlockAllocator.setAllClientSizeClass(block, blockSizeClass[sizeClass], (byte) sizeClass); |
| |
| notifyNewBlock(block, sizeClass); |
| |
| int cellExtent = cellSize[sizeClass]; |
| int blockSize = BlockAllocator.blockSize(blockSizeClass[sizeClass]); |
| int useableBlockSize = blockSize - blockHeaderSize[sizeClass]; |
| Address firstCell = block.plus(blockHeaderSize[sizeClass]); |
| Address sentinel = block.plus(blockSize); |
| |
| /* pre-zero the block */ |
| VM.memory.zero(firstCell, Extent.fromIntZeroExtend(useableBlockSize)); |
| |
| /* construct the free list */ |
| Address nextCell; |
| Address cell = firstCell; |
| while ((nextCell = cell.plus(cellExtent)).LT(sentinel)) { |
| cell.store(nextCell); |
| cell = nextCell; |
| } |
| |
| /* Populate the free list */ |
| freeList.set(sizeClass, firstCell); |
| return block; |
| } |
| |
| /**************************************************************************** |
| * |
| * Block management |
| */ |
| |
| /** |
| * Initialize the size class data structures. |
| */ |
| private void initSizeClasses() { |
| for (int sc = 0; sc < sizeClassCount(); sc++) { |
| cellSize[sc] = getBaseCellSize(sc); |
| for (byte blk = 0; blk < BlockAllocator.BLOCK_SIZE_CLASSES; blk++) { |
| int usableBytes = BlockAllocator.blockSize(blk); |
| int cells = usableBytes / cellSize[sc]; |
| blockSizeClass[sc] = blk; |
| /* cells must start at multiple of MIN_ALIGNMENT because |
| cellSize is also supposed to be multiple, this should do |
| the trick: */ |
| blockHeaderSize[sc] = BlockAllocator.blockSize(blk) - cells * cellSize[sc]; |
| if (((usableBytes < BYTES_IN_PAGE) && (cells*2 > MAX_CELLS)) || |
| ((usableBytes > (BYTES_IN_PAGE>>1)) && (cells > MIN_CELLS))) |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Get the size class for a given number of bytes. |
| * |
| * We use size classes based on a worst case internal fragmentation |
| * loss target of 1/8. In fact, across sizes from 8 bytes to 512 |
| * the average worst case loss is 13.3%, giving an expected loss |
| * (assuming uniform distribution) of about 7%. We avoid using the |
| * Lea class sizes because they were so numerous and therefore |
| * liable to lead to excessive inter-class-size fragmentation.<p> |
| * |
| * This method may segregate arrays and scalars (currently it does |
| * not).<p> |
| * |
| * This method should be more intelligent and take alignment requests |
| * into consideration. The issue with this is that the block header |
| * which can be varied by subclasses can change the alignment of the |
| * cells.<p> |
| * |
| * @param bytes The number of bytes required to accommodate the object |
| * to be allocated. |
| * @return The size class capable of accommodating the allocation request. |
| */ |
| @Inline |
| public final int getSizeClass(int bytes) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert((bytes > 0) && (bytes <= MAX_CELL_SIZE)); |
| |
| int sz1 = bytes - 1; |
| |
| if (BYTES_IN_ADDRESS == 4) { // 32-bit |
| if (COMPACT_SIZE_CLASSES) |
| return ( |
| (sz1 <= 31) ? (sz1 >> 2) : // 4 bytes apart |
| (sz1 <= 63) ? 4 + (sz1 >> 3) : // 8 bytes apart |
| (sz1 <= 95) ? 8 + (sz1 >> 4) : // 16 bytes apart |
| (sz1 <= 223) ? 14 + (sz1 >> 6) : // 64 bytes apart |
| (sz1 <= 734) ? 17 + (sz1 >> 8) : // 256 bytes apart |
| 20 + (sz1 >> 10)); // 1024 bytes apart |
| else |
| return ( |
| (sz1 <= 63) ? (sz1 >> 2) : // 4 bytes apart |
| (sz1 <= 127) ? 12 + (sz1 >> 4) : // 16 bytes apart |
| (sz1 <= 255) ? 16 + (sz1 >> 5) : // 32 bytes apart |
| (sz1 <= 511) ? 20 + (sz1 >> 6) : // 64 bytes apart |
| (sz1 <= 2047) ? 26 + (sz1 >> 8) : // 256 bytes apart |
| 32 + (sz1 >> 10)); // 1024 bytes apart |
| } else { // 64-bit |
| if (COMPACT_SIZE_CLASSES) |
| return ( |
| (sz1 <= 95) ? (sz1 >> 3) : // 8 bytes apart |
| (sz1 <= 127) ? 6 + (sz1 >> 4) : // 16 bytes apart |
| (sz1 <= 191) ? 10 + (sz1 >> 5) : // 32 bytes apart |
| (sz1 <= 383) ? 13 + (sz1 >> 6) : // 64 bytes apart |
| (sz1 <= 511) ? 16 + (sz1 >> 7) : // 128 bytes apart |
| (sz1 <= 1023) ? 19 + (sz1 >> 9) : // 512 bytes apart |
| 20 + (sz1 >> 10)); // 1024 bytes apart |
| else |
| return ( |
| (sz1 <= 111) ? (sz1 >> 3) : // 8 bytes apart |
| (sz1 <= 223) ? 7 + (sz1 >> 4) : // 16 bytes apart |
| (sz1 <= 319) ? 14 + (sz1 >> 5) : // 32 bytes apart |
| (sz1 <= 575) ? 19 + (sz1 >> 6) : // 64 bytes apart |
| (sz1 <= 2047) ? 26 + (sz1 >> 8) : // 256 bytes apart |
| 32 + (sz1 >> 10)); // 1024 bytes apart |
| } |
| } |
| |
| /** |
| * Return the size of a basic cell (i.e. not including any cell |
| * header) for a given size class. |
| * |
| * @param sc The size class in question |
| * @return The size of a basic cell (i.e. not including any cell |
| * header). |
| */ |
| @Inline |
| public final int getBaseCellSize(int sc) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert((sc >= 0) && (sc < sizeClassCount())); |
| |
| if (BYTES_IN_ADDRESS == 4) { // 32-bit |
| if (COMPACT_SIZE_CLASSES) |
| return ((sc < 8) ? (sc + 1) << 2: |
| (sc < 12) ? (sc - 3) << 3: |
| (sc < 16) ? (sc - 7) << 4: |
| (sc < 18) ? (sc - 13) << 6: |
| (sc < 21) ? (sc - 16) << 8: |
| (sc - 19) << 10); |
| else |
| return ((sc < 16) ? (sc + 1) << 2: |
| (sc < 20) ? (sc - 11) << 4: |
| (sc < 24) ? (sc - 15) << 5: |
| (sc < 28) ? (sc - 19) << 6: |
| (sc < 34) ? (sc - 25) << 8: |
| (sc - 31) << 10); |
| } else { // 64-bit |
| if (COMPACT_SIZE_CLASSES) |
| return ((sc < 12) ? (sc + 1) << 3: |
| (sc < 14) ? (sc - 5) << 4: |
| (sc < 16) ? (sc - 9) << 5: |
| (sc < 19) ? (sc - 12) << 6: |
| (sc < 20) ? (sc - 15) << 7: |
| (sc < 21) ? (sc - 18) << 9: |
| (sc - 19) << 10); |
| else |
| return ((sc < 14) ? (sc + 1) << 3: |
| (sc < 21) ? (sc - 6) << 4: |
| (sc < 24) ? (sc - 13) << 5: |
| (sc < 28) ? (sc - 18) << 6: |
| (sc < 34) ? (sc - 25) << 8: |
| (sc - 31) << 10); |
| } |
| } |
| |
| /** |
| * The number of distinct size classes. |
| */ |
| @Inline |
| public static int sizeClassCount() { |
| return (COMPACT_SIZE_CLASSES) ? 28 : 40; |
| } |
| |
| /**************************************************************************** |
| * |
| * Preserving (saving & restoring) free lists |
| */ |
| |
| /** |
| * Prepare a block for allocation, returning a free list into the block. |
| * |
| * @param block The new block |
| * @param sizeClass The block's sizeclass. |
| */ |
| protected abstract Address advanceToBlock(Address block, int sizeClass); |
| |
| /** |
| * Notify that a new block has been installed. |
| * |
| * @param block The new block |
| * @param sizeClass The block's sizeclass. |
| */ |
| protected void notifyNewBlock(Address block, int sizeClass) {} |
| |
| /** |
| * Should the sweep reclaim the cell containing this object. Is this object |
| * live. This is only used when maintainSideBitmap is false. |
| * |
| * @param object The object to query |
| * @return True if the cell should be reclaimed |
| */ |
| protected boolean reclaimCellForObject(ObjectReference object) { |
| VM.assertions.fail("Must implement reclaimCellForObject if not maintaining side bitmap"); |
| return false; |
| } |
| |
| /**************************************************************************** |
| * |
| * Metadata manipulation |
| */ |
| |
| /** |
| * In the case where free lists associated with each block are |
| * preserved, get the free list for a given block. |
| * |
| * @param block The block whose free list is to be found |
| * @return The free list for this block |
| */ |
| @Inline |
| protected final Address getFreeList(Address block) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(preserveFreeList()); |
| return BlockAllocator.getFreeListMeta(block); |
| } |
| |
| /** |
| * In the case where free lists associated with each block are |
| * preserved, set the free list for a given block. |
| * |
| * @param block The block whose free list is to be found |
| * @param cell The head of the free list (i.e. the first cell in the |
| * free list). |
| */ |
| @Inline |
| protected final void setFreeList(Address block, Address cell) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(preserveFreeList()); |
| BlockAllocator.setFreeListMeta(block, cell); |
| } |
| |
| /**************************************************************************** |
| * |
| * Collection |
| */ |
| |
| /** |
| * Clear all block marks for this space. This method is important when |
| * it is desirable to do partial collections, which man mean that block |
| * marks need to be explicitly cleared when necessary. |
| */ |
| protected final void clearAllBlockMarks() { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!maintainSideBitmap()); |
| for (int sizeClass = 0; sizeClass < sizeClassCount(); sizeClass++) { |
| Extent blockSize = Extent.fromIntSignExtend(BlockAllocator.blockSize(blockSizeClass[sizeClass])); |
| /* Flushed blocks */ |
| Address block = flushedBlockHead.get(sizeClass); |
| while (!block.isZero()) { |
| Address next = BlockAllocator.getNext(block); |
| clearBlockMark(block, blockSize); |
| block = next; |
| } |
| /* Available blocks */ |
| block = consumedBlockHead.get(sizeClass); |
| while (!block.isZero()) { |
| Address next = BlockAllocator.getNext(block); |
| clearBlockMark(block, blockSize); |
| block = next; |
| } |
| } |
| } |
| |
| /** |
| * Sweep all blocks for free objects. |
| * |
| * @param clearMarks should we clear block mark bits as we process. |
| */ |
| protected final void sweepConsumedBlocks(boolean clearMarks) { |
| for (int sizeClass = 0; sizeClass < sizeClassCount(); sizeClass++) { |
| Extent blockSize = Extent.fromIntSignExtend(BlockAllocator.blockSize(blockSizeClass[sizeClass])); |
| Address availableHead = Address.zero(); |
| /* Flushed blocks */ |
| Address block = flushedBlockHead.get(sizeClass); |
| flushedBlockHead.set(sizeClass, Address.zero()); |
| while (!block.isZero()) { |
| Address next = BlockAllocator.getNext(block); |
| availableHead = sweepBlock(block, sizeClass, blockSize, availableHead, clearMarks); |
| block = next; |
| } |
| /* Consumed blocks */ |
| block = consumedBlockHead.get(sizeClass); |
| consumedBlockHead.set(sizeClass, Address.zero()); |
| while (!block.isZero()) { |
| Address next = BlockAllocator.getNext(block); |
| availableHead = sweepBlock(block, sizeClass, blockSize, availableHead, clearMarks); |
| block = next; |
| } |
| /* Make blocks available */ |
| availableBlockHead.set(sizeClass, availableHead); |
| } |
| } |
| |
| /** |
| * Sweep a block, freeing it and adding to the list given by availableHead |
| * if it contains no free objects. |
| * |
| * @param clearMarks should we clear block mark bits as we process. |
| */ |
| protected final Address sweepBlock(Address block, int sizeClass, Extent blockSize, Address availableHead, boolean clearMarks) { |
| boolean liveBlock = containsLiveCell(block, blockSize, clearMarks); |
| if (!liveBlock) { |
| BlockAllocator.setNext(block, Address.zero()); |
| BlockAllocator.free(this, block); |
| } else { |
| BlockAllocator.setNext(block, availableHead); |
| availableHead = block; |
| if (!LAZY_SWEEP) { |
| setFreeList(block, makeFreeList(block, sizeClass)); |
| } |
| } |
| return availableHead; |
| } |
| |
| /** |
| * Eagerly consume all remaining blocks. |
| */ |
| protected final void consumeBlocks() { |
| for (int sizeClass = 0; sizeClass < sizeClassCount(); sizeClass++) { |
| while (!getAllocationBlock(sizeClass, null).isZero()); |
| } |
| } |
| |
| /** |
| * Flush all the allocation blocks to the consumed list. |
| */ |
| protected final void flushAvailableBlocks() { |
| for (int sizeClass = 0; sizeClass < sizeClassCount(); sizeClass++) { |
| flushedBlockHead.set(sizeClass, availableBlockHead.get(sizeClass)); |
| availableBlockHead.set(sizeClass, Address.zero()); |
| } |
| } |
| |
| /** |
| * Does this block contain any live cells. |
| * |
| * @param block The block |
| * @param blockSize The size of the block |
| * @param clearMarks should we clear block mark bits as we process. |
| * @return True if any cells in the block are live |
| */ |
| @Inline |
| protected boolean containsLiveCell(Address block, Extent blockSize, boolean clearMarks) { |
| if (maintainSideBitmap()) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(alignToLiveStride(block).EQ(block)); |
| Address cursor = getLiveWordAddress(block); |
| Address sentinel = getLiveWordAddress(block.plus(blockSize.minus(1))); |
| while (cursor.LE(sentinel)) { |
| Word live = cursor.loadWord(); |
| if (!live.isZero()) { |
| return true; |
| } |
| cursor = cursor.plus(BYTES_IN_WORD); |
| } |
| return false; |
| } else { |
| boolean live = false; |
| Address cursor = block; |
| while(cursor.LT(block.plus(blockSize))) { |
| live |= BlockAllocator.checkBlockMeta(cursor); |
| if (clearMarks) |
| BlockAllocator.clearBlockMeta(cursor); |
| cursor = cursor.plus(1 << BlockAllocator.LOG_MIN_BLOCK); |
| } |
| return live; |
| } |
| } |
| |
| |
| /** |
| * Clear block marks for a block |
| * |
| * @param block The block |
| * @param blockSize The size of the block |
| */ |
| @Inline |
| protected void clearBlockMark(Address block, Extent blockSize) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!maintainSideBitmap()); |
| Address cursor = block; |
| while(cursor.LT(block.plus(blockSize))) { |
| BlockAllocator.clearBlockMeta(cursor); |
| cursor = cursor.plus(1 << BlockAllocator.LOG_MIN_BLOCK); |
| } |
| } |
| |
| /** |
| * In the cell containing this object live? |
| * |
| * @param object The object |
| * @return True if the cell is live |
| */ |
| @Inline |
| protected boolean isCellLive(ObjectReference object) { |
| /* Must override if not using the side bitmap */ |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(maintainSideBitmap()); |
| return liveBitSet(object); |
| } |
| |
| /** |
| * Use the live bits for a block to infer free cells and thus |
| * construct a free list for the block. |
| * |
| * @param block The block to be processed |
| * @param sizeClass The size class for the block |
| * @return The head of the new free list |
| */ |
| @Inline |
| protected final Address makeFreeList(Address block, int sizeClass) { |
| Extent blockSize = Extent.fromIntSignExtend(BlockAllocator.blockSize(blockSizeClass[sizeClass])); |
| Address cursor = block.plus(blockHeaderSize[sizeClass]); |
| Address lastFree = Address.zero(); |
| Address firstFree = Address.zero(); |
| Address end = block.plus(blockSize); |
| Extent cellExtent = Extent.fromIntSignExtend(cellSize[sizeClass]); |
| while (cursor.LT(end)) { |
| ObjectReference current = VM.objectModel.getObjectFromStartAddress(cursor); |
| boolean free = true; |
| if (!current.isNull()) { |
| free = !isCellLive(current); |
| } |
| if (free) { |
| if (firstFree.isZero()) { |
| firstFree = cursor; |
| } else { |
| lastFree.store(cursor); |
| } |
| Memory.zeroSmall(cursor, cellExtent); |
| lastFree = cursor; |
| } |
| cursor = cursor.plus(cellExtent); |
| } |
| return firstFree; |
| } |
| |
| /** |
| * Sweep all blocks for free objects. |
| */ |
| public void sweepCells(Sweeper sweeper) { |
| for (int sizeClass = 0; sizeClass < sizeClassCount(); sizeClass++) { |
| Address availableHead = Address.zero(); |
| /* Flushed blocks */ |
| Address block = flushedBlockHead.get(sizeClass); |
| flushedBlockHead.set(sizeClass, Address.zero()); |
| while (!block.isZero()) { |
| Address next = BlockAllocator.getNext(block); |
| availableHead = sweepCells(sweeper, block, sizeClass, availableHead); |
| block = next; |
| } |
| /* Consumed blocks */ |
| block = consumedBlockHead.get(sizeClass); |
| consumedBlockHead.set(sizeClass, Address.zero()); |
| while (!block.isZero()) { |
| Address next = BlockAllocator.getNext(block); |
| availableHead = sweepCells(sweeper, block, sizeClass, availableHead); |
| block = next; |
| } |
| /* Make blocks available */ |
| availableBlockHead.set(sizeClass, availableHead); |
| } |
| } |
| |
| /** |
| * Sweep a block, freeing it and adding to the list given by availableHead |
| * if it contains no free objects. |
| */ |
| private Address sweepCells(Sweeper sweeper, Address block, int sizeClass, Address availableHead) { |
| boolean liveBlock = sweepCells(sweeper, block, sizeClass); |
| if (!liveBlock) { |
| BlockAllocator.setNext(block, Address.zero()); |
| BlockAllocator.free(this, block); |
| } else { |
| BlockAllocator.setNext(block, availableHead); |
| availableHead = block; |
| } |
| return availableHead; |
| } |
| |
| /** |
| * Sweep a block, freeing it and making it available if any live cells were found. |
| * if it contains no free objects. |
| * |
| * This is designed to be called in parallel by multiple collector threads. |
| */ |
| public void parallelSweepCells(Sweeper sweeper) { |
| for (int sizeClass = 0; sizeClass < sizeClassCount(); sizeClass++) { |
| Address block; |
| while(!(block = getSweepBlock(sizeClass)).isZero()) { |
| boolean liveBlock = sweepCells(sweeper, block, sizeClass); |
| if (!liveBlock) { |
| BlockAllocator.setNext(block, Address.zero()); |
| BlockAllocator.free(this, block); |
| } else { |
| lock.acquire(); |
| BlockAllocator.setNext(block, availableBlockHead.get(sizeClass)); |
| availableBlockHead.set(sizeClass, block); |
| lock.release(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Get a block for a parallel sweep. |
| * |
| * @param sizeClass The size class of the block to sweep. |
| * @return The block or zero if no blocks remain to be swept. |
| */ |
| private Address getSweepBlock(int sizeClass) { |
| lock.acquire(); |
| Address block; |
| |
| /* Flushed blocks */ |
| block = flushedBlockHead.get(sizeClass); |
| if (!block.isZero()) { |
| flushedBlockHead.set(sizeClass, BlockAllocator.getNext(block)); |
| lock.release(); |
| BlockAllocator.setNext(block, Address.zero()); |
| return block; |
| } |
| |
| /* Consumed blocks */ |
| block = consumedBlockHead.get(sizeClass); |
| if (!block.isZero()) { |
| flushedBlockHead.set(sizeClass, BlockAllocator.getNext(block)); |
| lock.release(); |
| BlockAllocator.setNext(block, Address.zero()); |
| return block; |
| } |
| |
| /* All swept! */ |
| lock.release(); |
| return Address.zero(); |
| } |
| |
| /** |
| * Does this block contain any live cells? |
| */ |
| @Inline |
| public boolean sweepCells(Sweeper sweeper, Address block, int sizeClass) { |
| Extent blockSize = Extent.fromIntSignExtend(BlockAllocator.blockSize(blockSizeClass[sizeClass])); |
| Address cursor = block.plus(blockHeaderSize[sizeClass]); |
| Address end = block.plus(blockSize); |
| Extent cellExtent = Extent.fromIntSignExtend(cellSize[sizeClass]); |
| boolean containsLive = false; |
| while (cursor.LT(end)) { |
| ObjectReference current = VM.objectModel.getObjectFromStartAddress(cursor); |
| boolean free = true; |
| if (!current.isNull()) { |
| free = !liveBitSet(current); |
| if (!free) { |
| free = sweeper.sweepCell(current); |
| if (free) unsyncClearLiveBit(current); |
| } |
| } |
| if (!free) { |
| containsLive = true; |
| } |
| cursor = cursor.plus(cellExtent); |
| } |
| return containsLive; |
| } |
| |
| /** |
| * A callback used to perform sweeping of a free list space. |
| */ |
| @Uninterruptible |
| public abstract static class Sweeper { |
| public abstract boolean sweepCell(ObjectReference object); |
| } |
| |
| /**************************************************************************** |
| * |
| * Live bit manipulation |
| */ |
| |
| /** |
| * Atomically set the live bit for a given object |
| * |
| * @param object The object whose live bit is to be set. |
| * @return True if the bit was changed to true. |
| */ |
| @Inline |
| public static boolean testAndSetLiveBit(ObjectReference object) { |
| return updateLiveBit(VM.objectModel.objectStartRef(object), true, true); |
| } |
| |
| /** |
| * Set the live bit for the block containing the given object |
| * |
| * @param object The object whose blocks liveness is to be set. |
| */ |
| @Inline |
| protected static void markBlock(ObjectReference object) { |
| BlockAllocator.markBlockMeta(object); |
| } |
| |
| /** |
| * Set the live bit for the given block. |
| * |
| * @param block The block whose liveness is to be set. |
| */ |
| @Inline |
| protected static void markBlock(Address block) { |
| BlockAllocator.markBlockMeta(block); |
| } |
| |
| /** |
| * Set the live bit for a given object, without using |
| * synchronization primitives---must only be used when contention |
| * for live bit is strictly not possible |
| * |
| * @param object The object whose live bit is to be set. |
| */ |
| @Inline |
| public static boolean unsyncSetLiveBit(ObjectReference object) { |
| return updateLiveBit(VM.objectModel.refToAddress(object), true, false); |
| } |
| |
| /** |
| * Set the live bit for a given address |
| * |
| * @param address The address whose live bit is to be set. |
| * @param set True if the bit is to be set, as opposed to cleared |
| * @param atomic True if we want to perform this operation atomically |
| */ |
| @Inline |
| private static boolean updateLiveBit(Address address, boolean set, boolean atomic) { |
| Word oldValue, newValue; |
| Address liveWord = getLiveWordAddress(address); |
| Word mask = getMask(address, true); |
| if (atomic) { |
| do { |
| oldValue = liveWord.prepareWord(); |
| newValue = (set) ? oldValue.or(mask) : oldValue.and(mask.not()); |
| } while (!liveWord.attempt(oldValue, newValue)); |
| } else { |
| oldValue = liveWord.loadWord(); |
| liveWord.store(set ? oldValue.or(mask) : oldValue.and(mask.not())); |
| } |
| return oldValue.and(mask).NE(mask); |
| } |
| |
| /** |
| * Test the live bit for a given object |
| * |
| * @param object The object whose live bit is to be set. |
| */ |
| @Inline |
| protected static boolean liveBitSet(ObjectReference object) { |
| return liveBitSet(VM.objectModel.refToAddress(object)); |
| } |
| |
| /** |
| * Set the live bit for a given address |
| * |
| * @param address The address whose live bit is to be set. |
| * @return true if this operation changed the state of the live bit. |
| */ |
| @Inline |
| protected static boolean liveBitSet(Address address) { |
| Address liveWord = getLiveWordAddress(address); |
| Word mask = getMask(address, true); |
| Word value = liveWord.loadWord(); |
| return value.and(mask).EQ(mask); |
| } |
| |
| /** |
| * Clear the live bit for a given object |
| * |
| * @param object The object whose live bit is to be cleared. |
| */ |
| @Inline |
| protected static void clearLiveBit(ObjectReference object) { |
| clearLiveBit(VM.objectModel.refToAddress(object)); |
| } |
| |
| /** |
| * Clear the live bit for a given address |
| * |
| * @param address The address whose live bit is to be cleared. |
| */ |
| @Inline |
| protected static void clearLiveBit(Address address) { |
| updateLiveBit(address, false, true); |
| } |
| |
| /** |
| * Clear the live bit for a given object |
| * |
| * @param object The object whose live bit is to be cleared. |
| */ |
| @Inline |
| protected static void unsyncClearLiveBit(ObjectReference object) { |
| unsyncClearLiveBit(VM.objectModel.refToAddress(object)); |
| } |
| |
| /** |
| * Clear the live bit for a given address |
| * |
| * @param address The address whose live bit is to be cleared. |
| */ |
| @Inline |
| protected static void unsyncClearLiveBit(Address address) { |
| updateLiveBit(address, false, false); |
| } |
| |
| /** |
| * Clear all live bits for a block |
| */ |
| protected void clearLiveBits(Address block, int sizeClass) { |
| int blockSize = BlockAllocator.blockSize(blockSizeClass[sizeClass]); |
| Address cursor = getLiveWordAddress(block); |
| Address sentinel = getLiveWordAddress(block.plus(blockSize - 1)); |
| while (cursor.LE(sentinel)) { |
| cursor.store(Word.zero()); |
| cursor = cursor.plus(BYTES_IN_WORD); |
| } |
| } |
| |
| /** |
| * Clear all live bits |
| */ |
| protected static void zeroLiveBits(Address start, Address end) { |
| Extent bytes = Extent.fromIntSignExtend(EmbeddedMetaData.BYTES_IN_REGION>>LOG_LIVE_COVERAGE); |
| while (start.LT(end)) { |
| Address metadata = EmbeddedMetaData.getMetaDataBase(start).plus(META_DATA_OFFSET); |
| VM.memory.zero(metadata, bytes); |
| start = start.plus(EmbeddedMetaData.BYTES_IN_REGION); |
| } |
| } |
| |
| /** |
| * Align an address so that it corresponds to a live word boundary. |
| * In other words, if the live bit for the given address is not the |
| * zeroth bit of a live word, round the address down such that it |
| * does. |
| * |
| * @param address The address to be aligned to a live word |
| * @return The given address, aligned down so that it corresponds to |
| * an address on a live word boundary. |
| */ |
| private static Address alignToLiveStride(Address address) { |
| return address.toWord().and(LIVE_WORD_STRIDE_MASK).toAddress(); |
| } |
| |
| /** |
| * Given an address, produce a bit mask for the live table |
| * |
| * @param address The address whose live bit mask is to be established |
| * @param set True if we want the mask for <i>setting</i> the bit, |
| * false if we want the mask for <i>clearing</i> the bit. |
| * @return The appropriate bit mask for object for the live table for. |
| */ |
| @Inline |
| private static Word getMask(Address address, boolean set) { |
| int shift = address.toWord().rshl(OBJECT_LIVE_SHIFT).and(WORD_SHIFT_MASK).toInt(); |
| Word rtn = Word.one().lsh(shift); |
| return (set) ? rtn : rtn.not(); |
| } |
| |
| /** |
| * Given an address, return the address of the live word for |
| * that address. |
| * |
| * @param address The address whose live word address is to be returned |
| * @return The address of the live word for this object |
| */ |
| @Inline |
| private static Address getLiveWordAddress(Address address) { |
| Address rtn = EmbeddedMetaData.getMetaDataBase(address); |
| return rtn.plus(META_DATA_OFFSET).plus(EmbeddedMetaData.getMetaDataOffset(address, LOG_LIVE_COVERAGE, LOG_BYTES_IN_WORD)); |
| } |
| } |