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 * (ie. 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(false, "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.
160 *
161 * Assumption: caller has disabled gc (otherwise collector could move
162 * objects without fixing up the raw <code>ip</code> pointer)
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
172 *
173 * Usage note: <code>ip</code> must point to the instruction *following* the
174 * actual instruction whose method is sought. This allows us to properly
175 * handle the case where the only address we have to work with is a return
176 * address (ie. from a stackframe) or an exception address (ie. from a null
177 * pointer dereference, array bounds check, or divide by zero) on a machine
178 * architecture with variable length instructions. In such situations we'd
179 * have no idea how far to back up the instruction pointer to point to the
180 * "call site" or "exception site".
181 *
182 * @return method (<code>null</code> --> not found)
183 */
184 @Uninterruptible
185 public static CompiledMethod findMethodForInstruction(Address ip) {
186 for (int i = 0, n = numCompiledMethods(); i < n; ++i) {
187 CompiledMethod compiledMethod = getCompiledMethodUnchecked(i);
188 if (compiledMethod == null || !compiledMethod.isCompiled()) {
189 continue; // empty slot
190 }
191
192 if (compiledMethod.containsReturnAddress(ip)) {
193 return compiledMethod;
194 }
195 }
196
197 return null;
198 }
199
200 // We keep track of compiled methods that become obsolete because they have
201 // been replaced by another version. These are candidates for GC. But, they
202 // can only be collected once we are certain that they are no longer being
203 // executed. Here, we keep track of them until we know they are no longer
204 // in use.
205 public static void setCompiledMethodObsolete(CompiledMethod compiledMethod) {
206 // Currently, we avoid setting methods of java.lang.Object obsolete.
207 // This is because the TIBs for arrays point to the original version
208 // and are not updated on recompilation.
209 // !!TODO: When replacing a java.lang.Object method, find arrays in JTOC
210 // and update TIB to use newly recompiled method.
211 if (compiledMethod.getMethod().getDeclaringClass().isJavaLangObjectType()) {
212 return;
213 }
214
215 compiledMethod.setObsolete();
216 Magic.sync();
217 scanForObsoleteMethods = true;
218 }
219
220 /**
221 * Snip reference to CompiledMethod so that we can reclaim code space. If
222 * the code is currently being executed, stack scanning is responsible for
223 * marking it NOT obsolete. Keep such reference until a future GC.
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 public int compare(String a, String b) {
297 return a.compareTo(b);
298 }
299 });
300 for (int i = 0; i < numCompiledMethods(); ++i) {
301 CompiledMethod compiledMethod = getCompiledMethodUnchecked(i);
302 if (compiledMethod != null) {
303 RVMMethod m = compiledMethod.getMethod();
304 if (m != null && compiledMethod.isCompiled()) {
305 String packageName = m.getDeclaringClass().getPackageName();
306 int numInstructions = compiledMethod.numberOfInstructions();
307 Integer val = packageData.get(packageName);
308 if (val == null) {
309 val = numInstructions;
310 } else {
311 val = val + numInstructions;
312 }
313 packageData.put(packageName, val);
314 }
315 }
316 }
317 VM.sysWriteln("------------------------------------------------------------------------------------------");
318 VM.sysWriteln(" Break down of code space usage by package (bytes):");
319 VM.sysWriteln("------------------------------------------------------------------------------------------");
320 Set<String> keys = packageData.keySet();
321 int maxPackageNameSize = 0;
322 for (String packageName : keys) {
323 maxPackageNameSize = Math.max(maxPackageNameSize, packageName.length());
324 }
325 maxPackageNameSize++;
326 for (String packageName : keys) {
327 VM.sysWriteField(maxPackageNameSize, packageName);
328 VM.sysWriteField(10, packageData.get(packageName));
329 VM.sysWriteln();
330 }
331 }
332 }
333 }