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}