/* * 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.utility.alloc; import org.mmtk.policy.Space; import org.mmtk.policy.immix.Block; import org.mmtk.policy.immix.Chunk; import org.mmtk.policy.immix.Line; import org.mmtk.policy.immix.ImmixSpace; import static org.mmtk.policy.immix.ImmixConstants.*; import org.mmtk.utility.Constants; import org.mmtk.utility.Log; import org.mmtk.utility.options.Options; import org.mmtk.vm.VM; import org.vmmagic.unboxed.*; import org.vmmagic.pragma.*; /** * */ @Uninterruptible public class ImmixAllocator extends Allocator implements Constants { /**************************************************************************** * * Instance variables */ protected final ImmixSpace space; /* space this allocator is associated with */ private final boolean hot; private final boolean copy; private Address cursor; /* bump pointer */ private Address limit; /* limit for bump pointer */ private Address largeCursor; /* bump pointer for large objects */ private Address largeLimit; /* limit for bump pointer for large objects */ private boolean requestForLarge; /* is the current request for large or small? */ private boolean straddle; /* did the last allocation straddle a line? */ private int lineUseCount; /* approximation to bytes allocated (measured at 99% accurate) 07/10/30 */ private Address markTable; private Address recyclableBlock; private int line; private boolean recyclableExhausted; /** * Constructor. * * @param space The space to bump point into. * @param hot TODO * @param copy TODO */ public ImmixAllocator(ImmixSpace space, boolean hot, boolean copy) { this.space = space; this.hot = hot; this.copy = copy; reset(); } /** * Reset the allocator. Note that this does not reset the space. */ public void reset() { cursor = Address.zero(); limit = Address.zero(); largeCursor = Address.zero(); largeLimit = Address.zero(); markTable = Address.zero(); recyclableBlock = Address.zero(); requestForLarge = false; recyclableExhausted = false; line = LINES_IN_BLOCK; lineUseCount = 0; } /***************************************************************************** * * Public interface */ /** * Allocate space for a new object. This is frequently executed code and * the coding is deliberaetly sensitive to the optimizing compiler. * After changing this, always check the IR/MC that is generated. * * @param bytes The number of bytes allocated * @param align The requested alignment * @param offset The offset from the alignment * @return The address of the first byte of the allocated region */ @Inline public final Address alloc(int bytes, int align, int offset) { /* establish how much we need */ Address start = alignAllocationNoFill(cursor, align, offset); Address end = start.plus(bytes); /* check whether we've exceeded the limit */ if (end.GT(limit)) { if (bytes > BYTES_IN_LINE) return overflowAlloc(bytes, align, offset); else return allocSlowHot(bytes, align, offset); } /* sufficient memory is available, so we can finish performing the allocation */ fillAlignmentGap(cursor, start); cursor = end; return start; } /** * Allocate space for a new object. This is frequently executed code and * the coding is deliberaetly sensitive to the optimizing compiler. * After changing this, always check the IR/MC that is generated. * * @param bytes The number of bytes allocated * @param align The requested alignment * @param offset The offset from the alignment * @return The address of the first byte of the allocated region */ public final Address overflowAlloc(int bytes, int align, int offset) { /* establish how much we need */ Address start = alignAllocationNoFill(largeCursor, align, offset); Address end = start.plus(bytes); /* check whether we've exceeded the limit */ if (end.GT(largeLimit)) { requestForLarge = true; Address rtn = allocSlowInline(bytes, align, offset); requestForLarge = false; return rtn; } /* sufficient memory is available, so we can finish performing the allocation */ fillAlignmentGap(largeCursor, start); largeCursor = end; return start; } @Inline public final boolean getLastAllocLineStraddle() { return straddle; } /** * External allocation slow path (called by superclass when slow path is * actually taken. This is necessary (rather than a direct call * from the fast path) because of the possibility of a thread switch * and corresponding re-association of bump pointers to kernel * threads. * * @param bytes The number of bytes allocated * @param align The requested alignment * @param offset The offset from the alignment * @return The address of the first byte of the allocated region or * zero on failure */ protected final Address allocSlowOnce(int bytes, int align, int offset) { boolean success = false; while (!success) { Address ptr = space.getSpace(hot, copy, lineUseCount); if (ptr.isZero()) { lineUseCount = 0; return ptr; // failed allocation --- we will need to GC } /* we have been given a clean block */ success = true; lineUseCount = LINES_IN_BLOCK; if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(Block.isAligned(ptr)); zeroBlock(ptr); if (requestForLarge) { largeCursor = ptr; largeLimit = ptr.plus(BYTES_IN_BLOCK); } else { cursor = ptr; limit = ptr.plus(BYTES_IN_BLOCK); } } return alloc(bytes, align, offset); } /**************************************************************************** * * Bump allocation */ /** * Internal allocation slow path. This is called whenever the bump * pointer reaches the internal limit. The code is forced out of * line. If required we perform an external slow path take, which * we inline into this method since this is already out of line. * * @param bytes The number of bytes allocated * @param align The requested alignment * @param offset The offset from the alignment * @return The address of the first byte of the allocated region */ @NoInline private Address allocSlowHot(int bytes, int align, int offset) { if (acquireRecyclableLines(bytes, align, offset)) return alloc(bytes, align, offset); else return allocSlowInline(bytes, align, offset); } private boolean acquireRecyclableLines(int bytes, int align, int offset) { while (line < LINES_IN_BLOCK || acquireRecyclableBlock()) { line = space.getNextAvailableLine(markTable, line); if (line < LINES_IN_BLOCK) { int endLine = space.getNextUnavailableLine(markTable, line); cursor = recyclableBlock.plus(Extent.fromIntSignExtend(line<<LOG_BYTES_IN_LINE)); limit = recyclableBlock.plus(Extent.fromIntSignExtend(endLine<<LOG_BYTES_IN_LINE)); if (SANITY_CHECK_LINE_MARKS) { Address tmp = cursor; while (tmp.LT(limit)) { if (tmp.loadByte() != (byte) 0) { Log.write("cursor: "); Log.writeln(cursor); Log.write(" limit: "); Log.writeln(limit); Log.write("current: "); Log.write(tmp); Log.write(" value: "); Log.write(tmp.loadByte()); Log.write(" line: "); Log.write(line); Log.write("endline: "); Log.write(endLine); Log.write(" chunk: "); Log.write(Chunk.align(cursor)); Log.write(" hw: "); Log.write(Chunk.getHighWater(Chunk.align(cursor))); Log.writeln(" values: "); Address tmp2 = cursor; while(tmp2.LT(limit)) { Log.write(tmp2.loadByte()); Log.write(" ");} Log.writeln(); } VM.assertions._assert(tmp.loadByte() == (byte) 0); tmp = tmp.plus(1); } } if (VM.VERIFY_ASSERTIONS && bytes <= BYTES_IN_LINE) { Address start = alignAllocationNoFill(cursor, align, offset); Address end = start.plus(bytes); VM.assertions._assert(end.LE(limit)); } VM.memory.zero(cursor, limit.diff(cursor).toWord().toExtent()); if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9) { Log.write("Z["); Log.write(cursor); Log.write("->"); Log.write(limit); Log.writeln("]"); } line = endLine; if (VM.VERIFY_ASSERTIONS && copy) VM.assertions._assert(!Block.isDefragSource(cursor)); return true; } } return false; } private boolean acquireRecyclableBlock() { boolean rtn; rtn = acquireRecyclableBlockAddressOrder(); if (rtn) { markTable = Line.getBlockMarkTable(recyclableBlock); line = 0; } return rtn; } @Inline private boolean acquireRecyclableBlockAddressOrder() { if (recyclableExhausted) { if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9) { Log.writeln("[no recyclable available]"); } return false; } int markState = 0; boolean usable = false; while (!usable) { Address next = recyclableBlock.plus(BYTES_IN_BLOCK); if (recyclableBlock.isZero() || ImmixSpace.isRecycleAllocChunkAligned(next)) { recyclableBlock = space.acquireReusableBlocks(); if (recyclableBlock.isZero()) { recyclableExhausted = true; if (VM.VERIFY_ASSERTIONS && Options.verbose.getValue() >= 9) { Log.writeln("[recyclable exhausted]"); } line = LINES_IN_BLOCK; return false; } } else { recyclableBlock = next; } markState = Block.getBlockMarkState(recyclableBlock); usable = (markState > 0 && markState <= ImmixSpace.getReusuableMarkStateThreshold(copy)); if (copy && Block.isDefragSource(recyclableBlock)) usable = false; } if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!Block.isUnused(recyclableBlock)); Block.setBlockAsReused(recyclableBlock); lineUseCount += (LINES_IN_BLOCK-markState); return true; // found something good } private void zeroBlock(Address block) { // FIXME: efficiency check here! if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(block.toWord().and(Word.fromIntSignExtend(BYTES_IN_BLOCK-1)).isZero()); VM.memory.zero(block, Extent.fromIntZeroExtend(BYTES_IN_BLOCK)); } /** @return the space associated with this squish allocator */ public final Space getSpace() { return space; } /** * Print out the status of the allocator (for debugging) */ public final void show() { Log.write("cursor = "); Log.write(cursor); Log.write(" limit = "); Log.writeln(limit); } }