/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.compilers.common;
import static org.jikesrvm.runtime.UnboxedSizeConstants.BYTES_IN_ADDRESS;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
import org.jikesrvm.jni.JNICompiledMethod;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.Memory;
import org.jikesrvm.util.Services;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
/**
* Manage pool of compiled methods. <p>
* Original extracted from RVMClassLoader.
*/
public class CompiledMethods {
/**
* 2^LOG_ROW_SIZE is the number of elements per row
*/
private static final int LOG_ROW_SIZE = 10;
/**
* Mask to ascertain row from id number
*/
private static final int ROW_MASK = (1 << LOG_ROW_SIZE) - 1;
/**
* Java methods that have been compiled into machine code.
* Note that there may be more than one compiled versions of the same method
* (i.e. at different levels of optimization).
*/
private static CompiledMethod[][] compiledMethods = new CompiledMethod[16][1 << LOG_ROW_SIZE];
/**
* Index of most recently allocated slot in compiledMethods[].
*/
private static int currentCompiledMethodId = 0;
/**
* Used to communicate between {@link #setCompiledMethodObsolete}
* and {@link #snipObsoleteCompiledMethods}
*/
private static boolean scanForObsoleteMethods = false;
/**
* Ensure space in backing array for id.
*
* @param id the id we need to ensure capacity for
*/
private static void ensureCapacity(int id) {
int column = id >> LOG_ROW_SIZE;
if (column >= compiledMethods.length) {
CompiledMethod[][] tmp = new CompiledMethod[column + 1][];
for (int i = 0; i < column; i++) {
tmp[i] = compiledMethods[i];
}
tmp[column] = new CompiledMethod[1 << LOG_ROW_SIZE];
compiledMethods = tmp;
Magic.sync();
}
}
/**
* @param cmid id of the method
* @return a previously compiled method without checking
*/
@Uninterruptible
public static CompiledMethod getCompiledMethodUnchecked(int cmid) {
int column = cmid >> LOG_ROW_SIZE;
return compiledMethods[column][cmid & ROW_MASK];
}
@Uninterruptible
private static void setCompiledMethod(int cmid, CompiledMethod cm) {
int column = cmid >> LOG_ROW_SIZE;
CompiledMethod[] col = compiledMethods[column];
Services.setArrayUninterruptible(col, cmid & ROW_MASK, cm);
}
/**
* @param compiledMethodId the id of the compiled method
* @return a previously compiled method
*/
@Uninterruptible
public static CompiledMethod getCompiledMethod(int compiledMethodId) {
Magic.isync(); // see potential update from other procs
if (VM.VerifyAssertions) {
if (!(0 < compiledMethodId && compiledMethodId <= currentCompiledMethodId)) {
VM.sysWriteln("WARNING: attempt to get compiled method #", compiledMethodId);
VM.sysFail("attempt to get an invalid compiled method ID");
return null;
}
}
return getCompiledMethodUnchecked(compiledMethodId);
}
public static synchronized CompiledMethod createCompiledMethod(RVMMethod m, int compilerType) {
int id = currentCompiledMethodId + 1;
ensureCapacity(id);
currentCompiledMethodId++;
CompiledMethod cm = null;
if (compilerType == CompiledMethod.BASELINE) {
if (VM.BuildForIA32) {
cm = new org.jikesrvm.compilers.baseline.ia32.ArchBaselineCompiledMethod(id, m);
} else {
if (VM.VerifyAssertions) VM._assert(VM.BuildForPowerPC);
cm = new org.jikesrvm.compilers.baseline.ppc.ArchBaselineCompiledMethod(id, m);
}
} else if (VM.BuildForOptCompiler && compilerType == CompiledMethod.OPT) {
cm = new OptCompiledMethod(id, m);
} else if (compilerType == CompiledMethod.JNI) {
cm = new JNICompiledMethod(id, m);
} else {
if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED, "Unexpected compiler type!");
}
setCompiledMethod(id, cm);
return cm;
}
/**
* @return a CompiledMethod for the synthetic hardware trap frame
*/
public static synchronized CompiledMethod createHardwareTrapCompiledMethod() {
int id = currentCompiledMethodId + 1;
ensureCapacity(id);
currentCompiledMethodId++;
CompiledMethod cm = new HardwareTrapCompiledMethod(id, null);
setCompiledMethod(id, cm);
return cm;
}
/**
* @return number of methods compiled so far.
*/
@Uninterruptible
public static int numCompiledMethods() {
return currentCompiledMethodId + 1;
}
/**
* Find the method whose machine code contains the specified instruction.<p>
*
* Assumption: caller has disabled gc (otherwise collector could move
* objects without fixing up the raw <code>ip</code> pointer)<p>
*
* Note: this method is highly inefficient. Normally you should use the
* following instead:
*
* <code>
* RVMClassLoader.getCompiledMethod(Magic.getCompiledMethodID(fp))
* </code>
*
* @param ip instruction address. Usage note: <code>ip</code> must point
* to the instruction *following* the
* actual instruction whose method is sought. This allows us to properly
* handle the case where the only address we have to work with is a return
* address (i.e. from a stackframe) or an exception address (i.e. from a null
* pointer dereference, array bounds check, or divide by zero) on a machine
* architecture with variable length instructions. In such situations we'd
* have no idea how far to back up the instruction pointer to point to the
* "call site" or "exception site".
*
* @return method (<code>null</code> --> not found)
*/
@Uninterruptible
public static CompiledMethod findMethodForInstruction(Address ip) {
for (int i = 0, n = numCompiledMethods(); i < n; ++i) {
CompiledMethod compiledMethod = getCompiledMethodUnchecked(i);
if (compiledMethod == null || !compiledMethod.isCompiled()) {
continue; // empty slot
}
if (compiledMethod.containsReturnAddress(ip)) {
return compiledMethod;
}
}
return null;
}
// We keep track of compiled methods that become obsolete because they have
// been replaced by another version. These are candidates for GC. But, they
// can only be collected once we are certain that they are no longer being
// executed. Here, we keep track of them until we know they are no longer
// in use.
public static void setCompiledMethodObsolete(CompiledMethod compiledMethod) {
compiledMethod.setObsolete();
Magic.sync();
scanForObsoleteMethods = true;
}
/**
* Snip reference to CompiledMethod so that we can reclaim code space. If
* the code is currently being executed, stack scanning is responsible for
* marking it NOT obsolete. Keep such reference until a future GC.
* <p>
* NOTE: It's expected that this is processed during GC, after scanning
* stacks to determine which methods are currently executing.
*/
@Uninterruptible
public static void snipObsoleteCompiledMethods() {
Magic.isync();
if (!scanForObsoleteMethods) return;
scanForObsoleteMethods = false;
Magic.sync();
int max = numCompiledMethods();
for (int i = 0; i < max; i++) {
CompiledMethod cm = getCompiledMethodUnchecked(i);
if (cm != null) {
if (cm.isActiveOnStack()) {
if (cm.isObsolete()) {
// can't get it this time; force us to look again next GC
scanForObsoleteMethods = true;
Magic.sync();
}
cm.clearActiveOnStack();
} else {
if (cm.isObsolete()) {
// obsolete and not active on a thread stack: it's garbage!
setCompiledMethod(i, null);
}
}
}
}
}
/**
* Report on the space used by compiled code and associated mapping information
*/
public static void spaceReport() {
int[] codeCount = new int[CompiledMethod.NUM_COMPILER_TYPES + 1];
int[] codeBytes = new int[CompiledMethod.NUM_COMPILER_TYPES + 1];
int[] mapBytes = new int[CompiledMethod.NUM_COMPILER_TYPES + 1];
RVMArray codeArray = RVMType.CodeArrayType.asArray();
for (int i = 0; i < numCompiledMethods(); i++) {
CompiledMethod cm = getCompiledMethodUnchecked(i);
if (cm == null || !cm.isCompiled()) continue;
int ct = cm.getCompilerType();
codeCount[ct]++;
int size = codeArray.getInstanceSize(cm.numberOfInstructions());
codeBytes[ct] += Memory.alignUp(size, BYTES_IN_ADDRESS);
mapBytes[ct] += cm.size();
}
VM.sysWriteln("Compiled code space report");
VM.sysWriteln();
VM.sysWriteln(" Baseline Compiler");
VM.sysWriteln(" Number of compiled methods = " + codeCount[CompiledMethod.BASELINE]);
VM.sysWriteln(" Total size of code (bytes) = " + codeBytes[CompiledMethod.BASELINE]);
VM.sysWriteln(" Total size of mapping data (bytes) = " + mapBytes[CompiledMethod.BASELINE]);
if (codeCount[CompiledMethod.OPT] > 0) {
VM.sysWriteln(" Optimizing Compiler");
VM.sysWriteln(" Number of compiled methods = " + codeCount[CompiledMethod.OPT]);
VM.sysWriteln(" Total size of code (bytes) = " + codeBytes[CompiledMethod.OPT]);
VM.sysWriteln(" Total size of mapping data (bytes) = " + mapBytes[CompiledMethod.OPT]);
}
if (codeCount[CompiledMethod.JNI] > 0) {
VM.sysWriteln(" JNI Stub Compiler (Java->C stubs for native methods)");
VM.sysWriteln(" Number of compiled methods = " + codeCount[CompiledMethod.JNI]);
VM.sysWriteln(" Total size of code (bytes) = " + codeBytes[CompiledMethod.JNI]);
VM.sysWriteln(" Total size of mapping data (bytes) = " + mapBytes[CompiledMethod.JNI]);
}
if (!VM.runningVM) {
TreeMap<String, Integer> packageData = new TreeMap<String, Integer>(
new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
for (int i = 0; i < numCompiledMethods(); ++i) {
CompiledMethod compiledMethod = getCompiledMethodUnchecked(i);
if (compiledMethod != null) {
RVMMethod m = compiledMethod.getMethod();
if (m != null && compiledMethod.isCompiled()) {
String packageName = m.getDeclaringClass().getPackageName();
int numInstructions = compiledMethod.numberOfInstructions();
Integer val = packageData.get(packageName);
if (val == null) {
val = numInstructions;
} else {
val = val + numInstructions;
}
packageData.put(packageName, val);
}
}
}
VM.sysWriteln("------------------------------------------------------------------------------------------");
VM.sysWriteln(" Break down of code space usage by package (bytes):");
VM.sysWriteln("------------------------------------------------------------------------------------------");
Set<String> keys = packageData.keySet();
int maxPackageNameSize = 0;
for (String packageName : keys) {
maxPackageNameSize = Math.max(maxPackageNameSize, packageName.length());
}
maxPackageNameSize++;
for (String packageName : keys) {
VM.sysWriteField(maxPackageNameSize, packageName);
VM.sysWriteField(10, packageData.get(packageName));
VM.sysWriteln();
}
}
}
}