/* * 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.jikesrvm.mm.mmtk; import org.mmtk.plan.TraceLocal; import org.mmtk.utility.Constants; import org.mmtk.utility.Log; import org.jikesrvm.VM; import org.jikesrvm.runtime.BootRecord; import org.jikesrvm.runtime.Magic; import org.jikesrvm.scheduler.RVMThread; import org.jikesrvm.mm.mminterface.CollectorThread; import org.jikesrvm.mm.mminterface.MemoryManager; import org.vmmagic.unboxed.*; import org.vmmagic.pragma.*; /** * Scan the boot image for references using the boot image reference map */ public class ScanBootImage implements Constants { private static final boolean DEBUG = false; private static final boolean FILTER = true; private static final int LOG_CHUNK_BYTES = 12; private static final int CHUNK_BYTES = 1<<LOG_CHUNK_BYTES; private static final int LONGENCODING_MASK = 0x1; private static final int RUN_MASK = 0x2; private static final int MAX_RUN = (1<<BITS_IN_BYTE)-1; private static final int LONGENCODING_OFFSET_BYTES = 4; private static final int GUARD_REGION = LONGENCODING_OFFSET_BYTES + 1; /* long offset + run encoding */ /* statistics */ static int roots = 0; static int refs = 0; /**************************************************************************** * * GC-time decoding (multi-threaded) */ /** * Scan the boot image for object references. Executed by * all GC threads in parallel, with each doing a portion of the * boot image. * * @param trace The trace object to which the roots should be added */ @Inline @Uninterruptible public static void scanBootImage(TraceLocal trace) { /* establish sentinals in map & image */ Address mapStart = BootRecord.the_boot_record.bootImageRMapStart; Address mapEnd = BootRecord.the_boot_record.bootImageRMapEnd; Address imageStart = BootRecord.the_boot_record.bootImageDataStart; /* figure out striding */ int stride = CollectorThread.numCollectors()<<LOG_CHUNK_BYTES; CollectorThread collector = Magic.threadAsCollectorThread(RVMThread.getCurrentThread()); int start = (collector.getGCOrdinal() - 1)<<LOG_CHUNK_BYTES; Address cursor = mapStart.plus(start); /* statistics */ roots = 0; refs = 0; /* process chunks in parallel till done */ while (cursor.LT(mapEnd)) { processChunk(cursor, imageStart, mapStart, mapEnd, trace); cursor = cursor.plus(stride); } /* print some debugging stats */ if (DEBUG) { Log.write("<boot image"); Log.write(" roots: "); Log.write(roots); Log.write(" refs: "); Log.write(refs); Log.write(">"); } } /** * Process a chunk of encoded reference data, enqueuing each * reference (optionally filtering them on whether they point * outside the boot image). * * @param chunkStart The address of the first byte of encoded data * @param imageStart The address of the start of the boot image * @param mapStart The address of the start of the encoded reference map * @param mapEnd The address of the end of the encoded reference map * @param trace The <code>TraceLocal</code> into which roots should * be enqueued. */ @Inline @Uninterruptible private static void processChunk(Address chunkStart, Address imageStart, Address mapStart, Address mapEnd, TraceLocal trace) { int value; Offset offset = Offset.zero(); Address cursor = chunkStart; while ((value = (cursor.loadByte() & 0xff)) != 0) { /* establish the offset */ if ((value & LONGENCODING_MASK) != 0) { offset = decodeLongEncoding(cursor); cursor = cursor.plus(LONGENCODING_OFFSET_BYTES); } else { offset = offset.plus(value & 0xfc); cursor = cursor.plus(1); } /* figure out the length of the run, if any */ int runlength = 0; if ((value & RUN_MASK) != 0) { runlength = cursor.loadByte() & 0xff; cursor = cursor.plus(1); } /* enqueue the specified slot or slots */ if (VM.VerifyAssertions) VM._assert(isAddressAligned(offset)); Address slot = imageStart.plus(offset); if (DEBUG) refs++; if (!FILTER || slot.loadAddress().GT(mapEnd)) { if (DEBUG) roots++; trace.processRootEdge(slot, false); } if (runlength != 0) { for (int i = 0; i < runlength; i++) { offset = offset.plus(BYTES_IN_ADDRESS); slot = imageStart.plus(offset); if (VM.VerifyAssertions) VM._assert(isAddressAligned(slot)); if (DEBUG) refs++; if (!FILTER || slot.loadAddress().GT(mapEnd)) { if (DEBUG) roots++; if (ScanThread.VALIDATE_REFS) checkReference(slot); trace.processRootEdge(slot, false); } } } } } /** * Check that a reference encountered during scanning is valid. If * the reference is invalid, dump stack and die. * * @param refaddr The address of the reference in question. */ @Uninterruptible private static void checkReference(Address refaddr) { ObjectReference ref = org.mmtk.vm.VM.activePlan.global().loadObjectReference(refaddr); if (!MemoryManager.validRef(ref)) { Log.writeln(); Log.writeln("Invalid ref reported while scanning boot image"); Log.writeln(); Log.write(refaddr); Log.write(":"); Log.flush(); MemoryManager.dumpRef(ref); Log.writeln(); Log.writeln("Dumping stack:"); RVMThread.dumpStack(); VM.sysFail("\n\nScanStack: Detected bad GC map; exiting RVM with fatal error"); } } /** * Return true if the given offset is address-aligned * @param offset the offset to be check * @return true if the offset is address aligned. */ @Uninterruptible private static boolean isAddressAligned(Offset offset) { return (offset.toLong()>>LOG_BYTES_IN_ADDRESS)<<LOG_BYTES_IN_ADDRESS == offset.toLong(); } /** * Return true if the given address is address-aligned * @param address the address to be check * @return true if the address is address aligned. */ @Uninterruptible private static boolean isAddressAligned(Address address) { return (address.toLong()>>LOG_BYTES_IN_ADDRESS)<<LOG_BYTES_IN_ADDRESS == address.toLong(); } /**************************************************************************** * * Build-time encoding (assumed to be single-threaded) */ private static int lastOffset = Integer.MIN_VALUE / 2; /* bootstrap value */ private static int oldIndex = 0; private static int codeIndex = 0; /* statistics */ private static int shortRefs = 0; private static int runRefs = 0; private static int longRefs = 0; private static int startRefs = 0; /** * Take a bytemap encoding of all references in the boot image, and * produce an encoded byte array. Return the total length of the * encoding. */ public static int encodeRMap(byte[] bootImageRMap, byte[] referenceMap, int referenceMapLimit) { for (int index = 0; index <= referenceMapLimit; index++) { if (referenceMap[index] == 1) { addOffset(bootImageRMap, index<<LOG_BYTES_IN_ADDRESS); } } return codeIndex + 1; } /** * Print some basic statistics about the encoded references, for * debugging purposes. */ public static void encodingStats() { if (DEBUG) { Log.write("refs: "); Log.writeln(startRefs + shortRefs + longRefs + runRefs); Log.write("start: "); Log.writeln(startRefs); Log.write("short: "); Log.writeln(shortRefs); Log.write("long: "); Log.writeln(longRefs); Log.write("run: "); Log.writeln(runRefs); Log.write("size: "); Log.writeln(codeIndex); } } /** * Encode a given offset (distance from the start of the boot image) * into the code array. * * @param code A byte array into which the value should be encoded * @param offset The offset value to be encoded */ private static void addOffset(byte[] code, int offset) { if ((codeIndex ^ (codeIndex + GUARD_REGION)) >= CHUNK_BYTES) { codeIndex = (codeIndex + GUARD_REGION) & ~(CHUNK_BYTES - 1); oldIndex = codeIndex; codeIndex = encodeLongEncoding(code, codeIndex, offset); if (DEBUG) { startRefs++; Log.write("[chunk: "); Log.write(codeIndex); Log.write(" offset: "); Log.write(offset); Log.write(" last offset: "); Log.write(lastOffset); Log.writeln("]"); } } else { int delta = offset - lastOffset; if (VM.VerifyAssertions) VM._assert((delta & 0x3) == 0); if (VM.VerifyAssertions) VM._assert(delta > 0); int currentrun = ((int) code[codeIndex]) & 0xff; if ((delta == BYTES_IN_ADDRESS) && (currentrun < MAX_RUN)) { currentrun++; code[codeIndex] = (byte) (currentrun & 0xff); code[oldIndex] |= RUN_MASK; if (DEBUG) runRefs++; } else { if (currentrun != 0) codeIndex++; oldIndex = codeIndex; if (delta < 1<<BITS_IN_BYTE) { /* common case: single byte encoding */ code[codeIndex++] = (byte) (delta & 0xff); if (DEBUG) shortRefs++; } else { /* else four byte encoding */ codeIndex = encodeLongEncoding(code, codeIndex, offset); if (DEBUG) longRefs++; } } } if (offset != getOffset(code, oldIndex, lastOffset)) { Log.write("offset: "); Log.writeln(offset); Log.write("last offset: "); Log.writeln(lastOffset); Log.write("offset: "); Log.writeln(getOffset(code, oldIndex, lastOffset)); Log.write("index: "); Log.writeln(oldIndex); Log.write("index: "); Log.writeln(oldIndex & (CHUNK_BYTES - 1)); Log.writeln(); Log.write("1: "); Log.writeln(code[oldIndex]); Log.write("2: "); Log.writeln(code[oldIndex+1]); Log.write("3: "); Log.writeln(code[oldIndex+2]); Log.write("4: "); Log.writeln(code[oldIndex+3]); Log.write("5: "); Log.writeln(code[oldIndex+4]); if (VM.VerifyAssertions) VM._assert(offset == getOffset(code, oldIndex, lastOffset)); } lastOffset = offset; } /**************************************************************************** * * Utility encoding and decoding methods */ /** * Decode an encoded offset given the coded byte array, and index * into it, and the current (last) offset. * * @param code A byte array containing the encoded value * @param index The offset into the code array from which to * commence decoding * @param lastOffset The current (last) encoded offset * @return The next offset, which is either explicitly encoded in * the byte array or inferred from an encoded delta and the last * offset. */ private static int getOffset(byte[] code, int index, int lastOffset) { if (((int) code[index] & RUN_MASK) == RUN_MASK) { return lastOffset + BYTES_IN_WORD; } else { if (((index & (CHUNK_BYTES - 1)) == 0) || (((int) code[index] &LONGENCODING_MASK) == LONGENCODING_MASK)) { return decodeLongEncoding(code, index); } else { return lastOffset + (((int) code[index]) & 0xff); } } } /** * Decode a 4-byte encoding, taking a pointer to the first byte of * the encoding, and returning the encoded value as an <code>Offset</code> * * @param cursor A pointer to the first byte of encoded data * @return The encoded value as an <code>Offset</code> */ @Inline @Uninterruptible private static Offset decodeLongEncoding(Address cursor) { int value; value = ((int) cursor.loadByte()) & 0x000000fc; value |= ((int) cursor.loadByte(Offset.fromIntSignExtend(1))<<BITS_IN_BYTE) & 0x0000ff00; value |= ((int) cursor.loadByte(Offset.fromIntSignExtend(2))<<(2*BITS_IN_BYTE)) & 0x00ff0000; value |= ((int) cursor.loadByte(Offset.fromIntSignExtend(3))<<(3*BITS_IN_BYTE)) & 0xff000000; return Offset.fromIntSignExtend(value); } /** * Decode a 4-byte encoding, taking a byte array and an index into * it and returning the encoded value as an integer * * @param code A byte array containing the encoded value * @param index The offset into the code array from which to * commence decoding * @return The encoded value as an integer */ @Inline @Uninterruptible private static int decodeLongEncoding(byte[] code, int index) { int value; value = ((int) code[index]) & 0x000000fc; value |= ((int) code[index+1]<<BITS_IN_BYTE) & 0x0000ff00; value |= ((int) code[index+2]<<(2*BITS_IN_BYTE)) & 0x00ff0000; value |= ((int) code[index+3]<<(3*BITS_IN_BYTE)) & 0xff000000; return value; } /** * Encode a 4-byte encoding, taking a byte array, the current index into * it, and the value to be encoded. * * @param code A byte array to containthe encoded value * @param index The current offset into the code array * @param value The value to be encoded * @return The updated index into the code array */ private static int encodeLongEncoding(byte[] code, int index, int value) { code[index++] = (byte) ((value & 0xff) | LONGENCODING_MASK); value = value >>> BITS_IN_BYTE; code[index++] = (byte) (value & 0xff); value = value >>> BITS_IN_BYTE; code[index++] = (byte) (value & 0xff); value = value >>> BITS_IN_BYTE; code[index++] = (byte) (value & 0xff); return index; } }