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