/*
* 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.plan.TransitiveClosure;
import org.mmtk.utility.Constants;
import org.jikesrvm.jni.JNIEnvironment;
import org.jikesrvm.jni.JNIGlobalRefTable;
import org.jikesrvm.mm.mminterface.Selected;
import org.jikesrvm.mm.mminterface.CollectorThread;
import org.jikesrvm.mm.mminterface.MemoryManagerConstants;
import org.jikesrvm.mm.mminterface.SpecializedScanMethod;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.scheduler.RVMThread;
import org.vmmagic.unboxed.*;
import org.vmmagic.pragma.*;
@Uninterruptible
public final class Scanning extends org.mmtk.vm.Scanning implements Constants {
/****************************************************************************
*
* Class variables
*/
private static final boolean TRACE_PRECOPY = false; // DEBUG
/** Counter to track index into thread table for root tracing. */
private static final SynchronizedCounter threadCounter = new SynchronizedCounter();
/** Status flag used to determine if stacks were scanned in this collection increment */
private static boolean threadStacksScanned = false;
/**
* Were thread stacks scanned in this collection increment.
*/
public static boolean threadStacksScanned() {
return threadStacksScanned;
}
/**
* Clear the flag that indicates thread stacks have been scanned.
*/
public static void clearThreadStacksScanned() {
threadStacksScanned = false;
}
/**
* Scanning of a object, processing each pointer field encountered.
*
* @param trace The closure being used.
* @param object The object to be scanned.
*/
@Inline
public void scanObject(TransitiveClosure trace, ObjectReference object) {
SpecializedScanMethod.fallback(object.toObject(), trace);
}
/**
* Invoke a specialized scan method. Note that these methods must have been allocated
* explicitly through Plan and PlanConstraints.
*
* @param id The specialized method id
* @param trace The trace the method has been specialized for
* @param object The object to be scanned
*/
@Inline
public void specializedScanObject(int id, TransitiveClosure trace, ObjectReference object) {
if (SpecializedScanMethod.ENABLED) {
SpecializedScanMethod.invoke(id, object.toObject(), trace);
} else {
SpecializedScanMethod.fallback(object.toObject(), trace);
}
}
/**
* Precopying of a object's fields, processing each pointer field encountered.
*
* @param trace The trace being used.
* @param object The object to be scanned.
*/
@Inline
public void precopyChildren(TraceLocal trace, ObjectReference object) {
RVMType type = ObjectModel.getObjectType(object.toObject());
if (type.isClassType()) {
RVMClass klass = type.asClass();
int[] offsets = klass.getReferenceOffsets();
for(int i=0; i < offsets.length; i++) {
trace.processPrecopyEdge(object.toAddress().plus(offsets[i]), false);
}
} else if (type.isArrayType() && type.asArray().getElementType().isReferenceType()) {
for(int i=0; i < ObjectModel.getArrayLength(object.toObject()); i++) {
trace.processPrecopyEdge(object.toAddress().plus(i << LOG_BYTES_IN_ADDRESS), false);
}
}
}
/**
* Prepares for using the <code>computeAllRoots</code> method. The
* thread counter allows multiple GC threads to co-operatively
* iterate through the thread data structure (if load balancing
* parallel GC threads were not important, the thread counter could
* simply be replaced by a for loop).
*/
public void resetThreadCounter() {
threadCounter.reset();
}
/**
* Pre-copy all potentially movable instances used in the course of
* GC. This includes the thread objects representing the GC threads
* themselves. It is crucial that these instances are forwarded
* <i>prior</i> to the GC proper. Since these instances <i>are
* not</i> enqueued for scanning, it is important that when roots
* are computed the same instances are explicitly scanned and
* included in the set of roots. The existence of this method
* allows the actions of calculating roots and forwarding GC
* instances to be decoupled.
*
* The thread table is scanned in parallel by each processor, by striding
* through the table at a gap of chunkSize*numProcs. Feel free to adjust
* chunkSize if you want to tune a parallel collector.
*
* Explicitly no-inlined to prevent over-inlining of collectionPhase.
*
* TODO Experiment with specialization to remove virtual dispatch ?
*/
@NoInline
public void preCopyGCInstances(TraceLocal trace) {
int chunkSize = 2;
int threadIndex, start, end, stride;
CollectorThread ct;
stride = chunkSize * CollectorThread.numCollectors();
ct = Magic.threadAsCollectorThread(RVMThread.getCurrentThread());
start = (ct.getGCOrdinal() - 1) * chunkSize;
int numThreads = RVMThread.numThreads;
if (TRACE_PRECOPY)
VM.sysWriteln(ct.getGCOrdinal()," preCopying ",numThreads," threads");
ObjectReference threadTable = ObjectReference.fromObject(RVMThread.threads);
while (start < numThreads) {
end = start + chunkSize;
if (end > numThreads)
end = numThreads; // End of the table - partial chunk
if (TRACE_PRECOPY) {
VM.sysWriteln(ct.getGCOrdinal()," Chunk start",start);
VM.sysWriteln(ct.getGCOrdinal()," Chunk end ",end);
}
for (threadIndex = start; threadIndex < end; threadIndex++) {
RVMThread thread = RVMThread.threads[threadIndex];
if (thread != null) {
// FIXME: is this even remotely needed?
/* Copy the thread object - use address arithmetic to get the address
* of the array entry */
if (TRACE_PRECOPY) {
VM.sysWriteln(ct.getGCOrdinal()," Forwarding thread ",threadIndex);
VM.sysWriteln(ct.getGCOrdinal()," with slot number ",thread.getThreadSlot());
VM.sysWrite(ct.getGCOrdinal()," Old address ");
VM.sysWriteln(ObjectReference.fromObject(thread).toAddress());
}
Address threadTableSlot = threadTable.toAddress().plus(threadIndex<<LOG_BYTES_IN_ADDRESS);
if (VM.VerifyAssertions) {
Address a = ObjectReference.fromObject(thread).toAddress();
Address b = Selected.Plan.get().loadObjectReference(threadTableSlot).toAddress();
VM._assert(a.EQ(b), "Thread table address arithmetic is wrong!");
}
trace.processPrecopyEdge(threadTableSlot, false);
// don't need to reload thread from thread table slot because threads
// are non-moving
if (TRACE_PRECOPY) {
VM.sysWrite(ct.getGCOrdinal()," New address ");
VM.sysWriteln(ObjectReference.fromObject(thread).toAddress());
}
precopyChildren(trace, ObjectReference.fromObject(thread));
/* Registers */
if (TRACE_PRECOPY) {
VM.sysWriteln(ct.getGCOrdinal()," old cr address: ",Magic.objectAsAddress(thread.getContextRegisters()));
}
trace.processPrecopyEdge(Magic.objectAsAddress(thread).plus(Entrypoints.threadContextRegistersField.getOffset()), true);
if (TRACE_PRECOPY) {
VM.sysWriteln(ct.getGCOrdinal()," for thread ",Magic.objectAsAddress(thread));
VM.sysWriteln(ct.getGCOrdinal()," new cr address: ",Magic.objectAsAddress(thread.getContextRegisters()));
}
trace.processPrecopyEdge(Magic.objectAsAddress(thread).plus(Entrypoints.threadContextRegistersSaveField.getOffset()), true);
trace.processPrecopyEdge(Magic.objectAsAddress(thread).plus(Entrypoints.threadExceptionRegistersField.getOffset()), true);
if (thread.getJNIEnv() != null) {
// Right now, jniEnv are Java-visible objects (not C-visible)
// if (VM.VerifyAssertions)
// VM._assert(Plan.willNotMove(Magic.objectAsAddress(thread.jniEnv)));
trace.processPrecopyEdge(Magic.objectAsAddress(thread).plus(Entrypoints.jniEnvField.getOffset()), true);
trace.processPrecopyEdge(Magic.objectAsAddress(thread.getJNIEnv()).plus(Entrypoints.JNIRefsField.getOffset()), true);
trace.processPrecopyEdge(Magic.objectAsAddress(thread.getJNIEnv()).plus(Entrypoints.JNIEnvSavedTRField.getOffset()), true);
trace.processPrecopyEdge(Magic.objectAsAddress(thread.getJNIEnv()).plus(Entrypoints.JNIPendingExceptionField.getOffset()), true);
}
}
} // end of for loop
start = start + stride;
}
}
/**
* Computes static roots. This method establishes all such roots for
* collection and places them in the root locations queue. This method
* should not have side effects (such as copying or forwarding of
* objects). There are a number of important preconditions:
*
* <ul>
* <li> All objects used in the course of GC (such as the GC thread
* objects) need to be "pre-copied" prior to calling this method.
* <li> The <code>threadCounter</code> must be reset so that load
* balancing parallel GC can share the work of scanning threads.
* </ul>
*
* @param trace The trace to use for computing roots.
*/
public void computeStaticRoots(TraceLocal trace) {
/* scan statics */
ScanStatics.scanStatics(trace);
}
/**
* Computes global roots. This method establishes all such roots for
* collection and places them in the root locations queue. This method
* should not have side effects (such as copying or forwarding of
* objects). There are a number of important preconditions:
*
* <ul>
* <li> All objects used in the course of GC (such as the GC thread
* objects) need to be "pre-copied" prior to calling this method.
* <li> The <code>threadCounter</code> must be reset so that load
* balancing parallel GC can share the work of scanning threads.
* </ul>
*
* @param trace The trace to use for computing roots.
*/
public void computeGlobalRoots(TraceLocal trace) {
/* scan jni functions */
CollectorThread ct = Magic.threadAsCollectorThread(RVMThread.getCurrentThread());
Address jniFunctions = Magic.objectAsAddress(JNIEnvironment.JNIFunctions);
int threads = CollectorThread.numCollectors();
int size = JNIEnvironment.JNIFunctions.length();
int chunkSize = size / threads;
int start = (ct.getGCOrdinal() - 1) * chunkSize;
int end = (ct.getGCOrdinal() == threads) ? size : ct.getGCOrdinal() * chunkSize;
for(int i=start; i < end; i++) {
trace.processRootEdge(jniFunctions.plus(i << LOG_BYTES_IN_ADDRESS), true);
}
Address linkageTriplets = Magic.objectAsAddress(JNIEnvironment.LinkageTriplets);
if (linkageTriplets != null) {
for(int i=start; i < end; i++) {
trace.processRootEdge(linkageTriplets.plus(i << LOG_BYTES_IN_ADDRESS), true);
}
}
/* scan jni global refs */
Address jniGlobalRefs = Magic.objectAsAddress(JNIGlobalRefTable.JNIGlobalRefs);
size = JNIGlobalRefTable.JNIGlobalRefs.length();
chunkSize = size / threads;
start = (ct.getGCOrdinal() - 1) * chunkSize;
end = (ct.getGCOrdinal() == threads) ? size : ct.getGCOrdinal() * chunkSize;
for(int i=start; i < end; i++) {
trace.processRootEdge(jniGlobalRefs.plus(i << LOG_BYTES_IN_ADDRESS), true);
}
}
/**
* Computes roots pointed to by threads, their associated registers
* and stacks. This method places these roots in the root values,
* root locations and interior root locations queues. This method
* should not have side effects (such as copying or forwarding of
* objects). There are a number of important preconditions:
*
* <ul>
* <li> All objects used in the course of GC (such as the GC thread
* objects) need to be "pre-copied" prior to calling this method.
* <li> The <code>threadCounter</code> must be reset so that load
* balancing parallel GC can share the work of scanning threads.
* </ul>
*
* TODO rewrite to avoid the per-thread synchronization, like precopy.
*
* @param trace The trace to use for computing roots.
*/
public void computeThreadRoots(TraceLocal trace) {
boolean processCodeLocations = MemoryManagerConstants.MOVES_CODE;
/* Set status flag */
threadStacksScanned = true;
/* scan all threads */
while (true) {
int threadIndex = threadCounter.increment();
if (threadIndex > RVMThread.numThreads) break;
RVMThread thread = RVMThread.threads[threadIndex];
if (thread == null) continue;
/* scan the thread (stack etc.) */
ScanThread.scanThread(thread, trace, processCodeLocations);
/* identify this thread as a root */
trace.processRootEdge(Magic.objectAsAddress(RVMThread.threads).plus(threadIndex<<LOG_BYTES_IN_ADDRESS), false);
}
/* flush out any remset entries generated during the above activities */
Selected.Mutator.get().flushRememberedSets();
}
/**
* Compute all roots out of the VM's boot image (if any). This method is a no-op
* in the case where the VM does not maintain an MMTk-visible Java space. However,
* when the VM does maintain a space (such as a boot image) which is visible to MMTk,
* that space could either be scanned by MMTk as part of its transitive closure over
* the whole heap, or as a (considerable) performance optimization, MMTk could avoid
* scanning the space if it is aware of all pointers out of that space. This method
* is used to establish the root set out of the scannable space in the case where
* such a space exists.
*
* @param trace The trace object to use to report root locations.
*/
public void computeBootImageRoots(TraceLocal trace) {
ScanBootImage.scanBootImage(trace);
}
}