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:&lt;cmd&gt;
053 * can be given to the optimizing compiler by org.jikesrvm.tools.oth.OptTestHarness via -oc:&lt;cmd&gt;.
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 &lt;filename&gt;    Read commands (one per line) from a file
058 * +baseline                      Switch default compiler to baseline
059 * -baseline                      Switch default compiler to optimizing
060 * -load  &lt;class    &gt;             Load a class
061 * -class &lt;class&gt;                 Load a class and compile all its methods
062 * -method &lt;class&gt; &lt;method&gt; [-|&lt;descrip&gt;] Compile method with default compiler
063 * -methodOpt &lt;class&gt; &lt;method&gt; [-|&lt;descrip&gt;] Compile method with opt compiler
064 * -methodBase &lt;class&gt; &lt;method&gt; [-|&lt;descrip&gt;] Compile method with base compiler
065 * -er &lt;class&gt; &lt;method&gt; [-|&lt;descrip&gt;] {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}