/** Performs peephole optimizations on a program's live methods. */ private static void peephole(final BloatContext context) { final Set liveMethods = new TreeSet(new MemberRefComparator()); final CallGraph cg = context.getCallGraph(); liveMethods.addAll(cg.liveMethods()); // Perform peephole optimizations. We do this separately because // some peephole optimizations do things to the stack that // inlining doesn't like. For instance, a peephole optimizations // might make it so that a method has a non-empty stack upon // return. Inlining will barf at the sight of this. BloatBenchmark.tr("Performing peephole optimizations"); final Iterator iter = liveMethods.iterator(); while (BloatBenchmark.PEEPHOLE && iter.hasNext()) { try { final MethodEditor live = context.editMethod((MemberRef) iter.next()); Peephole.transform(live); context.commit(live.methodInfo()); context.release(live.methodInfo()); } catch (final NoSuchMethodException ex314) { BloatBenchmark.err.println("** Could not find method " + ex314.getMessage()); ex314.printStackTrace(System.err); System.exit(1); } } }
/** * Returns the live methods of a program whose root methods are the <tt>main</tt> method of a set * of classes. * * @param classes Names of classes containing root methods * @param context Repository for accessing BLOAT stuff * @return The <tt>MemberRef</tt>s of the live methods */ private static Collection liveMethods(final Collection classes, final BloatContext context) { // Determine the roots of the call graph final Set roots = new HashSet(); Iterator iter = classes.iterator(); while (iter.hasNext()) { final String className = (String) iter.next(); try { final ClassEditor ce = context.editClass(className); final MethodInfo[] methods = ce.methods(); for (int i = 0; i < methods.length; i++) { final MethodEditor me = context.editMethod(methods[i]); if (!me.name().equals("main")) { continue; } BloatBenchmark.tr(" Root " + ce.name() + "." + me.name() + me.type()); roots.add(me.memberRef()); } } catch (final ClassNotFoundException ex1) { BloatBenchmark.err.println("** Could not find class: " + ex1.getMessage()); System.exit(1); } } if (roots.isEmpty()) { BloatBenchmark.err.print("** No main method found in classes: "); iter = classes.iterator(); while (iter.hasNext()) { final String name = (String) iter.next(); BloatBenchmark.err.print(name); if (iter.hasNext()) { BloatBenchmark.err.print(", "); } } BloatBenchmark.err.println(""); } context.setRootMethods(roots); final CallGraph cg = context.getCallGraph(); final Set liveMethods = new TreeSet(new MemberRefComparator()); liveMethods.addAll(cg.liveMethods()); return (liveMethods); }
/** Specializes the live methods in a program. */ private static void specialize(final BloatContext context) { final CallGraph cg = context.getCallGraph(); final Set liveMethods = new TreeSet(new MemberRefComparator()); liveMethods.addAll(cg.liveMethods()); // Specialize all possible methods final InlineStats stats = context.getInlineStats(); if (BloatBenchmark.statsFile != null) { Specialize.STATS = true; stats.setConfigName("BloatBenchmark"); } if (BloatBenchmark.MORPH != -1) { Specialize.MAX_MORPH = BloatBenchmark.MORPH; } final Specialize spec = new Specialize(context); if (Specialize.STATS) { stats.noteLiveMethods(liveMethods.size()); stats.noteLiveClasses(cg.liveClasses().size()); } BloatBenchmark.tr("Specializing live methods"); final Iterator iter = liveMethods.iterator(); for (int count = 0; iter.hasNext(); count++) { try { final MethodEditor live = context.editMethod((MemberRef) iter.next()); if (context.ignoreMethod(live.memberRef())) { // Don't display ignored methods, it's misleading. continue; } BloatBenchmark.tr( " " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type()); spec.specialize(live); } catch (final NoSuchMethodException ex2) { BloatBenchmark.err.println("** Could not find method " + ex2.getMessage()); System.exit(1); } } }
public static void dumpcode(final MethodEditor m) { final PrintWriter out = new PrintWriter(System.out, true); final StackHeightCounter shc = new StackHeightCounter(m); out.println("Code for method " + m.name() + m.type()); final List instructions = m.code(); final ListIterator iter = instructions.listIterator(); while (iter.hasNext()) { final Object obj = iter.next(); if (obj instanceof Label) { shc.handle((Label) obj); } else if (obj instanceof Instruction) { shc.handle((Instruction) obj); } System.out.println(" " + obj + " (sh: " + shc.height() + ")"); } }
/** Inlines calls to static methods in the live methods of a given program. */ private static void inline(final BloatContext context) { final Set liveMethods = new TreeSet(new MemberRefComparator()); final CallGraph cg = context.getCallGraph(); liveMethods.addAll(cg.liveMethods()); BloatBenchmark.tr("Inlining " + liveMethods.size() + " live methods"); if (BloatBenchmark.CALLEE_SIZE != -1) { Inline.CALLEE_SIZE = BloatBenchmark.CALLEE_SIZE; } final Iterator iter = liveMethods.iterator(); for (int count = 0; BloatBenchmark.INLINE && iter.hasNext(); count++) { try { final MethodEditor live = context.editMethod((MemberRef) iter.next()); if (context.ignoreMethod(live.memberRef())) { // Don't display ignored methods, it's misleading. continue; } BloatBenchmark.tr( " " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type()); final Inline inline = new Inline(context, BloatBenchmark.SIZE); inline.setMaxCallDepth(BloatBenchmark.DEPTH); inline.inline(live); // Commit here in an attempt to conserve memory context.commit(live.methodInfo()); context.release(live.methodInfo()); } catch (final NoSuchMethodException ex3) { BloatBenchmark.err.println("** Could not find method " + ex3.getMessage()); System.exit(1); } } }
/** Runs BLOAT on a method. */ public static void bloatMethod(final MethodEditor m, final BloatContext context) { try { if (Main.COMPACT_ARRAY_INIT) { // Compact the initialization of arrays of the basic types by // putting the values of the array into a string in the constant // pool. The initialization code is replaced with a loop that // loads the array from the string in the constant pool. if (Main.TRACE) { System.out.println(" Compacting Arrays: " + Main.dateFormat.format(new Date())); } CompactArrayInitializer.transform(m); if (Main.DEBUG) { System.out.println("---------- After compaction:"); m.print(System.out); System.out.println("---------- end print"); } } FlowGraph cfg; // The control flow graph for a method if (Main.TRACE) { System.out.println(" Constructing CFG: " + Main.dateFormat.format(new Date())); } try { // Construct the control flow graph for method m cfg = new FlowGraph(m); } catch (final ClassFormatException ex) { System.err.println(ex.getMessage()); context.release(m.methodInfo()); return; } // We separate out initialization since before this the FlowGraph // more exactly represents the input program. cfg.initialize(); if (Main.TRACE) { System.out.println(" Transforming to SSA: " + Main.dateFormat.format(new Date())); } SSA.transform(cfg); if (FlowGraph.DEBUG) { System.out.println("---------- After SSA:"); cfg.print(System.out); System.out.println("---------- end print"); } if (Main.DEBUG) { cfg.visit(new VerifyCFG(false)); } if (!Tree.USE_STACK) { // Do copy propagation and value numbering first to get rid of // all the extra copies inserted for dups. If they're left in, // it really slows down value numbering. if (Main.PROP) { if (Main.DEBUG) { System.out.println("-----Before Copy Propagation-----"); } if (Main.TRACE) { System.out.println(" Copy propagation: " + Main.dateFormat.format(new Date())); } final ExprPropagation copy = new ExprPropagation(cfg); copy.transform(); if (Main.DEBUG) { cfg.visit(new VerifyCFG(false)); } if (Main.DEBUG) { System.out.println("------After Copy Propagation-----"); cfg.print(System.out); } } } DeadCodeElimination dce = null; if (Main.DCE) { if (Main.TRACE) { System.out.println(" Dead Code Elimination: " + Main.dateFormat.format(new Date())); } if (Main.DEBUG) { System.out.println("---Before Dead Code Elimination--"); } dce = new DeadCodeElimination(cfg); dce.transform(); if (Main.DEBUG) { cfg.visit(new VerifyCFG(false)); } if (Main.DEBUG) { System.out.println("---After Dead Code Elimination---"); cfg.print(System.out); } } if (Main.INFER) { if (Main.DEBUG) { System.out.println("---------Doing type inference--------"); } if (Main.TRACE) { System.out.println(" Type Inferencing: " + Main.dateFormat.format(new Date())); } TypeInference.transform(cfg, context.getHierarchy()); } if (Main.NUMBER) { if (Main.TRACE) { System.out.println(" Value Numbering: " + Main.dateFormat.format(new Date())); } if (Main.DEBUG) { System.out.println("--------Doing value numbering--------"); } (new ValueNumbering()).transform(cfg); } if (Main.FOLD) { if (Main.DEBUG) { System.out.println("--------Before Value Folding---------"); } if (Main.TRACE) { System.out.println(" Value Folding: " + Main.dateFormat.format(new Date())); } (new ValueFolding()).transform(cfg); if (Main.DEBUG) { cfg.visit(new VerifyCFG()); } if (Main.DEBUG) { System.out.println("---------After Value Folding---------"); cfg.print(System.out); } } if (Main.PRE) { if (Main.DEBUG) { System.out.println("-------------Before SSAPRE-----------"); } if (Main.TRACE) { System.out.println(" SSAPRE: " + Main.dateFormat.format(new Date())); } final SSAPRE pre = new SSAPRE(cfg, context); pre.transform(); if (Main.DEBUG) { cfg.visit(new VerifyCFG()); } if (Main.DEBUG) { System.out.println("-------------After SSAPRE------------"); cfg.print(System.out); } } if (Main.FOLD) { if (Main.DEBUG) { System.out.println("--------Before Value Folding---------"); } if (Main.TRACE) { System.out.println(" Value Folding: " + Main.dateFormat.format(new Date())); } (new ValueFolding()).transform(cfg); if (Main.DEBUG) { cfg.visit(new VerifyCFG()); } if (Main.DEBUG) { System.out.println("---------After Value Folding---------"); cfg.print(System.out); } } if (Main.PROP) { if (Main.DEBUG) { System.out.println("-------Before Copy Propagation-------"); } if (Main.TRACE) { System.out.println(" Copy Propagation " + Main.dateFormat.format(new Date())); } final ExprPropagation copy = new ExprPropagation(cfg); copy.transform(); if (Main.DEBUG) { cfg.visit(new VerifyCFG()); } if (Main.DEBUG) { System.out.println("--------After Copy Propagation-------"); cfg.print(System.out); } } // make sure we've done at least one thing since the last DCE if (Main.DCE && (Main.INFER || Main.NUMBER || Main.FOLD || Main.PRE || Main.PROP)) { if (Main.DEBUG) { System.out.println("-----Before Dead Code Elimination----"); } if (Main.TRACE) { System.out.println(" Dead Code Elimination: " + Main.dateFormat.format(new Date())); } dce = new DeadCodeElimination(cfg); dce.transform(); if (Main.DEBUG) { cfg.visit(new VerifyCFG()); } if (Main.DEBUG) { System.out.println("-----After Dead Code Elimination-----"); cfg.print(System.out); } } if (Main.PERSIST) { (new PersistentCheckElimination()).transform(cfg); } if (Main.DIVA) { if (Main.DEBUG) { System.out.println("-----Before DIVA------"); } if (Main.TRACE) { System.out.println(" DIVA: " + Main.dateFormat.format(new Date())); } (new InductionVarAnalyzer()).transform(cfg); if (Main.DEBUG) { System.out.println("-----After DIVA-----"); cfg.print(System.out); } } /* * if (STACK_ALLOC) { if (DEBUG) { * System.out.println("------------Before StackPRE----------"); } * * StackPRE pre = new StackPRE(cfg); pre.transform(); * * if (DEBUG) { cfg.visit(new VerifyCFG()); } * * if (DEBUG) { System.out.println("------------After * StackPRE-----------"); cfg.print(System.out); } } */ // Do the new stack optimization if (Main.OPT_STACK_2) { if (Main.TRACE) { System.out.println(" New stack optimization: " + Main.dateFormat.format(new Date())); } // generate code without doing liveness or register allocation final CodeGenerator codegen = new CodeGenerator(m); codegen.replacePhis(cfg); m.clearCode2(); cfg.visit(codegen); // do stack optimization on the bytecode final StackOpt so = new StackOpt(); so.transform(m); // convert it back to a cfg cfg = new FlowGraph(m); cfg.initialize(); // convert it back to SSA SSA.transform(cfg); // do more dead code elimination (eliminate stores) dce = new DeadCodeElimination(cfg); dce.transform(); } if (Main.TRACE) { System.out.println(" Register allocation: " + Main.dateFormat.format(new Date())); } if (Main.VERIFY) { try { cfg.visit(new VerifyCFG()); } catch (final IllegalArgumentException ee) { System.out.println( " NOTE: CFG did not verify while " + "bloating " + m.name() + " after all optimizations. Exception: " + ee); } } // We're all done performing optimizations. Let's generate some code // and go home. // Perform liveness analysis of variables in the method. // Assign local variables ("registers") to expression values. final Liveness liveness = new Liveness(cfg); final RegisterAllocator alloc = new RegisterAllocator(cfg, liveness); // Gather information which can be used to optimize use of the stack if (CodeGenerator.OPT_STACK) { if (Main.TRACE) { System.out.println(" Old stack optimization: " + Main.dateFormat.format(new Date())); } StackOptimizer.optimizeCFG(cfg); } if (Main.TRACE) { System.out.println(" Code Generation: " + Main.dateFormat.format(new Date())); } // Start the code generation process. final CodeGenerator codegen = new CodeGenerator(m); codegen.replacePhis(cfg); if (Main.DEBUG) { System.out.println("After fixing Phis------------------------"); cfg.print(System.out); System.out.println("End print--------------------------------"); } codegen.simplifyControlFlow(cfg); codegen.allocReturnAddresses(cfg, alloc); if (Main.DEBUG) { System.out.println("After removing empty blocks--------------"); cfg.print(System.out); System.out.println("End print--------------------------------"); } // Clear the old contents of the bytecode store and generate new // code. // Code is generated using a visitor pattern on the CFG. m.clearCode(); cfg.visit(codegen); Peephole.transform(m); // Commit any changes that have been made to the method context.commit(m.methodInfo()); } catch (final Exception ex99) { final String msg = "** Exception while optimizing " + m.name() + m.type() + " of class " + m.declaringClass().name(); System.err.println(msg); System.err.println(ex99.getMessage()); ex99.printStackTrace(System.err); System.exit(1); } }
/** * Performs the actual editing of a class. Does a whole mess of stuff including reading in the * classfile, building data structures to represent the class file, converting the CFG for each * method in the class into SSA form, perform some anlayses and optimizations on the method, and * finally committing it back to the class file. Phew. */ private static void editClass(final String className) { ClassFile classFile; // Holds info about a class (implements // ClassInfo) // Get information about the class className try { classFile = (ClassFile) Main.context.loadClass(className); } catch (final ClassNotFoundException ex) { System.err.println("** Couldn't find class: " + ex.getMessage()); return; } if (!Main.FORCE) { // Check to see if the file is up-to-date (i.e. has been // recompiled since it was last optimized). If so, do nothing // because the FORCE flag is false. final File source = classFile.file(); final File target = classFile.outputFile(); if ((source != null) && (target != null) && source.exists() && target.exists() && (source.lastModified() < target.lastModified())) { if (Main.VERBOSE) { System.out.println(classFile.name() + " is up to date"); } return; } } if (Main.DEBUG) { // Print the contents of the class file to System.out classFile.print(System.out); } final ClassEditor c = Main.context.editClass(classFile); boolean skip = false; final String name = c.type().className(); final String qual = c.type().qualifier() + "/*"; // Edit only classes explicitly mentioned. if (Main.ONLY.size() > 0) { skip = true; // Only edit classes we explicitly don't name. for (int i = 0; i < Main.ONLY.size(); i++) { final String pkg = (String) Main.ONLY.get(i); if (name.equals(pkg) || qual.equals(pkg)) { skip = false; break; } } } // Don't edit classes we explicitly skip. if (!skip) { for (int i = 0; i < Main.SKIP.size(); i++) { final String pkg = (String) Main.SKIP.get(i); if (name.equals(pkg) || qual.equals(pkg)) { skip = true; break; } } } if (skip) { if (Main.VERBOSE) { System.out.println("Skipping " + c.type().className()); } // We're done with this class file, decrement its reference count Main.context.release(classFile); return; } // Touch the output file first. That is, create the file, but make // it empty, just to make sure we can create it. try { final File f = classFile.outputFile(); if (f.exists()) { f.delete(); } final File dir = new File(f.getParent()); dir.mkdirs(); if (!dir.exists()) { throw new RuntimeException("Couldn't create directory: " + dir); } final DataOutputStream out = new DataOutputStream(new FileOutputStream(f)); new PrintStream(out).println(); out.close(); } catch (final IOException e) { e.printStackTrace(); System.exit(1); } if (Main.VERBOSE) { System.out.println("Optimizing " + c.type().className()); } // Finally, we can start playing with the methods... final MethodInfo[] methods = c.methods(); final int numMethods = methods.length + 1; ; int whichMethod = 0; for (int j = 0; j < methods.length; j++) { final MethodEditor m; try { m = Main.context.editMethod(methods[j]); } catch (final ClassFormatException ex) { System.err.println(ex.getMessage()); continue; } if (Main.TRACE) { whichMethod++; System.out.println( "Optimizing " + name + "." + m.name() + " (method " + whichMethod + " of " + numMethods + ")"); } if (Main.METHOD != null) { // A method name has been specified on the command line using // -only-method. boolean pass = true; String t = m.name() + m.type(); if (t.equals(Main.METHOD)) { pass = false; } t = m.name(); if (t.equals(Main.METHOD)) { pass = false; } if (pass) { // This isn't the method we're looking for. // Decrement its reference count. Main.context.release(methods[j]); continue; } } if (Main.DEBUG) { m.print(System.out); } if (m.isNative() || m.isAbstract()) { // We can't edit native or abstract methods Main.context.release(methods[j]); continue; } Main.bloatMethod(m, Main.context); } if (Main.ANNO) { String s = "Optimized with: EDU.purdue.cs.bloat.optimize.Main"; for (int i = 0; i < Main.ARGS.length; i++) { if ((Main.ARGS[i].indexOf(' ') >= 0) || (Main.ARGS[i].indexOf('\t') >= 0) || (Main.ARGS[i].indexOf('\r') >= 0) || (Main.ARGS[i].indexOf('\n') >= 0)) { s += " '" + Main.ARGS[i] + "'"; } else { s += " " + Main.ARGS[i]; } } System.out.println(s); // c.constants().addConstant(Constant.UTF8, s); } Main.context.commit(classFile); Main.context.release(classFile); if (Main.TRACE) { System.out.println(Main.context.toString()); } }
/** * Performs intraprocedural BLOAT on a program's live methods. * * @param liveMethods Should be alphabetized. This way we can commit a class once we've BLOATed * all of its methods. */ private static void intraBloat(final Collection liveMethods, final BloatContext context) { ClassEditor prevClass = null; final Iterator iter = liveMethods.iterator(); for (int count = 0; iter.hasNext(); count++) { MethodEditor live = null; ClassEditor ce = null; // Hack to make sure commit happens try { live = context.editMethod((MemberRef) iter.next()); ce = context.editClass(live.declaringClass().classInfo()); } catch (final NoSuchMethodException ex3) { BloatBenchmark.err.println("** Could not find method " + ex3.getMessage()); System.exit(1); } /* So we can skip classes or packages */ final String name = ce.type().className(); final String qual = ce.type().qualifier() + "/*"; boolean skip = false; for (int i = 0; i < BloatBenchmark.SKIP.size(); i++) { final String pkg = (String) BloatBenchmark.SKIP.get(i); if (name.equals(pkg) || qual.equals(pkg)) { skip = true; break; } } if (context.ignoreMethod(live.memberRef()) || skip) { // Don't display ignored methods, it's misleading. context.release(live.methodInfo()); continue; } final Runtime runtime = Runtime.getRuntime(); runtime.gc(); final Date start = new Date(); BloatBenchmark.tr( " " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type()); BloatBenchmark.tr(" Start: " + start); try { EDU.purdue.cs.bloat.optimize.Main.TRACE = BloatBenchmark.TRACE; if (!BloatBenchmark.VERIFY) { EDU.purdue.cs.bloat.optimize.Main.VERIFY = false; } EDU.purdue.cs.bloat.optimize.Main.bloatMethod(live, context); } catch (final Exception oops) { BloatBenchmark.err.println("******************************************"); BloatBenchmark.err.println( "Exception while BLOATing " + live.declaringClass().name() + "." + live.name() + live.type()); BloatBenchmark.err.println(oops.getMessage()); oops.printStackTrace(System.err); BloatBenchmark.err.println("******************************************"); } // Commit here in an attempt to conserve memory context.commit(live.methodInfo()); context.release(live.methodInfo()); if (prevClass == null) { prevClass = ce; } else if (!prevClass.equals(ce)) { // We've finished BLOATed the methods for prevClass, commit // prevClass and move on BloatBenchmark.tr(prevClass.type() + " != " + ce.type()); context.commit(prevClass.classInfo()); context.release(prevClass.classInfo()); // context.commitDirty(); // tr(context.toString()); prevClass = ce; } else { context.release(ce.classInfo()); } final Date end = new Date(); BloatBenchmark.tr(" Ellapsed time: " + (end.getTime() - start.getTime()) + " ms"); } context.commitDirty(); }