/* * 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.plan.markcompact.MC; import org.mmtk.utility.Log; import org.mmtk.utility.alloc.Allocator; import org.mmtk.utility.alloc.BumpPointer; import org.mmtk.vm.VM; import org.vmmagic.pragma.Inline; import org.vmmagic.pragma.Uninterruptible; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.Extent; import org.vmmagic.unboxed.ObjectReference; /** * This class implements unsynchronized (local) per-collector-thread elements of a * sliding mark-compact collector. * * Specifically, this class provides the methods that * - Calculate the forwarding pointers for heap objects, in a linear pass over * (part of) the heap * - Performs the compaction pass over the heap. * * Each collector thread maintains a private list of the pages that it compacts. * If it runs out of work during the calculateForwardingPointers pass, it requests * a new region from the global MarkCompactSpace. Regions compacted by a collector * remain local to the collector. * * @see MarkCompactSpace * @see MarkCompactLocal */ @Uninterruptible public final class MarkCompactCollector { static final boolean VERBOSE = false; static final boolean VERY_VERBOSE = VERBOSE && false; private final MarkCompactSpace space; /** * This collector's work list */ private Address regions = Address.zero(); private final FromCursor fromCursor = new FromCursor(); private final ToCursor toCursor = new ToCursor(); /** * Constructor * * @param space The space to bump point into. */ public MarkCompactCollector(MarkCompactSpace space) { this.space = space; } /* ******************************************************************************** * * Cursor classes * */ /** * Both the 'compact' and 'calculate' phases can be thought of as sweeping * a pair of cursors across a linked list of regions. Each cursor requires * maintaining pointers to the current region, the current address and the end of * the region. The regionCursor class maintains these 3 pointers, while the * subclasses ToCursor and FromCursor provide methods specific to the * read and write pointers. */ @Uninterruptible private abstract static class RegionCursor { /** Name of the cursor - for debugging messages */ private final String name; /** * The current region, or zero if the cursor is invalid (eg after advancing * past the end of the current work list */ protected Address region; /** * The limit of the current region. When reading a populated region, this is the * address of the last used byte. When writing to a fresh region, this is the last * byte in the region. */ protected Address limit; /** The current address */ protected Address cursor; /** * @param name The name of the region - for debugging messages. */ public RegionCursor(String name) { this.name = name; } /** * Hook to allow subclasses to initialize the cursor in different ways. * * @param region The region to be processed. */ abstract void init(Address region); /** * Assert that the cursor is within the bounds of the region. Calls to this * must be guarded by {@code if (VM.VERIFY_ASSERTIONS)} */ protected void assertCursorInBounds() { VM.assertions._assert(!region.isZero()); VM.assertions._assert(cursor.GE(BumpPointer.getDataStart(region)), "Cursor is below start of region"); VM.assertions._assert(cursor.LE(limit),"Cursor beyond end of region"); } /** * Increment the cursor. * @param size Bytes to increment by */ void inc(int size) { this.cursor = cursor.plus(size); if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); } /** * Increment the cursor to a specific address * @param cursor Destination address */ public void incTo(Address cursor) { if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(cursor.GE(this.cursor)); this.cursor = cursor; } /** * @param other Other region * @return {@code true} if this cursor points to the same region as {@code other} */ boolean sameRegion(RegionCursor other) { return region.EQ(other.getRegion()); } /** * @param size Size in bytes * @return {@code true} if {@code size} bytes are available in the current region */ boolean isAvailable(int size) { return cursor.plus(size).LE(limit); } /** * @return The current cursor */ public Address get() { return cursor; } /** * @return The current region pointer */ public Address getRegion() { return region; } /** * @return The current region limit */ public Address getLimit() { return limit; } /** * Follow the linked-list of regions to the next region. */ void advanceToNextRegion() { Address nextRegion = MarkCompactLocal.getNextRegion(region); if (nextRegion.isZero()) { region = Address.zero(); } else { init(nextRegion); if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); } } /** * @return {@code true} if we haven't advanced beyond the end of the region list */ boolean isValid() { return !region.isZero(); } /** * @param ref The object in question * @return {@code true} if the object's start address is in this region */ @Inline boolean isInRegion(ObjectReference ref) { Address addr = VM.objectModel.refToAddress(ref); return addr.GE(BumpPointer.getDataStart(region)) && addr.LE(limit); } /** * Print the cursor - for debugging */ void print() { Log.write(name); Log.write(" cursor:"); Log.write(" region="); Log.write(region); Log.write(" limit="); Log.write(limit); Log.write(" cursor="); Log.write(cursor); Log.writeln(); } } /** * Subclass for the read-only cursor that leads the scan of regions. */ @Uninterruptible private static final class FromCursor extends RegionCursor { public FromCursor() { super("from"); } /** * Initialize the cursor - the limit is the end of the allocated data */ @Override void init(Address region) { if (VM.VERIFY_ASSERTIONS) BumpPointer.checkRegionMetadata(region); this.region = region; this.cursor = MarkCompactLocal.getDataStart(region); this.limit = MarkCompactLocal.getDataEnd(region); } /** * Advance from the cursor to the start of the next object. * @return The object reference of the next object. */ @Inline ObjectReference advanceToObject() { ObjectReference current = VM.objectModel.getObjectFromStartAddress(cursor); cursor = VM.objectModel.objectStartRef(current); if (VM.VERIFY_ASSERTIONS) { Address lowBound = BumpPointer.getDataStart(region); VM.assertions._assert(cursor.GE(lowBound) && cursor.LE(limit),"Cursor outside region"); } return current; } /** * Advance the cursor to the end of the given object. * @return The object reference of the next object. */ @Inline void advanceToObjectEnd(ObjectReference current) { cursor = VM.objectModel.getObjectEndAddress(current); if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); } /** * Advance the cursor either to the next region in the list, * or to a new region allocated from the global list. * @param m */ void advanceToNextForwardableRegion(MarkCompactSpace space) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(get().EQ(getLimit())); Address nextRegion = BumpPointer.getNextRegion(region); if (nextRegion.isZero()) { nextRegion = space.getNextRegion(); if (nextRegion.isZero()) { region = Address.zero(); return; } MarkCompactLocal.setNextRegion(region,nextRegion); MarkCompactLocal.clearNextRegion(nextRegion); } init(nextRegion); if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); } /** * Override the superclass with an additional assertion - we only advance * when we have read to the end, and the cursor must point *precisely* * to the last allocated byte in the region. */ @Override void advanceToNextRegion() { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(get().EQ(getLimit())); super.advanceToNextRegion(); } /** * @return {@code true} if there are more objects in this region */ boolean hasMoreObjects() { return cursor.LT(limit); } } /** * Subclass for the read-only cursor that follows the 'from' cursor, * writing or calculating the position of copied objects */ @Uninterruptible private static final class ToCursor extends RegionCursor { public ToCursor() { super("to"); } /** * Initialize the cursor to a given region. The limit is the limit of * available space in the region. */ @Override void init(Address region) { if (VM.VERIFY_ASSERTIONS) BumpPointer.checkRegionMetadata(region); this.region = region; this.cursor = MarkCompactLocal.getDataStart(region); this.limit = MarkCompactLocal.getRegionLimit(region); if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); } /** * Update the metadata of the current region with the current value * of the cursor. Zero the region from here to the end. */ void finish() { if (VM.VERIFY_ASSERTIONS) assertCursorInBounds(); Extent zeroBytes = limit.diff(cursor).toWord().toExtent(); VM.memory.zero(cursor, zeroBytes); MarkCompactLocal.setDataEnd(region, cursor); MarkCompactLocal.checkRegionMetadata(region); } /** * Terminate the list of regions here. * @return The address of the (old) next region in the list. */ Address snip() { Address nextRegion = BumpPointer.getNextRegion(region); BumpPointer.clearNextRegion(region); finish(); return nextRegion; } /** * Copy an object to an address within this cursor's region. * @param from The source object * @param to The target object */ @Inline void copy(ObjectReference from, ObjectReference to) { if (VM.VERIFY_ASSERTIONS) { VM.assertions._assert(MarkCompactSpace.getForwardingPointer(from).toAddress().EQ(to.toAddress())); VM.assertions._assert(cursor.GT(region) && cursor.LE(limit)); } Address savedCursor = Address.zero(); if (VM.VERIFY_ASSERTIONS) savedCursor = cursor; cursor = VM.objectModel.copyTo(from, to, cursor); if (VM.VERIFY_ASSERTIONS) { if (cursor.LT(BumpPointer.getDataStart(region)) || cursor.GT(limit)) { Log.write("Copy of "); Log.write(from); Log.write(" to "); Log.write(to); Log.write(" puts cursor at "); Log.write(cursor); Log.write(" (was: "); Log.write(savedCursor); Log.writeln(")"); } VM.assertions._assert(cursor.GT(region) && cursor.LE(limit)); } MarkCompactSpace.setForwardingPointer(to, ObjectReference.nullReference()); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(VM.objectModel.getObjectEndAddress(to).LE(limit)); } /** * Move to the next region, updating the metadata with the current 'write' state. */ void finishAndAdvanceToNextRegion() { finish(); advanceToNextRegion(); } /** * Move to the next region, in read-only mode. Add the assertion of validity, * since we shouldn't be able to fall off the end of the list while writing. */ @Override void advanceToNextRegion() { super.advanceToNextRegion(); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(isValid()); } } /* ***************************************************************************************** */ /** * Perform a linear scan through the objects allocated by this bump pointer, * calculating where each live object will be post collection. * * We maintain two cursors, {@code fromCursor} and {@code toCursor}, and simulate * copying live objects from the former to the latter. Initially, the cursors * point to the first region in this collector's local list, and increment in * lockstep until the first dead object is encountered. After that, the to cursor * trails the from cursor. * * The outer loop advances the 'from' pointer */ public void calculateForwardingPointers() { if (regions.isZero()) { regions = space.getNextRegion(); } if (regions.isZero()) return; fromCursor.init(regions); toCursor.init(regions); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(true); /* Loop through active regions or until the last region */ while (fromCursor.isValid()) { if (VERBOSE) { fromCursor.print(); toCursor.print(); } /* Loop through the objects in the current 'from' region */ while (fromCursor.hasMoreObjects()) { ObjectReference current = fromCursor.advanceToObject(); fromCursor.advanceToObjectEnd(current); if (MarkCompactSpace.toBeCompacted(current)) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(MarkCompactSpace.getForwardingPointer(current).isNull()); // Fake - allocate it. int size = VM.objectModel.getSizeWhenCopied(current); int align = VM.objectModel.getAlignWhenCopied(current); int offset = VM.objectModel.getAlignOffsetWhenCopied(current); // Move to the (aligned) start of the next object toCursor.incTo(Allocator.alignAllocationNoFill(toCursor.get(), align, offset)); /* * If we're allocating into separate regions, and we've allocated beyond the end of the * current region, advance to the next one. We always allocate into regions we have * scanned in this collector. */ if (!toCursor.sameRegion(fromCursor) && !toCursor.isAvailable(size)) { // The 'to' pointer always trails the 'from' pointer, guaranteeing that // there's a next region to advance to. toCursor.advanceToNextRegion(); toCursor.incTo(Allocator.alignAllocationNoFill(toCursor.get(), align, offset)); } ObjectReference target = VM.objectModel.getReferenceWhenCopiedTo(current, toCursor.get()); if (toCursor.sameRegion(fromCursor) && target.toAddress().GE(current.toAddress())) { // Don't move the object. MarkCompactSpace.setForwardingPointer(current, current); toCursor.incTo(VM.objectModel.getObjectEndAddress(current)); } else { MarkCompactSpace.setForwardingPointer(current, target); toCursor.inc(size); } } } fromCursor.advanceToNextForwardableRegion(space); } } /** * Perform the compacting phase of the collection. */ public void compact() { if (regions.isZero()) return; toCursor.init(regions); fromCursor.init(regions); /* Loop through active regions or until the last region */ while (fromCursor.isValid()) { if (VERBOSE) { Log.write("Compacting from region "); Log.write(fromCursor.getRegion()); Log.write(" to region "); Log.writeln(toCursor.getRegion()); } /* Loop through the objects in the region */ while (fromCursor.hasMoreObjects()) { ObjectReference current = fromCursor.advanceToObject(); fromCursor.advanceToObjectEnd(current); ObjectReference copyTo = MarkCompactSpace.getForwardingPointer(current); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!copyTo.toAddress().EQ(Address.fromIntZeroExtend(VM.ALIGNMENT_VALUE))); if (!copyTo.isNull() && Space.isInSpace(MC.MARK_COMPACT, copyTo)) { if (VM.VERIFY_ASSERTIONS) { if (MarkCompactSpace.isMarked(current)) { Log.write("Object "); Log.write(current); Log.writeln(" is marked during the compact phase"); } VM.assertions._assert(!MarkCompactSpace.isMarked(current)); } if (!toCursor.isInRegion(copyTo)) { // Update metadata and move on toCursor.finishAndAdvanceToNextRegion(); } if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(toCursor.isInRegion(copyTo)); toCursor.copy(current, copyTo); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(toCursor.isInRegion(copyTo)); MarkCompactSpace.setForwardingPointer(copyTo, ObjectReference.nullReference()); } } fromCursor.advanceToNextRegion(); } /* Fix up the last object pointer etc */ toCursor.finish(); /* * Return unused pages to the global page resource */ Address region = toCursor.snip(); while (!region.isZero()) { Address nextRegion = MarkCompactLocal.getNextRegion(region); space.release(region); region = nextRegion; } } }