/*
* 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;
import static org.mmtk.utility.Constants.LOG_BYTES_IN_ADDRESS;
import static org.mmtk.utility.TracingConstants.TRACE_ALLOC;
import static org.mmtk.utility.TracingConstants.TRACE_ARRAY_SET;
import static org.mmtk.utility.TracingConstants.TRACE_BOOT_ALLOC;
import static org.mmtk.utility.TracingConstants.TRACE_DEATH;
import static org.mmtk.utility.TracingConstants.TRACE_EXACT_ALLOC;
import static org.mmtk.utility.TracingConstants.TRACE_EXACT_IMMORTAL_ALLOC;
import static org.mmtk.utility.TracingConstants.TRACE_FIELD_SET;
import static org.mmtk.utility.TracingConstants.TRACE_IMMORTAL_ALLOC;
import static org.mmtk.utility.TracingConstants.TRACE_TIB_SET;
import org.mmtk.plan.ParallelCollector;
import org.mmtk.plan.Plan;
import org.mmtk.plan.TraceLocal;
import org.mmtk.plan.semispace.gctrace.GCTrace;
import org.mmtk.utility.deque.*;
import org.mmtk.utility.heap.layout.HeapParameters;
import org.mmtk.utility.options.Options;
import org.mmtk.utility.options.TraceRate;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.*;
import org.vmmagic.unboxed.*;
/**
* Class that supports scanning Objects and Arrays for references
* during tracing, handling those references, and computing death times
*/
@Uninterruptible public final class TraceGenerator {
/***********************************************************************
*
* Class variables
*/
/** Type of lifetime analysis to be used */
public static final boolean MERLIN_ANALYSIS = true;
/* include the notion of build-time allocation to our list of allocators */
private static final int ALLOC_BOOT = GCTrace.ALLOCATORS;
private static final int ALLOCATORS = ALLOC_BOOT + 1;
/* Fields for tracing */
private static SortTODSharedDeque tracePool; // Buffers to hold raw trace
private static TraceBuffer trace;
private static boolean traceBusy; // If we are building the trace
private static Word lastGC; // Last time GC was performed
private static ObjectReferenceArray objectLinks; // Lists of active objs
/* Fields needed for Merlin lifetime analysis */
private static SortTODSharedDeque workListPool; // Holds objs to process
private static SortTODObjectReferenceStack worklist; // Objs to process
private static Word agePropagate; // Death time propagating
static {
traceBusy = false;
lastGC = Word.fromIntZeroExtend(4);
Options.traceRate = new TraceRate();
}
/***********************************************************************
*
* Public analysis methods
*/
/**
* This is called at "build-time" and passes the necessary build image
* objects to the trace processor.
*
* @param worklist_ The dequeue that serves as the worklist for
* death time propagation
* @param trace_ The dequeue used to store and then output the trace
*/
@Interruptible
public static void init(SortTODSharedDeque worklist_,
SortTODSharedDeque trace_) {
/* Objects are only needed for merlin tracing */
if (MERLIN_ANALYSIS) {
workListPool = worklist_;
worklist = new SortTODObjectReferenceStack(workListPool);
}
/* Trace objects */
tracePool = trace_;
trace = new TraceBuffer(tracePool);
objectLinks = ObjectReferenceArray.create(HeapParameters.MAX_SPACES);
}
/**
* This is called immediately before Jikes terminates. It will perform
* any death time processing that the analysis requires and then output
* any remaining information in the trace buffer.
*
* @param value The integer value for the reason Jikes is terminating
*/
public static void notifyExit(int value) {
if (MERLIN_ANALYSIS)
findDeaths();
trace.process();
}
/**
* Add a newly allocated object into the linked list of objects in a region.
* This is typically called after each object allocation.
*
* @param ref The address of the object to be added to the linked list
* @param linkSpace The region to which the object should be added
*/
public static void addTraceObject(ObjectReference ref, int linkSpace) {
VM.traceInterface.setLink(ref, objectLinks.get(linkSpace));
objectLinks.set(linkSpace, ref);
}
/**
* Do the work necessary following each garbage collection. This HAS to be
* called after EACH collection.
*/
public static void postCollection() {
/* Find and output the object deaths */
traceBusy = true;
findDeaths();
traceBusy = false;
trace.process();
}
/***********************************************************************
*
* Trace generation code
*/
/**
* Add the information in the bootImage to the trace. This should be
* called before any allocations and pointer updates have occurred.
*
* @param bootStart The address at which the bootimage starts
*/
public static void boot(Address bootStart) {
Word nextOID = VM.traceInterface.getOID();
ObjectReference trav = VM.traceInterface.getBootImageLink().plus(bootStart.toWord().toOffset()).toObjectReference();
objectLinks.set(ALLOC_BOOT, trav);
/* Loop through all the objects within boot image */
while (!trav.isNull()) {
ObjectReference next = VM.traceInterface.getLink(trav);
Word thisOID = VM.traceInterface.getOID(trav);
/* Add the boot image object to the trace. */
trace.push(TRACE_BOOT_ALLOC);
trace.push(thisOID);
trace.push(nextOID.minus(thisOID).lsh(LOG_BYTES_IN_ADDRESS));
nextOID = thisOID;
/* Move to the next object & adjust for starting address of
the bootImage */
if (!next.isNull()) {
next = next.toAddress().plus(bootStart.toWord().toOffset()).toObjectReference();
VM.traceInterface.setLink(trav, next);
}
trav = next;
}
}
/**
* Do any tracing work required at each a pointer store operation. This
* will add the pointer store to the trace buffer and, when Merlin lifetime
* analysis is being used, performs the necessary timestamping.
*
* @param isScalar If this is a pointer store to a scalar object
* @param src The address of the source object
* @param slot The address within <code>src</code> into which
* <code>tgt</code> will be stored
* @param tgt The target of the pointer store
*/
@NoInline
public static void processPointerUpdate(boolean isScalar,
ObjectReference src,
Address slot, ObjectReference tgt) {
// The trace can be busy only if this is a pointer update as a result of
// the garbage collection needed by tracing. For the moment, we will
// not report these updates.
if (!traceBusy) {
/* Process the old target potentially becoming unreachable when needed. */
if (MERLIN_ANALYSIS) {
ObjectReference oldTgt = slot.loadObjectReference();
if (!oldTgt.isNull())
VM.traceInterface.updateDeathTime(oldTgt);
}
traceBusy = true;
/* Add the pointer store to the trace */
Offset traceOffset = VM.traceInterface.adjustSlotOffset(isScalar, src, slot);
if (isScalar)
trace.push(TRACE_FIELD_SET);
else
trace.push(TRACE_ARRAY_SET);
trace.push(VM.traceInterface.getOID(src));
trace.push(traceOffset.toWord());
if (tgt.isNull())
trace.push(Word.zero());
else
trace.push(VM.traceInterface.getOID(tgt));
traceBusy = false;
}
}
/**
* Do any tracing work required at each object allocation. This will add the
* object allocation to the trace buffer, triggers the necessary collection
* work at exact allocations, and output the data in the trace buffer.
*
* @param isImmortal whether the allocation goes to an immortal space
* @param ref The address of the object just allocated.
* @param typeRef the type reference for the instance being created
* @param bytes The size of the object being allocated
*/
@LogicallyUninterruptible
@NoInline
public static void traceAlloc(boolean isImmortal, ObjectReference ref,
ObjectReference typeRef, int bytes) {
boolean gcAllowed = VM.traceInterface.gcEnabled() && Plan.isInitialized() && VM.activePlan.isMutator();
/* Test if it is time/possible for an exact allocation. */
Word oid = VM.traceInterface.getOID(ref);
Word allocType;
if (gcAllowed && (oid.GE(lastGC.plus(Word.fromIntZeroExtend(Options.traceRate.getValue())))))
allocType = TRACE_EXACT_ALLOC;
else {
allocType = TRACE_ALLOC;
}
/* Add the allocation into the trace. */
traceBusy = true;
/* When legally permissible, add the record to the trace buffer */
if (MERLIN_ANALYSIS) {
Address fp = (TraceBuffer.OMIT_ALLOCS) ? Address.zero() : VM.traceInterface.skipOwnFramesAndDump(typeRef);
if (isImmortal && allocType.EQ(TRACE_EXACT_ALLOC))
trace.push(TRACE_EXACT_IMMORTAL_ALLOC);
else if (isImmortal)
trace.push(TRACE_IMMORTAL_ALLOC);
else
trace.push(allocType);
trace.push(VM.traceInterface.getOID(ref));
trace.push(Word.fromIntZeroExtend(bytes - VM.traceInterface.getHeaderSize()));
trace.push(fp.toWord());
trace.push(Word.zero()); /* Magic.getThreadId() */
trace.push(TRACE_TIB_SET);
trace.push(VM.traceInterface.getOID(ref));
trace.push(VM.traceInterface.getOID(typeRef));
}
/* Perform the necessary work for death times. */
if (allocType.EQ(TRACE_EXACT_ALLOC)) {
if (MERLIN_ANALYSIS) {
lastGC = VM.traceInterface.getOID(ref);
VM.traceInterface.updateTime(lastGC);
// FIXME TODO: VM.collection.triggerCollection(Collection.INTERNAL_GC_TRIGGER);
} else {
// FIXME TODO: VM.collection.triggerCollection(Collection.RESOURCE_GC_TRIGGER);
lastGC = VM.traceInterface.getOID(ref);
}
}
/* Add the allocation record to the buffer if we have not yet done so. */
if (!MERLIN_ANALYSIS) {
Address fp = (TraceBuffer.OMIT_ALLOCS) ? Address.zero() : VM.traceInterface.skipOwnFramesAndDump(typeRef);
if (isImmortal && allocType.EQ(TRACE_EXACT_ALLOC))
trace.push(TRACE_EXACT_IMMORTAL_ALLOC);
else if (isImmortal)
trace.push(TRACE_IMMORTAL_ALLOC);
else
trace.push(allocType);
trace.push(VM.traceInterface.getOID(ref));
trace.push(Word.fromIntZeroExtend(bytes - VM.traceInterface.getHeaderSize()));
trace.push(fp.toWord());
trace.push(Word.zero()); /* Magic.getThreadId() */
trace.push(TRACE_TIB_SET);
trace.push(VM.traceInterface.getOID(ref));
trace.push(VM.traceInterface.getOID(typeRef));
}
trace.process();
traceBusy = false;
}
/***********************************************************************
*
* Merlin lifetime analysis methods
*/
/**
* This computes and adds to the trace buffer the unreachable time for
* all of the objects that are _provably_ unreachable. This method
* should be called after garbage collection (but before the space has
* been reclaimed) and at program termination.
*/
private static void findDeaths() {
/* Only the merlin analysis needs to compute death times */
if (MERLIN_ANALYSIS) {
/* Start with an empty stack. */
if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(worklist.isEmpty());
/* Scan the linked list of objects within each region */
for (int allocator = 0; allocator < ALLOCATORS; allocator++) {
ObjectReference thisRef = objectLinks.get(allocator);
/* Start at the top of each linked list */
while (!thisRef.isNull()) {
/* Add the unreachable objects onto the worklist. */
if (!getTraceLocal().isReachable(thisRef))
worklist.push(thisRef);
thisRef = VM.traceInterface.getLink(thisRef);
}
}
/* Sort the objects on the worklist by their timestamp */
if (!worklist.isEmpty())
worklist.sort();
/* Now compute the death times. */
computeTransitiveClosure();
}
/* Output the death times for each object */
for (int allocator = 0; allocator < ALLOCATORS; allocator++) {
ObjectReference thisRef = objectLinks.get(allocator);
ObjectReference prevRef = ObjectReference.nullReference(); // the last live object seen
while (!thisRef.isNull()) {
ObjectReference nextRef = VM.traceInterface.getLink(thisRef);
/* Maintain reachable objects on the linked list of allocated objects */
if (getTraceLocal().isReachable(thisRef)) {
thisRef = getTraceLocal().getForwardedReference(thisRef);
VM.traceInterface.setLink(thisRef, prevRef);
prevRef = thisRef;
} else {
/* For brute force lifetime analysis, objects become
unreachable "now" */
Word deadTime;
if (MERLIN_ANALYSIS)
deadTime = VM.traceInterface.getDeathTime(thisRef);
else
deadTime = lastGC;
/* Add the death record to the trace for unreachable objects. */
trace.push(TRACE_DEATH);
trace.push(VM.traceInterface.getOID(thisRef));
trace.push(deadTime);
}
thisRef = nextRef;
}
/* Purge the list of unreachable objects... */
objectLinks.set(allocator, prevRef);
}
}
/**
* This method is called for each root-referenced object at every Merlin
* root enumeration. The method will update the death time of the parameter
* to the current trace time.
*
* @param obj The root-referenced object
*/
public static void rootEnumerate(ObjectReference obj) {
VM.traceInterface.updateDeathTime(obj);
}
/**
* This propagates the death time being computed to the object passed as an
* address. If we find the unreachable time for the parameter, it will be
* pushed on to the processing stack.
*
* @param ref The address of the object to examine
*/
public static void propagateDeathTime(ObjectReference ref) {
/* If this death time is more accurate, set it. */
if (VM.traceInterface.getDeathTime(ref).LT(agePropagate)) {
/* If we should add the object for further processing. */
if (!getTraceLocal().isReachable(ref)) {
VM.traceInterface.setDeathTime(ref, agePropagate);
worklist.push(ref);
} else {
VM.traceInterface.setDeathTime(getTraceLocal().getForwardedReference(ref), agePropagate);
}
}
}
/**
* This finds all object death times by computing the (limited)
* transitive closure of the dead objects. Death times are computed
* as the latest reaching death time to an object.
*/
private static void computeTransitiveClosure() {
if (!worklist.isEmpty()) {
/* The latest time an object can die. */
agePropagate = Word.max();
/* Process through the entire buffer. */
ObjectReference ref = worklist.pop();
while (!ref.isNull()) {
Word currentAge = VM.traceInterface.getDeathTime(ref);
/* This is a cheap and simple test to process objects only once. */
if (currentAge.LE(agePropagate)) {
/* Set the "new" dead age. */
agePropagate = currentAge;
/* Scan the object, pushing the survivors */
VM.scanning.scanObject(getTraceLocal(), ref);
}
/* Get the next object to process */
ref = worklist.pop();
}
}
}
private static TraceLocal getTraceLocal() {
return ((ParallelCollector)VM.activePlan.collector()).getCurrentTrace();
}
}