| /* |
| * 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); |
| } |
| } |