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.tools.oth; 014 015import java.lang.reflect.InvocationTargetException; 016import java.lang.reflect.Method; 017import java.util.Vector; 018 019import org.jikesrvm.VM; 020import org.jikesrvm.classloader.Atom; 021import org.jikesrvm.classloader.RVMClass; 022import org.jikesrvm.classloader.RVMClassLoader; 023import org.jikesrvm.classloader.RVMMethod; 024import org.jikesrvm.classloader.NormalMethod; 025import org.jikesrvm.classloader.TypeReference; 026import org.jikesrvm.compilers.baseline.BaselineCompiler; 027import org.jikesrvm.compilers.common.CompiledMethod; 028import org.jikesrvm.compilers.opt.OptimizingCompilerException; 029import org.jikesrvm.compilers.opt.OptOptions; 030import org.jikesrvm.compilers.opt.driver.CompilationPlan; 031import org.jikesrvm.compilers.opt.driver.OptimizationPlanner; 032import org.jikesrvm.compilers.opt.driver.OptimizingCompiler; 033import org.jikesrvm.runtime.Callbacks; 034import org.jikesrvm.runtime.Magic; 035import org.jikesrvm.runtime.Reflection; 036import org.jikesrvm.runtime.Time; 037import org.vmmagic.unboxed.Address; 038 039/** 040 * A test harness for the optimizing compiler. 041 * <p> 042 * The role of this class is to allow the optimizing compiler 043 * to be run as an "application" to enabling selective testing and 044 * debugging of the optimizing compiler. 045 * For example, the following command line: 046 * <br> 047 * <pre>rvm -X:h=100 org.jikesrvm.tools.oth.OptTestHarness -oc:O2 -oc:phases=true -class hanoi -er hanoi run -</pre> 048 * <br> 049 * invokes the opt compiler at Opt level 2 and phases=true to compile 050 * the class hanoi, it then executes the run method of class hanoi. 051 * <p> 052 * Any command that can be given to the optimizing compiler via -X:irc:<cmd> 053 * can be given to the optimizing compiler by org.jikesrvm.tools.oth.OptTestHarness via -oc:<cmd>. 054 * In addition, the org.jikesrvm.tools.oth.OptTestHarness supports the following commands: 055 * <pre> 056 * -useBootOptions Use the same OptOptions as the bootimage compiler. 057 * -longcommandline <filename> Read commands (one per line) from a file 058 * +baseline Switch default compiler to baseline 059 * -baseline Switch default compiler to optimizing 060 * -load <class > Load a class 061 * -class <class> Load a class and compile all its methods 062 * -method <class> <method> [-|<descrip>] Compile method with default compiler 063 * -methodOpt <class> <method> [-|<descrip>] Compile method with opt compiler 064 * -methodBase <class> <method> [-|<descrip>] Compile method with base compiler 065 * -er <class> <method> [-|<descrip>] {args} Compile with default compiler and execute a method 066 * -performance Show performance results 067 * </pre> 068 */ 069class OptTestHarness { 070 boolean disableClassloading = false; 071 boolean executeWithReflection = false; 072 boolean executeMainMethod = false; 073 /** Default value for for compiling opt/baseline */ 074 boolean useBaselineCompiler = false; 075 076 /** 077 * Should we print the address of compiled methods (useful for 078 * debugging) 079 */ 080 boolean printCodeAddress = true; 081 082 boolean addCallbackForPerformancePrintout = true; 083 084 /** Record and show performance of executed methods, if any */ 085 Performance perf; 086 087 ClassLoader cl; 088 089 // Keep baseline and opt methods separate in list of methods 090 // to be compiled 091 Vector<RVMMethod> optMethodVector = null; 092 Vector<OptOptions> optOptionsVector = null; 093 Vector<RVMMethod> baselineMethodVector = null; 094 095 java.lang.reflect.Method reflectoid; 096 Object[] reflectMethodArgs; 097 Vector<Method> reflectoidVector; 098 Vector<RVMMethod> reflectMethodVector; 099 Vector<Object[]> reflectMethodArgsVector; 100 101 RVMClass mainClass; 102 String[] mainArgs; 103 104 private final OptTestHarnessOutput output; 105 private final FileAccess fileAccess; 106 107 OptTestHarness(OptTestHarnessOutput output, OptOptions optOptions, FileAccess fileAccess) { 108 this.output = output; 109 options = optOptions; 110 this.fileAccess = fileAccess; 111 } 112 113 int parseMethodArgs(TypeReference[] argDesc, String[] args, int i, Object[] methodArgs) { 114 try { 115 for (int argNum = 0; argNum < argDesc.length; ++argNum) { 116 if (argDesc[argNum].isBooleanType()) { 117 methodArgs[argNum] = Boolean.valueOf(args[++i]); 118 } else if (argDesc[argNum].isByteType()) { 119 methodArgs[argNum] = Byte.valueOf(args[++i]); 120 } else if (argDesc[argNum].isShortType()) { 121 methodArgs[argNum] = Short.valueOf(args[++i]); 122 } else if (argDesc[argNum].isIntType()) { 123 methodArgs[argNum] = Integer.valueOf(args[++i]); 124 } else if (argDesc[argNum].isLongType()) { 125 methodArgs[argNum] = Long.valueOf(args[++i]); 126 } else if (argDesc[argNum].isFloatType()) { 127 methodArgs[argNum] = Float.valueOf(args[++i]); 128 } else if (argDesc[argNum].isDoubleType()) { 129 methodArgs[argNum] = Double.valueOf(args[++i]); 130 } else if (argDesc[argNum].isCharType()) { 131 methodArgs[argNum] = args[++i].charAt(0); 132 } else if (argDesc[argNum].isClassType()) { 133 // TODO 134 output.sysErrPrintln("Parsing args of type " + argDesc[argNum] + " not implemented"); 135 } else if (argDesc[argNum].isArrayType()) { 136 TypeReference element = argDesc[argNum].getArrayElementType(); 137 if (element.equals(TypeReference.JavaLangString)) { 138 String[] array = new String[args.length - i - 1]; 139 for (int j = 0; j < array.length; j++) { 140 array[j] = args[++i]; 141 } 142 methodArgs[argNum] = array; 143 } else { 144 // TODO implement it 145 output.sysErrPrintln("Parsing args of array of " + element + " not implemented"); 146 } 147 } 148 } 149 } catch (ArrayIndexOutOfBoundsException e) { 150 throw new InternalError("Error: not enough method arguments specified on command line after -er"); 151 } 152 return i; 153 } 154 155 156 /** 157 * Finds a method, either one with a given descriptor or the first matching 158 * one in in the given class. 159 * @param klass the class to search 160 * @param methname the method's name 161 * @param methdesc a descriptor of the method's signature if a specific 162 * method is desired or "-" to find the first method with the given name 163 * @return the method or {@code null} if no method was found 164 */ 165 RVMMethod findDeclaredOrFirstMethod(RVMClass klass, String methname, String methdesc) { 166 if (klass == null) return null; 167 Atom methodName = Atom.findOrCreateAsciiAtom(methname); 168 Atom methodDesc = "-".equals(methdesc) ? null : Atom.findOrCreateAsciiAtom(methdesc); 169 170 for (RVMMethod method : klass.getDeclaredMethods()) { 171 if (method.getName() == methodName && ((methodDesc == null) || (methodDesc == method.getDescriptor()))) { 172 return method; 173 } 174 } 175 if (methodDesc == null) { 176 output.sysErrPrintln("No method named " + methodName + " found in class " + klass); 177 } else { 178 output.sysErrPrintln("No method matching " + methodName + " " + methodDesc + " found in class " + klass); 179 } 180 return null; 181 } 182 183 RVMClass loadClass(String s) throws ClassNotFoundException { 184 String className = convertToClassName(s); 185 Class<?> clazz = Class.forName(className, true, cl); 186 return (RVMClass) java.lang.JikesRVMSupport.getTypeForClass(clazz); 187 } 188 189 static String convertToClassName(String s) { 190 if (s.startsWith("./")) { 191 s = s.substring(2, s.length()); 192 } else if (s.startsWith("L") && s.endsWith(";")) { 193 // parse the class signature 194 s = s.substring(1, s.length() - 1); 195 } 196 197 if (s.endsWith(".java")) { 198 s = s.substring(0, s.length() - 5); 199 } else if (s.endsWith(".class")) { 200 s = s.substring(0, s.length() - 6); 201 } 202 203 return s; 204 } 205 206 void printFormatString() { 207 output.sysErrPrintln("Format: rvm org.jikesrvm.tools.oth.OptTestHarness { <command> }"); 208 } 209 210 private void processClass(RVMClass klass, OptOptions opts) { 211 RVMMethod[] methods = klass.getDeclaredMethods(); 212 for (RVMMethod method : methods) { 213 if (!method.isAbstract() && !method.isNative()) { 214 processMethod(method, opts); 215 } 216 } 217 } 218 219 // Wrapper applying default decision regarding opt/baseline 220 private void processMethod(RVMMethod method, OptOptions opts) { 221 processMethod(method, opts, useBaselineCompiler); 222 } 223 224 private void processMethod(RVMMethod method, OptOptions opts, boolean isBaseline) { 225 if (isBaseline) { 226 // Method to be baseline compiled 227 if (!baselineMethodVector.contains(method)) { 228 baselineMethodVector.add(method); 229 } 230 } else if (!optMethodVector.contains(method)) { 231 // Method to be opt compiled 232 optMethodVector.add(method); 233 optOptionsVector.add(opts); 234 } 235 } 236 237 // process the command line option 238 OptOptions options; 239 240 private void processOptionString(String[] args) { 241 for (int i = 0, n = args.length; i < n; i++) { 242 try { 243 String arg = args[i]; 244 if (arg.startsWith("-oc:") && options.processAsOption("-X:irc:", arg.substring(4))) { 245 // handled in processAsOption 246 } else if ("-useBootOptions".equals(arg)) { 247 OptimizingCompiler.setBootOptions(options); 248 } else if ("-longcommandline".equals(arg)) { 249 // the -longcommandline option reads options from a file. 250 String fileName = args[++i]; 251 String[] optionString = fileAccess.readOptionStringFromFile(fileName); 252 processOptionString(optionString); 253 } else if ("+baseline".equals(arg)) { 254 useBaselineCompiler = true; 255 } else if ("-baseline".equals(arg)) { 256 useBaselineCompiler = false; 257 } else if ("-load".equals(arg)) { 258 loadClass(args[++i]); 259 } else if ("-class".equals(arg)) { 260 RVMClass klass = loadClass(args[++i]); 261 processClass(klass, options); 262 duplicateOptions(); 263 } else if ("-method".equals(arg) || "-methodOpt".equals(arg) || "-methodBase".equals(arg)) { 264 // Default for this method is determined by BASELINE var 265 boolean isBaseline = useBaselineCompiler; 266 // Unless specified by these options 267 if ("-methodOpt".equals(arg)) { 268 isBaseline = false; 269 } 270 if ("-methodBase".equals(arg)) { 271 isBaseline = true; 272 } 273 274 RVMClass klass = null; 275 try { 276 klass = loadClass(args[++i]); 277 } catch (Exception e) { 278 output.sysErrPrintln("WARNING: Skipping method from " + args[i]); 279 } 280 if (klass == null) continue; 281 String name = args[++i]; 282 String desc = args[++i]; 283 RVMMethod method = findDeclaredOrFirstMethod(klass, name, desc); 284 if (method == null || method.isAbstract() || method.isNative()) { 285 output.sysErrPrintln("WARNING: Skipping method " + args[i - 2] + "." + name); 286 } else { 287 processMethod(method, options, isBaseline); 288 } 289 duplicateOptions(); 290 } else if ("-performance".equals(arg)) { 291 perf = new Performance(output); 292 } else if ("-disableClassLoading".equals(arg)) { 293 disableClassloading = true; 294 } else if ("-er".equals(arg)) { 295 executeWithReflection = true; 296 RVMClass klass = loadClass(args[++i]); 297 String name = args[++i]; 298 String desc = args[++i]; 299 NormalMethod method = (NormalMethod) findDeclaredOrFirstMethod(klass, name, desc); 300 CompiledMethod cm = null; 301 if (method == null) { 302 output.sysErrPrintln("Canceling further option processing to prevent assertion failures."); 303 return; 304 } 305 if (useBaselineCompiler) { 306 cm = BaselineCompiler.compile(method); 307 } else { 308 CompilationPlan cp = 309 new CompilationPlan(method, OptimizationPlanner.createOptimizationPlan(options), null, options); 310 try { 311 cm = OptimizingCompiler.compile(cp); 312 } catch (Throwable e) { 313 output.sysErrPrintln("SKIPPING method:" + method + "Due to exception: " + e); 314 } 315 } 316 if (cm != null) { 317 method.replaceCompiledMethod(cm); 318 if (printCodeAddress) { 319 output.sysOutPrintln(compiledMethodMessage(method)); 320 } 321 } 322 TypeReference[] argDesc = method.getDescriptor().parseForParameterTypes(klass.getClassLoader()); 323 Object[] reflectMethodArgs = new Object[argDesc.length]; 324 i = parseMethodArgs(argDesc, args, i, reflectMethodArgs); 325 java.lang.reflect.Method reflectoid = java.lang.reflect.JikesRVMSupport.createMethod(method); 326 reflectoidVector.add(reflectoid); 327 reflectMethodVector.add(method); 328 reflectMethodArgsVector.add(reflectMethodArgs); 329 duplicateOptions(); 330 } else if ("-main".equals(arg)) { 331 executeMainMethod = true; 332 i++; 333 mainClass = loadClass(args[i]); 334 i++; 335 mainArgs = new String[args.length - i]; 336 for (int j = 0, z = mainArgs.length; j < z; j++) { 337 mainArgs[j] = args[i + j]; 338 } 339 break; 340 } else { 341 output.sysErrPrintln("Unrecognized argument: " + arg + " - ignored"); 342 } 343 } catch (ArrayIndexOutOfBoundsException e) { 344 output.sysErrPrintln("Uncaught ArrayIndexOutOfBoundsException, possibly" + 345 " not enough command-line arguments - aborting"); 346 printFormatString(); 347 e.printStackTrace(output.getSystemErr()); 348 break; 349 } catch (Exception e) { 350 output.sysErrPrintln(e.toString()); 351 e.printStackTrace(output.getSystemErr()); 352 break; 353 } 354 } 355 } 356 357 private void duplicateOptions() { 358 if (VM.BuildForOptCompiler) { 359 options = options.dup(); 360 } 361 } 362 363 static String compiledMethodMessage(NormalMethod method) { 364 CompiledMethod cm = method.getCurrentCompiledMethod(); 365 Address addr = Magic.objectAsAddress(cm.getEntryCodeArray()); 366 return "Method: " + method + " compiled code: " + addrToString(addr); 367 } 368 369 private void compileMethodsInVector() { 370 // Compile all baseline methods first 371 int size = baselineMethodVector.size(); 372 output.sysOutPrintln("Compiling " + size + " methods baseline"); 373 // Compile all methods in baseline vector 374 for (int i = 0; i < size; i++) { 375 NormalMethod method = (NormalMethod) baselineMethodVector.get(i); 376 CompiledMethod cm = null; 377 cm = BaselineCompiler.compile(method); 378 method.replaceCompiledMethod(cm); 379 if (printCodeAddress) { 380 output.sysOutPrintln(compiledMethodMessage(method)); 381 } 382 } 383 384 // Now compile all methods in opt vector 385 size = optMethodVector.size(); 386 output.sysOutPrintln("Compiling " + size + " methods opt"); 387 for (int i = 0; i < size; i++) { 388 NormalMethod method = (NormalMethod) optMethodVector.get(i); 389 OptOptions opts = optOptionsVector.get(i); 390 try { 391 CompiledMethod cm = null; 392 CompilationPlan cp = 393 new CompilationPlan(method, OptimizationPlanner.createOptimizationPlan(opts), null, opts); 394 cm = OptimizingCompiler.compile(cp); 395 method.replaceCompiledMethod(cm); 396 if (printCodeAddress) { 397 output.sysOutPrintln(compiledMethodMessage(method)); 398 } 399 } catch (OptimizingCompilerException e) { 400 if (e.isFatal && VM.ErrorsFatal) { 401 e.printStackTrace(); 402 VM.sysFail("Internal vm error: " + e); 403 } else { 404 output.sysErrPrintln("SKIPPING opt-compilation of " + method + ":\n " + e.getMessage()); 405 if (opts.PRINT_METHOD) { 406 e.printStackTrace(); 407 } 408 } 409 } 410 } 411 } 412 413 private void executeCommand() throws InvocationTargetException, IllegalAccessException { 414 compileMethodsInVector(); 415 416 if (executeWithReflection) { 417 418 if (disableClassloading) { 419 RVMClass.setClassLoadingDisabled(true); 420 } 421 422 int size = reflectoidVector.size(); 423 for (int i = 0; i < size; i++) { 424 reflectoid = reflectoidVector.get(i); 425 reflectMethodArgs = reflectMethodArgsVector.get(i); 426 RVMMethod method = reflectMethodVector.get(i); 427 output.sysOutPrintln(startOfExecutionString(method)); 428 Object result = null; 429 if (perf != null) perf.reset(); 430 Object receiver = null; 431 if (!method.isStatic()) { 432 receiver = attemptToInvokeDefaultConstructor(method); 433 if (receiver == null) { 434 continue; 435 } 436 } 437 result = reflectoid.invoke(receiver, reflectMethodArgs); 438 if (perf != null) perf.stop(); 439 output.sysOutPrintln(endOfExecutionString(method)); 440 output.sysOutPrintln(resultString(result)); 441 } 442 executeWithReflection = false; 443 } 444 445 if (executeMainMethod) { 446 RVMMethod mainMethod = mainClass.findMainMethod(); 447 if (mainMethod == null) { 448 // no such method 449 output.sysErrPrintln(mainClass + " doesn't have a \"public static void main(String[])\" method to execute\n"); 450 return; 451 } 452 output.sysOutPrintln(startOfExecutionString(mainMethod)); 453 Reflection.invoke(mainMethod, null, null, new Object[]{mainArgs}, true); 454 output.sysOutPrintln(endOfExecutionString(mainMethod)); 455 } 456 } 457 458 private Object attemptToInvokeDefaultConstructor(RVMMethod method) { 459 Object receiver = null; 460 try { 461 receiver = method.getDeclaringClass().getClassForType().newInstance(); 462 } catch (Exception e) { 463 output.sysErrPrintln("Invocation of default constructor failed for method " + method); 464 e.printStackTrace(output.getSystemErr()); 465 } 466 return receiver; 467 } 468 469 static String resultString(Object result) { 470 return "**** RESULT: " + result; 471 } 472 473 static String endOfExecutionString(RVMMethod method) { 474 return "**** END OF EXECUTION of " + method + " ****."; 475 } 476 477 static String startOfExecutionString(RVMMethod method) { 478 return "**** START OF EXECUTION of " + method + " ****."; 479 } 480 481 public static void main(String[] args) throws InvocationTargetException, IllegalAccessException { 482 OptTestHarness oth = new OptTestHarness(new DefaultOutput(), new OptOptions(), new DefaultFileAccess()); 483 oth.mainMethod(args); 484 } 485 486 public void mainMethod(String[] args) throws InvocationTargetException, IllegalAccessException { 487 cl = RVMClassLoader.getApplicationClassLoader(); 488 optMethodVector = new Vector<RVMMethod>(50); 489 optOptionsVector = new Vector<OptOptions>(50); 490 baselineMethodVector = new Vector<RVMMethod>(50); 491 reflectoidVector = new Vector<Method>(10); 492 reflectMethodVector = new Vector<RVMMethod>(10); 493 reflectMethodArgsVector = new Vector<Object[]>(10); 494 if (VM.BuildForOptCompiler && !OptimizingCompiler.isInitialized()) { 495 OptimizingCompiler.init(options); 496 } else if (!VM.BuildForOptCompiler) { 497 useBaselineCompiler = true; 498 } 499 processOptionString(args); 500 if (perf != null && addCallbackForPerformancePrintout) { 501 Callbacks.addExitMonitor(perf); 502 } 503 executeCommand(); 504 if (perf != null) { 505 perf.show(); 506 } 507 } 508 509 private static String addrToString(Address addr) { 510 if (VM.BuildFor32Addr) { 511 return Integer.toHexString(addr.toInt()); 512 } else if (VM.BuildFor64Addr) { 513 return Long.toHexString(addr.toLong()); 514 } 515 return null; 516 } 517 518 519 private static class Performance implements Callbacks.ExitMonitor { 520 private long start = 0; 521 private long end = 0; 522 523 private final OptTestHarnessOutput output; 524 525 Performance(OptTestHarnessOutput output) { 526 this.output = output; 527 } 528 529 void reset() { 530 start = Time.nanoTime(); 531 } 532 533 void stop() { 534 if (end == 0) 535 end = Time.nanoTime(); 536 } 537 538 void show() { 539 stop(); // In case we got here due to a System.exit 540 output.sysOutPrintln(""); 541 output.sysOutPrintln("Performance of executed method"); 542 output.sysOutPrintln("------------------------------"); 543 output.sysOutPrint("Elapsed wallclock time: "); 544 output.sysOutPrint(Double.toString(Time.nanosToMillis(end - start))); 545 output.sysOutPrintln(" msec"); 546 } 547 548 @Override 549 public void notifyExit(int discard) { 550 show(); 551 } 552 } 553 554}