blob: e3962944699124353550ba3b3a41a07738a1e39f [file] [log] [blame]
/*
* 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));
}
}