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.jikesrvm.compilers.common; 014 015 import java.util.Comparator; 016 import java.util.Set; 017 import java.util.TreeMap; 018 019 import org.jikesrvm.VM; 020 import org.jikesrvm.Services; 021 import org.jikesrvm.SizeConstants; 022 import org.jikesrvm.classloader.RVMArray; 023 import org.jikesrvm.classloader.RVMMethod; 024 import org.jikesrvm.classloader.RVMType; 025 import org.jikesrvm.compilers.baseline.BaselineCompiledMethod; 026 import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod; 027 import org.jikesrvm.jni.JNICompiledMethod; 028 import org.jikesrvm.runtime.Magic; 029 import org.jikesrvm.runtime.Memory; 030 import org.vmmagic.pragma.Uninterruptible; 031 import org.vmmagic.unboxed.Address; 032 033 /** 034 * Manage pool of compiled methods. <p> 035 * Original extracted from RVMClassLoader. <p> 036 */ 037 public class CompiledMethods implements SizeConstants { 038 /** 039 * 2^LOG_ROW_SIZE is the number of elements per row 040 */ 041 private static final int LOG_ROW_SIZE = 10; 042 /** 043 * Mask to ascertain row from id number 044 */ 045 private static final int ROW_MASK = (1 << LOG_ROW_SIZE)-1; 046 /** 047 * Java methods that have been compiled into machine code. 048 * Note that there may be more than one compiled versions of the same method 049 * (i.e. at different levels of optimization). 050 */ 051 private static CompiledMethod[][] compiledMethods = new CompiledMethod[16][1 << LOG_ROW_SIZE]; 052 053 /** 054 * Index of most recently allocated slot in compiledMethods[]. 055 */ 056 private static int currentCompiledMethodId = 0; 057 058 /** 059 * Used to communicate between {@link #setCompiledMethodObsolete} 060 * and {@link #snipObsoleteCompiledMethods} 061 */ 062 private static boolean scanForObsoleteMethods = false; 063 064 /** 065 * Ensure space in backing array for id 066 */ 067 private static void ensureCapacity(int id) { 068 int column = id >> LOG_ROW_SIZE; 069 if (column >= compiledMethods.length) { 070 CompiledMethod[][] tmp = new CompiledMethod[column+1][]; 071 for (int i=0; i < column; i++) { 072 tmp[i] = compiledMethods[i]; 073 } 074 tmp[column] = new CompiledMethod[1 << LOG_ROW_SIZE]; 075 compiledMethods = tmp; 076 Magic.sync(); 077 } 078 } 079 080 /** 081 * Fetch a previously compiled method without checking 082 */ 083 @Uninterruptible 084 public static CompiledMethod getCompiledMethodUnchecked(int cmid) { 085 int column = cmid >> LOG_ROW_SIZE; 086 return compiledMethods[column][cmid & ROW_MASK]; 087 } 088 089 /** 090 * Set entry in compiled method lookup 091 */ 092 @Uninterruptible 093 private static void setCompiledMethod(int cmid, CompiledMethod cm) { 094 int column = cmid >> LOG_ROW_SIZE; 095 CompiledMethod[] col = compiledMethods[column]; 096 Services.setArrayUninterruptible(col, cmid & ROW_MASK, cm); 097 } 098 099 /** 100 * Fetch a previously compiled method. 101 */ 102 @Uninterruptible 103 public static CompiledMethod getCompiledMethod(int compiledMethodId) { 104 Magic.isync(); // see potential update from other procs 105 106 if (VM.VerifyAssertions) { 107 if (!(0 < compiledMethodId && compiledMethodId <= currentCompiledMethodId)) { 108 VM.sysWriteln("WARNING: attempt to get compiled method #", compiledMethodId); 109 VM.sysFail("attempt to get an invalid compiled method ID"); 110 return null; 111 } 112 } 113 114 return getCompiledMethodUnchecked(compiledMethodId); 115 } 116 117 /** 118 * Create a CompiledMethod appropriate for the given compilerType 119 */ 120 public static synchronized CompiledMethod createCompiledMethod(RVMMethod m, int compilerType) { 121 int id = currentCompiledMethodId + 1; 122 ensureCapacity(id); 123 currentCompiledMethodId++; 124 CompiledMethod cm = null; 125 if (compilerType == CompiledMethod.BASELINE) { 126 cm = new BaselineCompiledMethod(id, m); 127 } else if (VM.BuildForOptCompiler && compilerType == CompiledMethod.OPT) { 128 cm = new OptCompiledMethod(id, m); 129 } else if (compilerType == CompiledMethod.JNI) { 130 cm = new JNICompiledMethod(id, m); 131 } else { 132 if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED, "Unexpected compiler type!"); 133 } 134 setCompiledMethod(id, cm); 135 return cm; 136 } 137 138 /** 139 * Create a CompiledMethod for the synthetic hardware trap frame 140 */ 141 public static synchronized CompiledMethod createHardwareTrapCompiledMethod() { 142 int id = currentCompiledMethodId + 1; 143 ensureCapacity(id); 144 currentCompiledMethodId++; 145 CompiledMethod cm = new HardwareTrapCompiledMethod(id, null); 146 setCompiledMethod(id, cm); 147 return cm; 148 } 149 150 /** 151 * Get number of methods compiled so far. 152 */ 153 @Uninterruptible 154 public static int numCompiledMethods() { 155 return currentCompiledMethodId + 1; 156 } 157 158 /** 159 * Find the method whose machine code contains the specified instruction.<p> 160 * 161 * Assumption: caller has disabled gc (otherwise collector could move 162 * objects without fixing up the raw <code>ip</code> pointer)<p> 163 * 164 * Note: this method is highly inefficient. Normally you should use the 165 * following instead: 166 * 167 * <code> 168 * RVMClassLoader.getCompiledMethod(Magic.getCompiledMethodID(fp)) 169 * </code> 170 * 171 * @param ip instruction address. Usage note: <code>ip</code> must point 172 * to the instruction *following* the 173 * actual instruction whose method is sought. This allows us to properly 174 * handle the case where the only address we have to work with is a return 175 * address (i.e. from a stackframe) or an exception address (i.e. from a null 176 * pointer dereference, array bounds check, or divide by zero) on a machine 177 * architecture with variable length instructions. In such situations we'd 178 * have no idea how far to back up the instruction pointer to point to the 179 * "call site" or "exception site". 180 * 181 * @return method (<code>null</code> --> not found) 182 */ 183 @Uninterruptible 184 public static CompiledMethod findMethodForInstruction(Address ip) { 185 for (int i = 0, n = numCompiledMethods(); i < n; ++i) { 186 CompiledMethod compiledMethod = getCompiledMethodUnchecked(i); 187 if (compiledMethod == null || !compiledMethod.isCompiled()) { 188 continue; // empty slot 189 } 190 191 if (compiledMethod.containsReturnAddress(ip)) { 192 return compiledMethod; 193 } 194 } 195 196 return null; 197 } 198 199 // We keep track of compiled methods that become obsolete because they have 200 // been replaced by another version. These are candidates for GC. But, they 201 // can only be collected once we are certain that they are no longer being 202 // executed. Here, we keep track of them until we know they are no longer 203 // in use. 204 public static void setCompiledMethodObsolete(CompiledMethod compiledMethod) { 205 // Currently, we avoid setting methods of java.lang.Object obsolete. 206 // This is because the TIBs for arrays point to the original version 207 // and are not updated on recompilation. 208 // !!TODO: When replacing a java.lang.Object method, find arrays in JTOC 209 // and update TIB to use newly recompiled method. 210 if (compiledMethod.getMethod().getDeclaringClass().isJavaLangObjectType()) { 211 return; 212 } 213 214 compiledMethod.setObsolete(); 215 Magic.sync(); 216 scanForObsoleteMethods = true; 217 } 218 219 /** 220 * Snip reference to CompiledMethod so that we can reclaim code space. If 221 * the code is currently being executed, stack scanning is responsible for 222 * marking it NOT obsolete. Keep such reference until a future GC. 223 * <p> 224 * NOTE: It's expected that this is processed during GC, after scanning 225 * stacks to determine which methods are currently executing. 226 */ 227 @Uninterruptible 228 public static void snipObsoleteCompiledMethods() { 229 Magic.isync(); 230 if (!scanForObsoleteMethods) return; 231 scanForObsoleteMethods = false; 232 Magic.sync(); 233 234 int max = numCompiledMethods(); 235 for (int i = 0; i < max; i++) { 236 CompiledMethod cm = getCompiledMethodUnchecked(i); 237 if (cm != null) { 238 if (cm.isActiveOnStack()) { 239 if (cm.isObsolete()) { 240 // can't get it this time; force us to look again next GC 241 scanForObsoleteMethods = true; 242 Magic.sync(); 243 } 244 cm.clearActiveOnStack(); 245 } else { 246 if (cm.isObsolete()) { 247 // obsolete and not active on a thread stack: it's garbage! 248 setCompiledMethod(i, null); 249 } 250 } 251 } 252 } 253 } 254 255 /** 256 * Report on the space used by compiled code and associated mapping information 257 */ 258 public static void spaceReport() { 259 int[] codeCount = new int[CompiledMethod.NUM_COMPILER_TYPES + 1]; 260 int[] codeBytes = new int[CompiledMethod.NUM_COMPILER_TYPES + 1]; 261 int[] mapBytes = new int[CompiledMethod.NUM_COMPILER_TYPES + 1]; 262 263 RVMArray codeArray = RVMType.CodeArrayType.asArray(); 264 for (int i = 0; i < numCompiledMethods(); i++) { 265 CompiledMethod cm = getCompiledMethodUnchecked(i); 266 if (cm == null || !cm.isCompiled()) continue; 267 int ct = cm.getCompilerType(); 268 codeCount[ct]++; 269 int size = codeArray.getInstanceSize(cm.numberOfInstructions()); 270 codeBytes[ct] += Memory.alignUp(size, BYTES_IN_ADDRESS); 271 mapBytes[ct] += cm.size(); 272 } 273 VM.sysWriteln("Compiled code space report\n"); 274 275 VM.sysWriteln(" Baseline Compiler"); 276 VM.sysWriteln(" Number of compiled methods = " + codeCount[CompiledMethod.BASELINE]); 277 VM.sysWriteln(" Total size of code (bytes) = " + codeBytes[CompiledMethod.BASELINE]); 278 VM.sysWriteln(" Total size of mapping data (bytes) = " + mapBytes[CompiledMethod.BASELINE]); 279 280 if (codeCount[CompiledMethod.OPT] > 0) { 281 VM.sysWriteln(" Optimizing Compiler"); 282 VM.sysWriteln(" Number of compiled methods = " + codeCount[CompiledMethod.OPT]); 283 VM.sysWriteln(" Total size of code (bytes) = " + codeBytes[CompiledMethod.OPT]); 284 VM.sysWriteln(" Total size of mapping data (bytes) = " + mapBytes[CompiledMethod.OPT]); 285 } 286 287 if (codeCount[CompiledMethod.JNI] > 0) { 288 VM.sysWriteln(" JNI Stub Compiler (Java->C stubs for native methods)"); 289 VM.sysWriteln(" Number of compiled methods = " + codeCount[CompiledMethod.JNI]); 290 VM.sysWriteln(" Total size of code (bytes) = " + codeBytes[CompiledMethod.JNI]); 291 VM.sysWriteln(" Total size of mapping data (bytes) = " + mapBytes[CompiledMethod.JNI]); 292 } 293 if (!VM.runningVM) { 294 TreeMap<String, Integer> packageData = new TreeMap<String, Integer>( 295 new Comparator<String>() { 296 @Override 297 public int compare(String a, String b) { 298 return a.compareTo(b); 299 } 300 }); 301 for (int i = 0; i < numCompiledMethods(); ++i) { 302 CompiledMethod compiledMethod = getCompiledMethodUnchecked(i); 303 if (compiledMethod != null) { 304 RVMMethod m = compiledMethod.getMethod(); 305 if (m != null && compiledMethod.isCompiled()) { 306 String packageName = m.getDeclaringClass().getPackageName(); 307 int numInstructions = compiledMethod.numberOfInstructions(); 308 Integer val = packageData.get(packageName); 309 if (val == null) { 310 val = numInstructions; 311 } else { 312 val = val + numInstructions; 313 } 314 packageData.put(packageName, val); 315 } 316 } 317 } 318 VM.sysWriteln("------------------------------------------------------------------------------------------"); 319 VM.sysWriteln(" Break down of code space usage by package (bytes):"); 320 VM.sysWriteln("------------------------------------------------------------------------------------------"); 321 Set<String> keys = packageData.keySet(); 322 int maxPackageNameSize = 0; 323 for (String packageName : keys) { 324 maxPackageNameSize = Math.max(maxPackageNameSize, packageName.length()); 325 } 326 maxPackageNameSize++; 327 for (String packageName : keys) { 328 VM.sysWriteField(maxPackageNameSize, packageName); 329 VM.sysWriteField(10, packageData.get(packageName)); 330 VM.sysWriteln(); 331 } 332 } 333 } 334 }