001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.mmtk.utility;
014
015import static org.mmtk.utility.Constants.LOG_BYTES_IN_ADDRESS;
016import static org.mmtk.utility.TracingConstants.TRACE_ALLOC;
017import static org.mmtk.utility.TracingConstants.TRACE_ARRAY_SET;
018import static org.mmtk.utility.TracingConstants.TRACE_BOOT_ALLOC;
019import static org.mmtk.utility.TracingConstants.TRACE_DEATH;
020import static org.mmtk.utility.TracingConstants.TRACE_EXACT_ALLOC;
021import static org.mmtk.utility.TracingConstants.TRACE_EXACT_IMMORTAL_ALLOC;
022import static org.mmtk.utility.TracingConstants.TRACE_FIELD_SET;
023import static org.mmtk.utility.TracingConstants.TRACE_IMMORTAL_ALLOC;
024import static org.mmtk.utility.TracingConstants.TRACE_TIB_SET;
025
026import org.mmtk.plan.ParallelCollector;
027import org.mmtk.plan.Plan;
028import org.mmtk.plan.TraceLocal;
029import org.mmtk.plan.semispace.gctrace.GCTrace;
030import org.mmtk.policy.Space;
031import org.mmtk.utility.deque.*;
032import org.mmtk.utility.options.Options;
033import org.mmtk.utility.options.TraceRate;
034import org.mmtk.vm.VM;
035
036import org.vmmagic.pragma.*;
037import org.vmmagic.unboxed.*;
038
039/**
040 * Class that supports scanning Objects and Arrays for references
041 * during tracing, handling those references, and computing death times
042 */
043@Uninterruptible public final class TraceGenerator {
044
045
046  /***********************************************************************
047   *
048   * Class variables
049   */
050
051  /** Type of lifetime analysis to be used */
052  public static final boolean MERLIN_ANALYSIS = true;
053
054  /* include the notion of build-time allocation to our list of allocators */
055  private static final int ALLOC_BOOT = GCTrace.ALLOCATORS;
056  private static final int ALLOCATORS = ALLOC_BOOT + 1;
057
058  /* Fields for tracing */
059  private static SortTODSharedDeque tracePool; // Buffers to hold raw trace
060  private static TraceBuffer trace;
061  private static boolean traceBusy; // If we are building the trace
062  private static Word lastGC; // Last time GC was performed
063  private static ObjectReferenceArray objectLinks; // Lists of active objs
064
065  /* Fields needed for Merlin lifetime analysis */
066  private static SortTODSharedDeque workListPool; // Holds objs to process
067  private static SortTODObjectReferenceStack worklist; // Objs to process
068  private static Word agePropagate; // Death time propagating
069
070  static {
071    traceBusy = false;
072    lastGC = Word.fromIntZeroExtend(4);
073    Options.traceRate = new TraceRate();
074  }
075
076
077  /***********************************************************************
078   *
079   * Public analysis methods
080   */
081
082  /**
083   * This is called at "build-time" and passes the necessary build image
084   * objects to the trace processor.
085   *
086   * @param worklist_ The dequeue that serves as the worklist for
087   * death time propagation
088   * @param trace_ The dequeue used to store and then output the trace
089   */
090  @Interruptible
091  public static void init(SortTODSharedDeque worklist_,
092                                SortTODSharedDeque trace_) {
093    /* Objects are only needed for merlin tracing */
094    if (MERLIN_ANALYSIS) {
095      workListPool = worklist_;
096      worklist = new SortTODObjectReferenceStack(workListPool);
097    }
098
099    /* Trace objects */
100    tracePool = trace_;
101    trace = new TraceBuffer(tracePool);
102    objectLinks = ObjectReferenceArray.create(Space.MAX_SPACES);
103  }
104
105  /**
106   * This is called immediately before Jikes terminates.  It will perform
107   * any death time processing that the analysis requires and then output
108   * any remaining information in the trace buffer.
109   *
110   * @param value The integer value for the reason Jikes is terminating
111   */
112  public static void notifyExit(int value) {
113    if (MERLIN_ANALYSIS)
114      findDeaths();
115    trace.process();
116  }
117
118  /**
119   * Add a newly allocated object into the linked list of objects in a region.
120   * This is typically called after each object allocation.
121   *
122   * @param ref The address of the object to be added to the linked list
123   * @param linkSpace The region to which the object should be added
124   */
125  public static void addTraceObject(ObjectReference ref, int linkSpace) {
126    VM.traceInterface.setLink(ref, objectLinks.get(linkSpace));
127    objectLinks.set(linkSpace, ref);
128  }
129
130  /**
131   * Do the work necessary following each garbage collection. This HAS to be
132   * called after EACH collection.
133   */
134  public static void postCollection() {
135    /* Find and output the object deaths */
136    traceBusy = true;
137    findDeaths();
138    traceBusy = false;
139    trace.process();
140  }
141
142
143  /***********************************************************************
144   *
145   * Trace generation code
146   */
147
148  /**
149   * Add the information in the bootImage to the trace.  This should be
150   * called before any allocations and pointer updates have occurred.
151   *
152   * @param bootStart The address at which the bootimage starts
153   */
154  public static void boot(Address bootStart) {
155    Word nextOID = VM.traceInterface.getOID();
156    ObjectReference trav = VM.traceInterface.getBootImageLink().plus(bootStart.toWord().toOffset()).toObjectReference();
157    objectLinks.set(ALLOC_BOOT, trav);
158    /* Loop through all the objects within boot image */
159    while (!trav.isNull()) {
160      ObjectReference next = VM.traceInterface.getLink(trav);
161      Word thisOID = VM.traceInterface.getOID(trav);
162      /* Add the boot image object to the trace. */
163      trace.push(TRACE_BOOT_ALLOC);
164      trace.push(thisOID);
165      trace.push(nextOID.minus(thisOID).lsh(LOG_BYTES_IN_ADDRESS));
166      nextOID = thisOID;
167      /* Move to the next object & adjust for starting address of
168         the bootImage */
169      if (!next.isNull()) {
170        next = next.toAddress().plus(bootStart.toWord().toOffset()).toObjectReference();
171        VM.traceInterface.setLink(trav, next);
172      }
173      trav = next;
174    }
175  }
176
177  /**
178   * Do any tracing work required at each a pointer store operation.  This
179   * will add the pointer store to the trace buffer and, when Merlin lifetime
180   * analysis is being used, performs the necessary timestamping.
181   *
182   * @param isScalar If this is a pointer store to a scalar object
183   * @param src The address of the source object
184   * @param slot The address within <code>src</code> into which
185   * <code>tgt</code> will be stored
186   * @param tgt The target of the pointer store
187   */
188  @NoInline
189  public static void processPointerUpdate(boolean isScalar,
190                                          ObjectReference src,
191                                          Address slot, ObjectReference tgt) {
192    // The trace can be busy only if this is a pointer update as a result of
193    // the garbage collection needed by tracing. For the moment, we will
194    // not report these updates.
195    if (!traceBusy) {
196      /* Process the old target potentially becoming unreachable when needed. */
197      if (MERLIN_ANALYSIS) {
198        ObjectReference oldTgt = slot.loadObjectReference();
199        if (!oldTgt.isNull())
200          VM.traceInterface.updateDeathTime(oldTgt);
201      }
202
203      traceBusy = true;
204      /* Add the pointer store to the trace */
205      Offset traceOffset = VM.traceInterface.adjustSlotOffset(isScalar, src, slot);
206      if (isScalar)
207        trace.push(TRACE_FIELD_SET);
208      else
209        trace.push(TRACE_ARRAY_SET);
210      trace.push(VM.traceInterface.getOID(src));
211      trace.push(traceOffset.toWord());
212      if (tgt.isNull())
213        trace.push(Word.zero());
214      else
215        trace.push(VM.traceInterface.getOID(tgt));
216      traceBusy = false;
217    }
218  }
219
220  /**
221   * Do any tracing work required at each object allocation. This will add the
222   * object allocation to the trace buffer, triggers the necessary collection
223   * work at exact allocations, and output the data in the trace buffer.
224   *
225   * @param isImmortal whether the allocation goes to an immortal space
226   * @param ref The address of the object just allocated.
227   * @param typeRef the type reference for the instance being created
228   * @param bytes The size of the object being allocated
229   */
230  @LogicallyUninterruptible
231  @NoInline
232  public static void traceAlloc(boolean isImmortal, ObjectReference ref,
233      ObjectReference typeRef, int bytes) {
234    boolean gcAllowed = VM.traceInterface.gcEnabled() && Plan.isInitialized() && VM.activePlan.isMutator();
235    /* Test if it is time/possible for an exact allocation. */
236    Word oid = VM.traceInterface.getOID(ref);
237    Word allocType;
238    if (gcAllowed && (oid.GE(lastGC.plus(Word.fromIntZeroExtend(Options.traceRate.getValue())))))
239      allocType = TRACE_EXACT_ALLOC;
240    else {
241      allocType = TRACE_ALLOC;
242    }
243    /* Add the allocation into the trace. */
244    traceBusy = true;
245    /* When legally permissible, add the record to the trace buffer */
246    if (MERLIN_ANALYSIS) {
247       Address fp = (TraceBuffer.OMIT_ALLOCS) ? Address.zero() : VM.traceInterface.skipOwnFramesAndDump(typeRef);
248
249       if (isImmortal && allocType.EQ(TRACE_EXACT_ALLOC))
250         trace.push(TRACE_EXACT_IMMORTAL_ALLOC);
251       else if (isImmortal)
252         trace.push(TRACE_IMMORTAL_ALLOC);
253       else
254         trace.push(allocType);
255       trace.push(VM.traceInterface.getOID(ref));
256       trace.push(Word.fromIntZeroExtend(bytes - VM.traceInterface.getHeaderSize()));
257       trace.push(fp.toWord());
258       trace.push(Word.zero()); /* Magic.getThreadId() */
259       trace.push(TRACE_TIB_SET);
260       trace.push(VM.traceInterface.getOID(ref));
261       trace.push(VM.traceInterface.getOID(typeRef));
262    }
263    /* Perform the necessary work for death times. */
264    if (allocType.EQ(TRACE_EXACT_ALLOC)) {
265      if (MERLIN_ANALYSIS) {
266        lastGC = VM.traceInterface.getOID(ref);
267        VM.traceInterface.updateTime(lastGC);
268        // FIXME TODO: VM.collection.triggerCollection(Collection.INTERNAL_GC_TRIGGER);
269      } else {
270        // FIXME TODO: VM.collection.triggerCollection(Collection.RESOURCE_GC_TRIGGER);
271        lastGC = VM.traceInterface.getOID(ref);
272      }
273    }
274    /* Add the allocation record to the buffer if we have not yet done so. */
275    if (!MERLIN_ANALYSIS) {
276       Address fp = (TraceBuffer.OMIT_ALLOCS) ? Address.zero() : VM.traceInterface.skipOwnFramesAndDump(typeRef);
277       if (isImmortal && allocType.EQ(TRACE_EXACT_ALLOC))
278         trace.push(TRACE_EXACT_IMMORTAL_ALLOC);
279       else if (isImmortal)
280         trace.push(TRACE_IMMORTAL_ALLOC);
281       else
282         trace.push(allocType);
283       trace.push(VM.traceInterface.getOID(ref));
284       trace.push(Word.fromIntZeroExtend(bytes - VM.traceInterface.getHeaderSize()));
285       trace.push(fp.toWord());
286       trace.push(Word.zero()); /* Magic.getThreadId() */
287       trace.push(TRACE_TIB_SET);
288       trace.push(VM.traceInterface.getOID(ref));
289       trace.push(VM.traceInterface.getOID(typeRef));
290    }
291    trace.process();
292    traceBusy = false;
293  }
294
295  /***********************************************************************
296   *
297   * Merlin lifetime analysis methods
298   */
299
300  /**
301   * This computes and adds to the trace buffer the unreachable time for
302   * all of the objects that are _provably_ unreachable.  This method
303   * should be called after garbage collection (but before the space has
304   * been reclaimed) and at program termination.
305   */
306  private static void findDeaths() {
307    /* Only the merlin analysis needs to compute death times */
308    if (MERLIN_ANALYSIS) {
309      /* Start with an empty stack. */
310      if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(worklist.isEmpty());
311      /* Scan the linked list of objects within each region */
312      for (int allocator = 0; allocator < ALLOCATORS; allocator++) {
313        ObjectReference thisRef = objectLinks.get(allocator);
314        /* Start at the top of each linked list */
315        while (!thisRef.isNull()) {
316          /* Add the unreachable objects onto the worklist. */
317          if (!getTraceLocal().isReachable(thisRef))
318            worklist.push(thisRef);
319          thisRef = VM.traceInterface.getLink(thisRef);
320        }
321      }
322      /* Sort the objects on the worklist by their timestamp */
323      if (!worklist.isEmpty())
324        worklist.sort();
325      /* Now compute the death times. */
326      computeTransitiveClosure();
327    }
328    /* Output the death times for each object */
329    for (int allocator = 0; allocator < ALLOCATORS; allocator++) {
330      ObjectReference thisRef = objectLinks.get(allocator);
331      ObjectReference prevRef = ObjectReference.nullReference(); // the last live object seen
332      while (!thisRef.isNull()) {
333        ObjectReference nextRef = VM.traceInterface.getLink(thisRef);
334        /* Maintain reachable objects on the linked list of allocated objects */
335        if (getTraceLocal().isReachable(thisRef)) {
336          thisRef = getTraceLocal().getForwardedReference(thisRef);
337          VM.traceInterface.setLink(thisRef, prevRef);
338          prevRef = thisRef;
339        } else {
340          /* For brute force lifetime analysis, objects become
341             unreachable "now" */
342          Word deadTime;
343          if (MERLIN_ANALYSIS)
344            deadTime = VM.traceInterface.getDeathTime(thisRef);
345          else
346            deadTime = lastGC;
347          /* Add the death record to the trace for unreachable objects. */
348          trace.push(TRACE_DEATH);
349          trace.push(VM.traceInterface.getOID(thisRef));
350          trace.push(deadTime);
351        }
352        thisRef = nextRef;
353      }
354      /* Purge the list of unreachable objects... */
355      objectLinks.set(allocator, prevRef);
356    }
357  }
358
359  /**
360   * This method is called for each root-referenced object at every Merlin
361   * root enumeration.  The method will update the death time of the parameter
362   * to the current trace time.
363   *
364   * @param obj The root-referenced object
365   */
366  public static void rootEnumerate(ObjectReference obj) {
367    VM.traceInterface.updateDeathTime(obj);
368  }
369
370  /**
371   * This propagates the death time being computed to the object passed as an
372   * address. If we find the unreachable time for the parameter, it will be
373   * pushed on to the processing stack.
374   *
375   * @param ref The address of the object to examine
376   */
377  public static void propagateDeathTime(ObjectReference ref) {
378    /* If this death time is more accurate, set it. */
379    if (VM.traceInterface.getDeathTime(ref).LT(agePropagate)) {
380      /* If we should add the object for further processing. */
381      if (!getTraceLocal().isReachable(ref)) {
382        VM.traceInterface.setDeathTime(ref, agePropagate);
383        worklist.push(ref);
384      } else {
385        VM.traceInterface.setDeathTime(getTraceLocal().getForwardedReference(ref), agePropagate);
386      }
387    }
388  }
389
390  /**
391   * This finds all object death times by computing the (limited)
392   * transitive closure of the dead objects.  Death times are computed
393   * as the latest reaching death time to an object.
394   */
395  private static void computeTransitiveClosure() {
396     if (!worklist.isEmpty()) {
397       /* The latest time an object can die. */
398       agePropagate = Word.max();
399       /* Process through the entire buffer. */
400       ObjectReference ref = worklist.pop();
401       while (!ref.isNull()) {
402         Word currentAge = VM.traceInterface.getDeathTime(ref);
403         /* This is a cheap and simple test to process objects only once. */
404         if (currentAge.LE(agePropagate)) {
405           /* Set the "new" dead age. */
406           agePropagate = currentAge;
407           /* Scan the object, pushing the survivors */
408           VM.scanning.scanObject(getTraceLocal(), ref);
409         }
410         /* Get the next object to process */
411         ref = worklist.pop();
412       }
413     }
414  }
415
416  private static TraceLocal getTraceLocal() {
417    return ((ParallelCollector)VM.activePlan.collector()).getCurrentTrace();
418  }
419
420}