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