/*
* 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.tools.oth;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Vector;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.Atom;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMClassLoader;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.baseline.BaselineCompiler;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.opt.OptimizingCompilerException;
import org.jikesrvm.compilers.opt.OptOptions;
import org.jikesrvm.compilers.opt.driver.CompilationPlan;
import org.jikesrvm.compilers.opt.driver.OptimizationPlanner;
import org.jikesrvm.compilers.opt.driver.OptimizingCompiler;
import org.jikesrvm.runtime.Callbacks;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.Reflection;
import org.jikesrvm.runtime.Time;
import org.vmmagic.unboxed.Address;
/**
* A test harness for the optimizing compiler.
* <p>
* The role of this class is to allow the optimizing compiler
* to be run as an "application" to enabling selective testing and
* debugging of the optimizing compiler.
* For example, the following command line:
* <br>
* <pre>rvm -X:h=100 org.jikesrvm.tools.oth.OptTestHarness -oc:O2 -oc:phases=true -class hanoi -er hanoi run -</pre>
* <br>
* invokes the opt compiler at Opt level 2 and phases=true to compile
* the class hanoi, it then executes the run method of class hanoi.
* <p>
* Any command that can be given to the optimizing compiler via -X:irc:<cmd>
* can be given to the optimizing compiler by org.jikesrvm.tools.oth.OptTestHarness via -oc:<cmd>.
* In addition, the org.jikesrvm.tools.oth.OptTestHarness supports the following commands:
* <pre>
* -useBootOptions Use the same OptOptions as the bootimage compiler.
* -longcommandline <filename> Read commands (one per line) from a file
* +baseline Switch default compiler to baseline
* -baseline Switch default compiler to optimizing
* -load <class > Load a class
* -class <class> Load a class and compile all its methods
* -method <class> <method> [-|<descrip>] Compile method with default compiler
* -methodOpt <class> <method> [-|<descrip>] Compile method with opt compiler
* -methodBase <class> <method> [-|<descrip>] Compile method with base compiler
* -er <class> <method> [-|<descrip>] {args} Compile with default compiler and execute a method
* -performance Show performance results
* </pre>
*/
class OptTestHarness {
boolean disableClassloading = false;
boolean executeWithReflection = false;
boolean executeMainMethod = false;
/** Default value for for compiling opt/baseline */
boolean useBaselineCompiler = false;
/**
* Should we print the address of compiled methods (useful for
* debugging)
*/
boolean printCodeAddress = true;
boolean addCallbackForPerformancePrintout = true;
/** Record and show performance of executed methods, if any */
Performance perf;
ClassLoader cl;
// Keep baseline and opt methods separate in list of methods
// to be compiled
Vector<RVMMethod> optMethodVector = null;
Vector<OptOptions> optOptionsVector = null;
Vector<RVMMethod> baselineMethodVector = null;
java.lang.reflect.Method reflectoid;
Object[] reflectMethodArgs;
Vector<Method> reflectoidVector;
Vector<RVMMethod> reflectMethodVector;
Vector<Object[]> reflectMethodArgsVector;
RVMClass mainClass;
String[] mainArgs;
private final OptTestHarnessOutput output;
private final FileAccess fileAccess;
OptTestHarness(OptTestHarnessOutput output, OptOptions optOptions, FileAccess fileAccess) {
this.output = output;
options = optOptions;
this.fileAccess = fileAccess;
}
int parseMethodArgs(TypeReference[] argDesc, String[] args, int i, Object[] methodArgs) {
try {
for (int argNum = 0; argNum < argDesc.length; ++argNum) {
if (argDesc[argNum].isBooleanType()) {
methodArgs[argNum] = Boolean.valueOf(args[++i]);
} else if (argDesc[argNum].isByteType()) {
methodArgs[argNum] = Byte.valueOf(args[++i]);
} else if (argDesc[argNum].isShortType()) {
methodArgs[argNum] = Short.valueOf(args[++i]);
} else if (argDesc[argNum].isIntType()) {
methodArgs[argNum] = Integer.valueOf(args[++i]);
} else if (argDesc[argNum].isLongType()) {
methodArgs[argNum] = Long.valueOf(args[++i]);
} else if (argDesc[argNum].isFloatType()) {
methodArgs[argNum] = Float.valueOf(args[++i]);
} else if (argDesc[argNum].isDoubleType()) {
methodArgs[argNum] = Double.valueOf(args[++i]);
} else if (argDesc[argNum].isCharType()) {
methodArgs[argNum] = args[++i].charAt(0);
} else if (argDesc[argNum].isClassType()) {
// TODO
output.sysErrPrintln("Parsing args of type " + argDesc[argNum] + " not implemented");
} else if (argDesc[argNum].isArrayType()) {
TypeReference element = argDesc[argNum].getArrayElementType();
if (element.equals(TypeReference.JavaLangString)) {
String[] array = new String[args.length - i - 1];
for (int j = 0; j < array.length; j++) {
array[j] = args[++i];
}
methodArgs[argNum] = array;
} else {
// TODO implement it
output.sysErrPrintln("Parsing args of array of " + element + " not implemented");
}
}
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new InternalError("Error: not enough method arguments specified on command line after -er");
}
return i;
}
/**
* Finds a method, either one with a given descriptor or the first matching
* one in in the given class.
* @param klass the class to search
* @param methname the method's name
* @param methdesc a descriptor of the method's signature if a specific
* method is desired or "-" to find the first method with the given name
* @return the method or {@code null} if no method was found
*/
RVMMethod findDeclaredOrFirstMethod(RVMClass klass, String methname, String methdesc) {
if (klass == null) return null;
Atom methodName = Atom.findOrCreateAsciiAtom(methname);
Atom methodDesc = "-".equals(methdesc) ? null : Atom.findOrCreateAsciiAtom(methdesc);
for (RVMMethod method : klass.getDeclaredMethods()) {
if (method.getName() == methodName && ((methodDesc == null) || (methodDesc == method.getDescriptor()))) {
return method;
}
}
if (methodDesc == null) {
output.sysErrPrintln("No method named " + methodName + " found in class " + klass);
} else {
output.sysErrPrintln("No method matching " + methodName + " " + methodDesc + " found in class " + klass);
}
return null;
}
RVMClass loadClass(String s) throws ClassNotFoundException {
String className = convertToClassName(s);
Class<?> clazz = Class.forName(className, true, cl);
return (RVMClass) java.lang.JikesRVMSupport.getTypeForClass(clazz);
}
static String convertToClassName(String s) {
if (s.startsWith("./")) {
s = s.substring(2, s.length());
} else if (s.startsWith("L") && s.endsWith(";")) {
// parse the class signature
s = s.substring(1, s.length() - 1);
}
if (s.endsWith(".java")) {
s = s.substring(0, s.length() - 5);
} else if (s.endsWith(".class")) {
s = s.substring(0, s.length() - 6);
}
return s;
}
void printFormatString() {
output.sysErrPrintln("Format: rvm org.jikesrvm.tools.oth.OptTestHarness { <command> }");
}
private void processClass(RVMClass klass, OptOptions opts) {
RVMMethod[] methods = klass.getDeclaredMethods();
for (RVMMethod method : methods) {
if (!method.isAbstract() && !method.isNative()) {
processMethod(method, opts);
}
}
}
// Wrapper applying default decision regarding opt/baseline
private void processMethod(RVMMethod method, OptOptions opts) {
processMethod(method, opts, useBaselineCompiler);
}
private void processMethod(RVMMethod method, OptOptions opts, boolean isBaseline) {
if (isBaseline) {
// Method to be baseline compiled
if (!baselineMethodVector.contains(method)) {
baselineMethodVector.add(method);
}
} else if (!optMethodVector.contains(method)) {
// Method to be opt compiled
optMethodVector.add(method);
optOptionsVector.add(opts);
}
}
// process the command line option
OptOptions options;
private void processOptionString(String[] args) {
for (int i = 0, n = args.length; i < n; i++) {
try {
String arg = args[i];
if (arg.startsWith("-oc:") && options.processAsOption("-X:irc:", arg.substring(4))) {
// handled in processAsOption
} else if ("-useBootOptions".equals(arg)) {
OptimizingCompiler.setBootOptions(options);
} else if ("-longcommandline".equals(arg)) {
// the -longcommandline option reads options from a file.
String fileName = args[++i];
String[] optionString = fileAccess.readOptionStringFromFile(fileName);
processOptionString(optionString);
} else if ("+baseline".equals(arg)) {
useBaselineCompiler = true;
} else if ("-baseline".equals(arg)) {
useBaselineCompiler = false;
} else if ("-load".equals(arg)) {
loadClass(args[++i]);
} else if ("-class".equals(arg)) {
RVMClass klass = loadClass(args[++i]);
processClass(klass, options);
duplicateOptions();
} else if ("-method".equals(arg) || "-methodOpt".equals(arg) || "-methodBase".equals(arg)) {
// Default for this method is determined by BASELINE var
boolean isBaseline = useBaselineCompiler;
// Unless specified by these options
if ("-methodOpt".equals(arg)) {
isBaseline = false;
}
if ("-methodBase".equals(arg)) {
isBaseline = true;
}
RVMClass klass = null;
try {
klass = loadClass(args[++i]);
} catch (Exception e) {
output.sysErrPrintln("WARNING: Skipping method from " + args[i]);
}
if (klass == null) continue;
String name = args[++i];
String desc = args[++i];
RVMMethod method = findDeclaredOrFirstMethod(klass, name, desc);
if (method == null || method.isAbstract() || method.isNative()) {
output.sysErrPrintln("WARNING: Skipping method " + args[i - 2] + "." + name);
} else {
processMethod(method, options, isBaseline);
}
duplicateOptions();
} else if ("-performance".equals(arg)) {
perf = new Performance(output);
} else if ("-disableClassLoading".equals(arg)) {
disableClassloading = true;
} else if ("-er".equals(arg)) {
executeWithReflection = true;
RVMClass klass = loadClass(args[++i]);
String name = args[++i];
String desc = args[++i];
NormalMethod method = (NormalMethod) findDeclaredOrFirstMethod(klass, name, desc);
CompiledMethod cm = null;
if (method == null) {
output.sysErrPrintln("Canceling further option processing to prevent assertion failures.");
return;
}
if (useBaselineCompiler) {
cm = BaselineCompiler.compile(method);
} else {
CompilationPlan cp =
new CompilationPlan(method, OptimizationPlanner.createOptimizationPlan(options), null, options);
try {
cm = OptimizingCompiler.compile(cp);
} catch (Throwable e) {
output.sysErrPrintln("SKIPPING method:" + method + "Due to exception: " + e);
}
}
if (cm != null) {
method.replaceCompiledMethod(cm);
if (printCodeAddress) {
output.sysOutPrintln(compiledMethodMessage(method));
}
}
TypeReference[] argDesc = method.getDescriptor().parseForParameterTypes(klass.getClassLoader());
Object[] reflectMethodArgs = new Object[argDesc.length];
i = parseMethodArgs(argDesc, args, i, reflectMethodArgs);
java.lang.reflect.Method reflectoid = java.lang.reflect.JikesRVMSupport.createMethod(method);
reflectoidVector.add(reflectoid);
reflectMethodVector.add(method);
reflectMethodArgsVector.add(reflectMethodArgs);
duplicateOptions();
} else if ("-main".equals(arg)) {
executeMainMethod = true;
i++;
mainClass = loadClass(args[i]);
i++;
mainArgs = new String[args.length - i];
for (int j = 0, z = mainArgs.length; j < z; j++) {
mainArgs[j] = args[i + j];
}
break;
} else {
output.sysErrPrintln("Unrecognized argument: " + arg + " - ignored");
}
} catch (ArrayIndexOutOfBoundsException e) {
output.sysErrPrintln("Uncaught ArrayIndexOutOfBoundsException, possibly" +
" not enough command-line arguments - aborting");
printFormatString();
e.printStackTrace(output.getSystemErr());
break;
} catch (Exception e) {
output.sysErrPrintln(e.toString());
e.printStackTrace(output.getSystemErr());
break;
}
}
}
private void duplicateOptions() {
if (VM.BuildForOptCompiler) {
options = options.dup();
}
}
static String compiledMethodMessage(NormalMethod method) {
CompiledMethod cm = method.getCurrentCompiledMethod();
Address addr = Magic.objectAsAddress(cm.getEntryCodeArray());
return "Method: " + method + " compiled code: " + addrToString(addr);
}
private void compileMethodsInVector() {
// Compile all baseline methods first
int size = baselineMethodVector.size();
output.sysOutPrintln("Compiling " + size + " methods baseline");
// Compile all methods in baseline vector
for (int i = 0; i < size; i++) {
NormalMethod method = (NormalMethod) baselineMethodVector.get(i);
CompiledMethod cm = null;
cm = BaselineCompiler.compile(method);
method.replaceCompiledMethod(cm);
if (printCodeAddress) {
output.sysOutPrintln(compiledMethodMessage(method));
}
}
// Now compile all methods in opt vector
size = optMethodVector.size();
output.sysOutPrintln("Compiling " + size + " methods opt");
for (int i = 0; i < size; i++) {
NormalMethod method = (NormalMethod) optMethodVector.get(i);
OptOptions opts = optOptionsVector.get(i);
try {
CompiledMethod cm = null;
CompilationPlan cp =
new CompilationPlan(method, OptimizationPlanner.createOptimizationPlan(opts), null, opts);
cm = OptimizingCompiler.compile(cp);
method.replaceCompiledMethod(cm);
if (printCodeAddress) {
output.sysOutPrintln(compiledMethodMessage(method));
}
} catch (OptimizingCompilerException e) {
if (e.isFatal && VM.ErrorsFatal) {
e.printStackTrace();
VM.sysFail("Internal vm error: " + e);
} else {
output.sysErrPrintln("SKIPPING opt-compilation of " + method + ":\n " + e.getMessage());
if (opts.PRINT_METHOD) {
e.printStackTrace();
}
}
}
}
}
private void executeCommand() throws InvocationTargetException, IllegalAccessException {
compileMethodsInVector();
if (executeWithReflection) {
if (disableClassloading) {
RVMClass.setClassLoadingDisabled(true);
}
int size = reflectoidVector.size();
for (int i = 0; i < size; i++) {
reflectoid = reflectoidVector.get(i);
reflectMethodArgs = reflectMethodArgsVector.get(i);
RVMMethod method = reflectMethodVector.get(i);
output.sysOutPrintln(startOfExecutionString(method));
Object result = null;
if (perf != null) perf.reset();
Object receiver = null;
if (!method.isStatic()) {
receiver = attemptToInvokeDefaultConstructor(method);
if (receiver == null) {
continue;
}
}
result = reflectoid.invoke(receiver, reflectMethodArgs);
if (perf != null) perf.stop();
output.sysOutPrintln(endOfExecutionString(method));
output.sysOutPrintln(resultString(result));
}
executeWithReflection = false;
}
if (executeMainMethod) {
RVMMethod mainMethod = mainClass.findMainMethod();
if (mainMethod == null) {
// no such method
output.sysErrPrintln(mainClass + " doesn't have a \"public static void main(String[])\" method to execute\n");
return;
}
output.sysOutPrintln(startOfExecutionString(mainMethod));
Reflection.invoke(mainMethod, null, null, new Object[]{mainArgs}, true);
output.sysOutPrintln(endOfExecutionString(mainMethod));
}
}
private Object attemptToInvokeDefaultConstructor(RVMMethod method) {
Object receiver = null;
try {
receiver = method.getDeclaringClass().getClassForType().newInstance();
} catch (Exception e) {
output.sysErrPrintln("Invocation of default constructor failed for method " + method);
e.printStackTrace(output.getSystemErr());
}
return receiver;
}
static String resultString(Object result) {
return "**** RESULT: " + result;
}
static String endOfExecutionString(RVMMethod method) {
return "**** END OF EXECUTION of " + method + " ****.";
}
static String startOfExecutionString(RVMMethod method) {
return "**** START OF EXECUTION of " + method + " ****.";
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
OptTestHarness oth = new OptTestHarness(new DefaultOutput(), new OptOptions(), new DefaultFileAccess());
oth.mainMethod(args);
}
public void mainMethod(String[] args) throws InvocationTargetException, IllegalAccessException {
cl = RVMClassLoader.getApplicationClassLoader();
optMethodVector = new Vector<RVMMethod>(50);
optOptionsVector = new Vector<OptOptions>(50);
baselineMethodVector = new Vector<RVMMethod>(50);
reflectoidVector = new Vector<Method>(10);
reflectMethodVector = new Vector<RVMMethod>(10);
reflectMethodArgsVector = new Vector<Object[]>(10);
if (VM.BuildForOptCompiler && !OptimizingCompiler.isInitialized()) {
OptimizingCompiler.init(options);
} else if (!VM.BuildForOptCompiler) {
useBaselineCompiler = true;
}
processOptionString(args);
if (perf != null && addCallbackForPerformancePrintout) {
Callbacks.addExitMonitor(perf);
}
executeCommand();
if (perf != null) {
perf.show();
}
}
private static String addrToString(Address addr) {
if (VM.BuildFor32Addr) {
return Integer.toHexString(addr.toInt());
} else if (VM.BuildFor64Addr) {
return Long.toHexString(addr.toLong());
}
return null;
}
private static class Performance implements Callbacks.ExitMonitor {
private long start = 0;
private long end = 0;
private final OptTestHarnessOutput output;
Performance(OptTestHarnessOutput output) {
this.output = output;
}
void reset() {
start = Time.nanoTime();
}
void stop() {
if (end == 0)
end = Time.nanoTime();
}
void show() {
stop(); // In case we got here due to a System.exit
output.sysOutPrintln("");
output.sysOutPrintln("Performance of executed method");
output.sysOutPrintln("------------------------------");
output.sysOutPrint("Elapsed wallclock time: ");
output.sysOutPrint(Double.toString(Time.nanosToMillis(end - start)));
output.sysOutPrintln(" msec");
}
@Override
public void notifyExit(int discard) {
show();
}
}
}