/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package EDU.purdue.cs.bloat.inline; import java.util.*; import EDU.purdue.cs.bloat.editor.*; import EDU.purdue.cs.bloat.util.*; /** * Performs call site specialization for virtual method invocations. At each * virtual method invocation, the call graph is consulted to determine which * methods the call site could resolve to. The virtual call site is converted * into a "switch" on the method receiver. Each "case" corresponds to a possible * type that the receiver may take on. Inside the "case" branch, the receiver is * cast to the expected type. Finally, a static version of the virtual method is * called. In the "default" case, the virtual method is invoked. */ public class Specialize implements Opcode { public static boolean DEBUG = false; public static boolean DB_STATIC = false; public static boolean PRINTCODE = false; public static boolean STATS = false; public static boolean NOSTATIC = false; public static boolean USE_PREEXISTS = true; private final Map staticMethods = new HashMap(); private final Set specialized = new HashSet(); // calls that have // specialized private InlineStats stats; /** * Maximum number of specializations per call site */ public static int MAX_MORPH = 5; /** * Do we specialize monomorphic call sites? By default, <tt>false</tt>. */ public static boolean MONO = false; private InlineContext context; private static void db(final String s) { if (Specialize.DEBUG) { System.out.println(s); } } /** * Constructor. */ public Specialize(final InlineContext context) { this.context = context; stats = context.getInlineStats(); } /** * Examines the virtual method call sites in a method and specializes them * based on the resolves-to information found in the call graph. * * @return <tt>true</tt>, if the method was modified (that is, it * requires committing) */ public boolean specialize(final MethodEditor method) { if (method.isNative()) { // Can't do anything with native methods return (false); } // Do we ignore this method if (context.ignoreMethod(method.memberRef())) { return (false); } Specialize.db("\nSpecializing " + method.declaringClass().name() + "." + method.name() + method.type()); // Okay, what are we doing here? The first task is to find // virtual call sites. That's easy. Finding the code that pushes // the call's arguments is not so easy. We use the following // method. At each instruction we use a InstructionStack to keep // track of what the stack looks like. That is, we want to know // which instructions push which stack elements. Thus, at a call // site we can easily determine which instruction(s) (yes, there // might be more than one) push the receiver object on the stack. // We insert a dup and a store into a local variable after these // instructions. Back at the call site we load from the local // variable and specialize away. This method only adds a couple // of instructions and can handle all sort of bizzare control // flow. final CallGraph cg = context.getCallGraph(); boolean changed = false; final InstructionStack stack = new InstructionStack(method); // Go through the code looking for call sites. Along the way keep // track of the stack. final List code = method.code(); for (int i = 0; i < code.size(); i++) { final Object o = code.get(i); if (o instanceof Instruction) { final Instruction inst = (Instruction) o; if ((inst.opcodeClass() == Opcode.opcx_invokevirtual) || (inst.opcodeClass() == Opcode.opcx_invokeinterface)) { // Found a virtual method call Specialize.db(" Call: " + inst); final MemberRef callee = (MemberRef) inst.operand(); // Do we ignore the callee? if (context.ignoreMethod(callee)) { Specialize.db(" Explicitly ignoring this method"); stack.handle(inst); continue; } MethodEditor me = null; try { me = context.editMethod(callee); } catch (final NoSuchMethodException ex0) { System.err.println("** Cannot find method " + callee); ex0.printStackTrace(System.err); System.exit(1); } if (me.isFinal() && !me.isNative()) { // Ah, we have a final method. Just convert it to a // static method. Specialize.db(" Converting final method " + callee + " to static"); final int index = code.indexOf(inst); final Instruction newInst = new Instruction( Opcode.opcx_invokespecial, me.memberRef()); code.add(index, newInst); code.remove(inst); stack.handle(newInst); // Make note of call continue; } // Calculate the number of stack slots needed to hold the // callee's arguments. final int nArgs = callee.nameAndType().type().paramTypes().length; // Determine the instructions that push the receiver on the // stack. Make note of these. final Set pushes = stack.atDepth(nArgs); if (Specialize.DEBUG) { Specialize .db(" Receiver on stack after: " + (stack.preexistsAtDepth(nArgs) != null ? " (preexists)" : "")); final Iterator pushies = pushes.iterator(); while (pushies.hasNext()) { final Instruction push = (Instruction) pushies .next(); Specialize.db(" " + code.indexOf(push) + ") " + push); } } Set set = null; // Method call could resolve to if (Specialize.USE_PREEXISTS) { // If the receiver is not preexistent, then we can't // safely inline the method. Don't bother converting it // into a static method. final Set preexists = stack.preexistsAtDepth(nArgs); if (preexists == null) { stack.handle(inst); if (Specialize.STATS) { stats.noteNoPreexist(); } Specialize.db(" Receiver does not preexist"); continue; } // If the preexists is non-empty, then it contains the // type(s) that we know the receiver can take on. Remove // all others from the set of possible receiver types. // Wicked awesome! if (!preexists.isEmpty()) { set = cg.resolvesTo(callee, preexists); ; if (Specialize.DEBUG) { final StringBuffer sb = new StringBuffer( " retaining: "); final Iterator iter = preexists.iterator(); while (iter.hasNext()) { final Type type = (Type) iter.next(); sb.append(Type.truncatedName(type)); if (iter.hasNext()) { sb.append(", "); } } Specialize.db(sb.toString()); } } } if (set == null) { set = cg.resolvesTo(callee); } if ((set == null) || set.isEmpty() || specialized.contains(inst)) { // What does it mean if a call has no call sites? Well, // it means that no class that implements the method is // created. So, either we have a null pointer exception // waiting to happen or the class is instantiated in // someplace that we don't analyze (e.g. native // methods). Specialize .db(" " + (specialized.contains(inst) ? "Call already handled" : "Resolves to no methods") + ". Ignoring."); // Don't forget to update the stack stack.handle(inst); continue; } specialized.add(inst); if (Specialize.STATS) { // Make note of the number of methods a call site // resolves // to stats.noteMorphicity(set.size()); } if (set.size() > Specialize.MAX_MORPH) { // If we only care about monomorphic calls sites, then // go // home. stack.handle(inst); continue; } // Don't update the stack if the call is specialized. This // will all get taken care of later. // Okay, it's showtime. Originally, I specialized all of // the call sites after I had visited the entire method. // However, this was incorrect because specialized code may // push the receiver on the stack. Bummer. // We're making changes changed = true; // Add a dup and store right after the instructions that // pushes the receiver. final LocalVariable var = method.newLocal(callee .declaringClass()); Specialize.db(" New local: " + var + " " + var.type() + " (from method " + method.name() + method.type() + ", max " + method.maxLocals() + ")"); final Iterator iter = pushes.iterator(); Assert.isTrue(iter.hasNext(), "No instruction pushes receiver for " + inst); Instruction newInst = null; boolean mono = (set.size() == 1); while (!mono && iter.hasNext()) { // Since we've already examined the code that pushes the // receiver, we don't need to worry about keeping track // of // the stack height. Besides, this code doesn't effect // the stack. I hope. // There is no need to dup the receiver object if the // call // is monomorphic. This avoids extra dups being // executed. final Instruction push = (Instruction) iter.next(); final int index = code.indexOf(push); Assert.isTrue(index != -1, push + " not found in code"); newInst = new Instruction(Opcode.opcx_dup); code.add(index + 1, newInst); final Instruction store = new Instruction( Opcode.opcx_astore, var); code.add(index + 2, store); } // We've added instructions before the call. This will // mess up our code index, i. Update accordingly. i = code.indexOf(inst) - 1; // Now specialize the call site. Examine each method the // call could resolve to in an order such that overriding // methods come before overriden methods. We don't need to // keep track of the stack because these instructions will // be iterated over in a minute. I hope. Specialize.db(" Specializing call site..."); Label nextLabel = null; final Label endLabel = method.newLabel(); endLabel.setStartsBlock(true); endLabel.setComment("End Specialization"); int index = code.indexOf(inst); Assert.isTrue(index != -1, "Call " + inst + " not found in code"); index--; // Trust me Assert.isTrue(set != null, "Call to " + callee + " should resolve to something"); final Object[] sortedSites = set.toArray(); if (sortedSites.length == 1) { // If the call site only resolve to one method, then we // don't need to do all of the specialization stuff. // Just // call the static-ized method. Specialize.db(" Monomorphic call site"); final MemberRef resolvesTo = (MemberRef) sortedSites[0]; MethodEditor resolvesToMethod = null; try { resolvesToMethod = this.context .editMethod(resolvesTo); } catch (final NoSuchMethodException ex) { System.err.println("** No such method " + resolvesTo); System.exit(1); } if (resolvesToMethod.isNative()) { // Can't specialize native methods. Oh well. // Remember // that it is possible for an overriding method to // be // native. newInst = new Instruction( Opcode.opcx_invokespecial, resolvesTo); code.add(++index, newInst); } else { // Make a static version of the virtual method being // invoked. Call it. if (Specialize.NOSTATIC) { // For testing the specialization stuff, call // the // virtual method instead of the static method. newInst = new Instruction(inst.opcodeClass(), inst.operand()); specialized.add(newInst); } else { newInst = new Instruction( Opcode.opcx_invokespecial, resolvesToMethod.memberRef()); } code.add(++index, newInst); } // Remove the call to the virtual method code.remove(inst); continue; } // If we get to here we have a polymorphic call site for (int s = 0; (s < sortedSites.length) && (s <= Specialize.MAX_MORPH); s++) { final MemberRef resolvesTo = (MemberRef) sortedSites[s]; // Do we ignore this method? if (context.ignoreMethod(resolvesTo)) { continue; } Specialize.db(" Resolves to " + resolvesTo + ")"); final Type rType = resolvesTo.declaringClass(); // This may be the target of a branch if (nextLabel != null) { nextLabel.setComment("Type " + rType); code.add(++index, nextLabel); } // Push the receiver object newInst = new Instruction(Opcode.opcx_aload, var); code.add(++index, newInst); // Check to see if it is this type newInst = new Instruction(Opcode.opcx_instanceof, rType); code.add(++index, newInst); // If its not, try something else nextLabel = method.newLabel(); nextLabel.setStartsBlock(true); newInst = new Instruction(Opcode.opcx_ifeq, nextLabel); code.add(++index, newInst); // We have to add a label here because all branches must // be followed by a label. If we don't BLOAT will barf // during CFG construction. And that's messy. final Label grumble = method.newLabel(); grumble.setStartsBlock(true); code.add(++index, grumble); // Otherwise, call the static-ified method. We can't // cast // the receiver to the expected type. Oh well, we // weren't // planning on verifying anyway. MethodEditor resolvesToMethod = null; try { resolvesToMethod = this.context .editMethod(resolvesTo); } catch (final NoSuchMethodException ex) { System.err.println("** No such method " + resolvesTo); System.exit(1); } if (resolvesToMethod.isNative()) { // Can't specialize native methods. Oh well. // Remember // that it is possible for an overriding method to // be // native. newInst = new Instruction( Opcode.opcx_invokespecial, resolvesTo); code.add(++index, newInst); } else { // Make a static version of the virtual method being // invoked. Call it. if (Specialize.NOSTATIC) { // For testing the specialization stuff, call // the // virtual method instead of the static method. newInst = new Instruction(inst.opcodeClass(), inst.operand()); specialized.add(newInst); } else { newInst = new Instruction( Opcode.opcx_invokespecial, resolvesToMethod.memberRef()); } code.add(++index, newInst); } // Jump to the end newInst = new Instruction(Opcode.opcx_goto, endLabel); code.add(++index, newInst); } // Default code still invokes virtual method if (nextLabel != null) { nextLabel.setComment("Default invocation"); code.add(++index, nextLabel); } // Call virtual method. Should be next in line. index++; // Jump to end. newInst = new Instruction(Opcode.opcx_goto, endLabel); code.add(++index, newInst); // We're all done code.add(++index, endLabel); if (Specialize.DEBUG) { // Print code up to and including end label System.out.println(" Code after specializing " + callee.name() + callee.type()); for (int j = 0; (j <= (index + 2)) && (j < code.size()); j++) { final Object q = code.get(j); if (q instanceof Label) { final Label label = (Label) q; System.out .println(" " + j + ") " + label + (label.startsBlock() ? " (starts block)" : "")); } else { System.out.println(" " + j + ") " + q); } } } // Don't print the stack at the call instruction continue; } else if (inst.opcodeClass() == Opcode.opcx_invokespecial) { // To make our lives easier, convert some invokespecial to // invoke static. Basically, if the callee is non-native // and is a method of the caller's class or one of its // superclasses, we can convert it. final MemberRef callee = (MemberRef) inst.operand(); MethodEditor me = null; try { me = context.editMethod(callee); if (me.isNative() || me.isSynchronized() || me.isConstructor()) { // Forget about these guys stack.handle(inst); } else { final Type calleeType = callee.declaringClass(); final Type callerType = method.declaringClass() .type(); final ClassHierarchy hier = context.getHierarchy(); if (calleeType.equals(callerType) || hier.subclassOf(callerType, calleeType)) { Specialize.db(" Making special " + inst + " static"); final int index = code.indexOf(inst); final Instruction newInst = new Instruction( Opcode.opcx_invokespecial, me .memberRef()); code.add(index, newInst); code.remove(inst); stack.handle(newInst); } else { // Don't forget about the stack stack.handle(inst); } } } catch (final NoSuchMethodException ex2) { System.err.println("** Couldn't find method " + callee); ex2.printStackTrace(System.err); System.exit(1); } } else { // Just update stack stack.handle(inst); } if (Specialize.DEBUG) { Specialize.db(" " + code.indexOf(inst) + ") " + inst); for (int q = 0; q < stack.height(); q++) { final Iterator iter = stack.atDepth(q).iterator(); if (iter.hasNext()) { System.out.print(" "); } while (iter.hasNext()) { System.out.print(code.indexOf(iter.next())); if (iter.hasNext()) { System.out.print(", "); } } if (stack.preexistsAtDepth(q) != null) { System.out.print(" (preexists)"); } else { System.out.print(" (does not preexist)"); } System.out.println(""); } } } else if (o instanceof Label) { // We've reached a label. final Label label = (Label) o; stack.handle(label); Specialize.db(" " + o); if (Specialize.DEBUG) { for (int q = 0; q < stack.height(); q++) { final Iterator iter = stack.atDepth(q).iterator(); if (iter.hasNext()) { System.out.print(" "); } while (iter.hasNext()) { System.out.print(code.indexOf(iter.next())); if (iter.hasNext()) { System.out.print(", "); } } System.out.println(""); } } } else { Assert.isTrue(false, "What is " + o + " doing in the instruction stream?"); } } if (Specialize.DEBUG || Specialize.PRINTCODE) { // Print out the code System.out.println("Specialized code for " + method.declaringClass().name() + "." + method.name() + method.type()); final Iterator iter2 = code.iterator(); while (iter2.hasNext()) { final Object o = iter2.next(); if (o instanceof Label) { System.out.println(""); } System.out.println(" " + o); } } if (changed) { method.setDirty(true); } return (changed); } }