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    }