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