/* 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.trans; import java.util.*; import EDU.purdue.cs.bloat.editor.*; import EDU.purdue.cs.bloat.util.*; /** * Performs some peephole optimizations such as loads and stores and removes * unreachable instructions. */ public class Peephole implements Opcode { public static boolean DEBUG = false; /** * Perform peephole optimizations on the bytecodes in a method. Peephole * optimizations look at two consecutive instructions and try to perform * some simple optimization. For instance, a push followed by a pop is * uninteresting, so both of those instructions can be removed. * * <p> * * After the peephole optimizations are performed a final phase removes * instructions that are not reachable. These instructions reside in basic * blocks whose starting label is never jumped to. */ public static void transform(final MethodEditor method) { if (Peephole.DEBUG) { System.out.println("Peephole optimizing " + method); } // Map between labels and the instruction that they label final Map targets = new HashMap(); final LinkedList jumps = new LinkedList(); // Jump instructions Instruction next = null; Instruction nextInst = null; final List code = method.code(); // Go backwards so we can eliminate redundant loads and stores // in one pass. During the pass collect the locations of labels. CODE: for (int i = code.size() - 1; i >= 0; i--) { final Object ce = code.get(i); if (ce instanceof Label) { if (nextInst != null) { targets.put(ce, nextInst); } next = null; } else if (ce instanceof Instruction) { final Instruction inst = (Instruction) ce; Filter peep = null; // Have we seen a label that starts a block? (i.e. is a target) boolean seenLabel = false; if (inst.isGoto()) { // Look at the instructions following the goto. If an // instruction follows and no label (that starts a block) // has been seen, the instruction is dead and can be // removed. If the target of the goto follows the goto // instruction, the goto is useless and is removed. final Label target = (Label) inst.operand(); for (int j = i + 1; j < code.size(); j++) { final Object t = code.get(j); // Replace // goto L // L: inst // with // L: inst // if (t instanceof Label) { if (((Label) t).startsBlock()) { seenLabel = true; } if (target.equals(t)) { code.remove(i); next = null; nextInst = null; continue CODE; } continue; } // Replace // goto L // this is unreachable // M: inst (M is a different label from L!) // with // goto L // M: inst // if (t instanceof Instruction) { if (seenLabel) { break; } code.remove(j); j--; } } } if (inst.isGoto() || inst.isSwitch()) { jumps.add(inst); } // Performs some peephole optimizations using the filter // method that returns an instance of the Filter class. The // filter method looks at two consecutive instructions and // determines whether or not something about them can be // changed. For instance, if a push is followed by a pop, // both instructions are useless and can be eliminated. The // contents of the Filter object represents the effects of the // peephole optimization. if (next != null) { peep = Peephole.filter(inst, next); } if (peep != null) { if (ClassEditor.DEBUG) { if (peep.replace.length == 0) { System.out.println("eliminate " + code.get(i) + "-" + code.get(i + 1)); } else { System.out.println("replace " + code.get(i) + "-" + code.get(i + 1)); System.out.println(" with"); for (int j = 0; j < peep.replace.length; j++) { System.out.println(" " + peep.replace[j]); } } } // Remove old instructions code.remove(i + 1); code.remove(i); // Add new instructions resulting from peephole // optimizations for (int j = peep.replace.length - 1; j >= 0; j--) { code.add(i, peep.replace[j]); } if ((i < code.size()) && (code.get(i) instanceof Instruction)) { next = (Instruction) code.get(i); } else { // No more instructions, or next thing is a label next = null; } } else { // Filter didn't find any peephole optimizations, skip to // next pair of instructions. next = inst; } nextInst = next; } } // Replace the target of jumps to gotos with the goto target. // Replace gotos to unconditional jumps with the unconditional jump. while (!jumps.isEmpty()) { final Instruction inst = (Instruction) jumps.removeFirst(); Instruction target; if (inst.isGoto()) { target = (Instruction) targets.get(inst.operand()); if (target != null) { if (target.isGoto() && !target.operand().equals(inst.operand())) { if (ClassEditor.DEBUG) { System.out.println("replace " + inst); } inst.setOperand(target.operand()); if (ClassEditor.DEBUG) { System.out.println(" with " + inst); } jumps.add(inst); } else if (target.isSwitch() || target.isReturn() || target.isThrow()) { if (ClassEditor.DEBUG) { System.out.println("replace " + inst); } inst.setOpcodeClass(target.opcodeClass()); inst.setOperand(target.operand()); if (ClassEditor.DEBUG) { System.out.println(" with " + inst); } } } } } // Remove unreachable code. Peephole.removeUnreachable(method, code); if (ClassEditor.DEBUG) { System.out.println("END PEEPHOLE---------------------------------"); } } /** * Iterate over the code in the method and determine which labels begin * blocks that are reachable. The code in blocks that are not reachable is * removed. */ // TODO: Currently, ALL ret targets are marked reachable from a // single ret. Correct this by looking at the local variables. private static void removeUnreachable(final MethodEditor method, final List code) { // Maps Labels to their instruction position final Map labelPos = new HashMap(); // Collect all the ret targets. Iterator iter = code.iterator(); int i = 0; while (iter.hasNext()) { final Object ce = iter.next(); if (ce instanceof Label) { labelPos.put(ce, new Integer(i)); } i++; } // Visit the blocks depth-first. // Stack of Labels that begin blocks that have been visited final Set visited = new HashSet(); // Stack of Labels that begin blocks that have not been visited final Stack stack = new Stack(); Label label; // Current label if (code.size() > 0) { // Start with the label of the first block label = (Label) code.get(0); visited.add(label); stack.push(label); } final Iterator e = method.tryCatches().iterator(); while (e.hasNext()) { // All exception handlers are considered to be live final TryCatch tc = (TryCatch) e.next(); visited.add(tc.handler()); stack.push(tc.handler()); } while (!stack.isEmpty()) { label = (Label) stack.pop(); final Integer labelIndex = (Integer) labelPos.get(label); Assert.isTrue(labelIndex != null, "Index of " + label + " not found"); i = labelIndex.intValue(); final ListIterator blockIter = code.listIterator(i + 1); while (blockIter.hasNext()) { // Iterate over the code in the block. If we encounter // instructions that change execution (i.e. go to another // block), add the Label of the target of the jump to the // stack if it is not already present. final Object ce = blockIter.next(); i++; if (ce instanceof Instruction) { final Instruction inst = (Instruction) ce; if (inst.isReturn() || inst.isThrow()) { // We've reached the end of the block, but we don't know // which block will be executed next. break; } else if (inst.isConditionalJump() || inst.isJsr()) { // We've reached the end of the block, add the Label of // the next block to be executed to the list. It's a // conditional jump, so don't break. The rest of the // code // in the block is not necessarily dead. label = (Label) inst.operand(); if (!visited.contains(label)) { visited.add(label); stack.push(label); } // Fall through. } else if (inst.isGoto()) { // Add next block to work list. label = (Label) inst.operand(); if (!visited.contains(label)) { visited.add(label); stack.push(label); } break; } else if (inst.isRet()) { // The ret targets were handled by the jsr. break; } else if (inst.isSwitch()) { // A switch. Add all possible targets of the switch to // the worklist. final Switch sw = (Switch) inst.operand(); label = sw.defaultTarget(); if (!visited.contains(label)) { visited.add(label); stack.push(label); } final Label[] targets = sw.targets(); for (int j = 0; j < targets.length; j++) { label = targets[j]; if (!visited.contains(label)) { visited.add(label); stack.push(label); } } break; } } else if (ce instanceof Label) { label = (Label) ce; visited.add(label); } } } boolean reachable = false; iter = code.iterator(); // Remove unreachable instructions while (iter.hasNext()) { final Object ce = iter.next(); if (ce instanceof Label) { reachable = visited.contains(ce); // Don't remove unreachable labels, only instructions. } else if (!reachable) { if (ClassEditor.DEBUG) { System.out.println("Removing unreachable " + ce); } iter.remove(); } } } /** * Filter represents a set of instructions that result from a peephole * optimizations. For instance, when uninteresting instructions are removed, * a Filter object with an empty "replace" array will be returned by the * below filter method. */ static class Filter { Instruction[] replace; Filter() { this.replace = new Instruction[0]; } Filter(final Instruction replace) { this.replace = new Instruction[] { replace }; } Filter(final Instruction replace1, final Instruction replace2) { this.replace = new Instruction[] { replace1, replace2 }; } } /** * Filter a pair of instructions. That is, do a peephole optimization on two * consecutive instructions. For instance, if a push is followed by a pop, * both instructions can be eliminated. The <tt>Filter</tt> object that is * returned specifies what instruction(s), if any, should replace the two * instructions that are the parameters to this method. * * @param first * The first instruction. * @param second * The second instruction. * @return A list of instructions to replace the two instructions with, or * null, if the instructions should be left as is. */ private static Filter filter(final Instruction first, final Instruction second) { switch (second.opcodeClass()) { // swap means nothing if it's after a dup. // (goodbye means nothing when it's all for show // so stop pretending you've somewhere else to go.) case opcx_swap: // Elminate swap-swap if (first.opcodeClass() == Opcode.opcx_swap) { return new Filter(); } // swap means nothing if it's after a dup. // (goodbye means nothing when it's all for show // so stop pretending you've somewhere else to go.) if (first.opcodeClass() == Opcode.opcx_dup) { return new Filter(first); } break; // Eliminate push-pop. case opcx_pop: // Eliminate push-pop. switch (first.opcodeClass()) { case opcx_ldc: // Make sure things being popped off is not wide (we're // dealing with a pop not a pop2). Assert.isTrue(!(first.operand() instanceof Long) && !(first.operand() instanceof Double), "Cannot pop a 2-word operand"); // Fall through. case opcx_iload: case opcx_fload: case opcx_aload: case opcx_dup: // Eliminate the load and the pop. return new Filter(); case opcx_dup_x1: // Replace dup_x1-pop with swap // (As if this is really likely to happen ;) <-- Nate made a // joke! return new Filter(new Instruction(Opcode.opcx_swap)); } break; case opcx_pop2: switch (first.opcodeClass()) { case opcx_ldc: Assert.isTrue((first.operand() instanceof Long) || (first.operand() instanceof Double), "Cannot pop2 a 1-word operand"); // Fall through. case opcx_lload: case opcx_dload: case opcx_dup2: // Eliminate push and pop return new Filter(); } break; case opcx_istore: // Eliminate load-store to same location. if (first.opcodeClass() == Opcode.opcx_iload) { if (first.operand().equals(second.operand())) { return new Filter(); } } break; case opcx_fstore: if (first.opcodeClass() == Opcode.opcx_fload) { if (first.operand().equals(second.operand())) { return new Filter(); } } break; case opcx_astore: if (first.opcodeClass() == Opcode.opcx_aload) { if (first.operand().equals(second.operand())) { return new Filter(); } } break; case opcx_lstore: if (first.opcodeClass() == Opcode.opcx_lload) { if (first.operand().equals(second.operand())) { return new Filter(); } } break; case opcx_dstore: if (first.opcodeClass() == Opcode.opcx_dload) { if (first.operand().equals(second.operand())) { return new Filter(); } } break; case opcx_ireturn: case opcx_freturn: case opcx_areturn: case opcx_lreturn: case opcx_dreturn: // Replace store-return with return. Remember that upon return // all local variables revert to their pre-call values, so any // stores are destroyed. switch (first.opcodeClass()) { case opcx_istore: case opcx_fstore: case opcx_astore: case opcx_lstore: case opcx_dstore: return new Filter(second); } break; case opcx_iadd: // Replace ineg-iadd with isub if (first.opcodeClass() == Opcode.opcx_ineg) { return new Filter(new Instruction(Opcode.opcx_isub)); } break; case opcx_isub: // Replace ineg-isub with iadd if (first.opcodeClass() == Opcode.opcx_ineg) { return new Filter(new Instruction(Opcode.opcx_iadd)); } break; case opcx_ladd: // Replace lneg-ladd with lsub if (first.opcodeClass() == Opcode.opcx_lneg) { return new Filter(new Instruction(Opcode.opcx_lsub)); } break; case opcx_lsub: // Replace lneg-lsub with ladd if (first.opcodeClass() == Opcode.opcx_lneg) { return new Filter(new Instruction(Opcode.opcx_ladd)); } break; case opcx_if_icmpeq: // Replace ldc 0-if_icmpeq with ifeq if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_ifeq, second.operand())); } } } break; case opcx_if_icmpne: // Replace ldc 0-if_icmpne with ifne if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_ifne, second.operand())); } } } break; case opcx_if_icmplt: // Replace ldc 0-if_icmplt with iflt if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_iflt, second.operand())); } } } break; case opcx_if_icmpge: // Replace ldc 0-if_icmpge with ifge if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_ifge, second.operand())); } } } break; case opcx_if_icmpgt: // Replace ldc 0-if_icmpgt with ifgt if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_ifgt, second.operand())); } } } break; case opcx_if_icmple: // Replace ldc 0-if_icmple with ifle if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_ifle, second.operand())); } } } break; case opcx_if_acmpeq: // Replace ldc null-if_acmpeq with ifnull if (first.opcodeClass() == Opcode.opcx_ldc) { if (first.operand() == null) { return new Filter(new Instruction(Opcode.opcx_ifnull, second.operand())); } } break; case opcx_if_acmpne: // Replace ldc null-if_acmpne with ifnonnull if (first.opcodeClass() == Opcode.opcx_ldc) { if (first.operand() == null) { return new Filter(new Instruction(Opcode.opcx_ifnonnull, second.operand())); } } break; case opcx_ifeq: // Replace ldc 0-ifeq with goto and eliminate ldc !0-ifeq if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() == 0) { return new Filter(new Instruction(Opcode.opcx_goto, second.operand())); } else { return new Filter(); } } } break; case opcx_ifne: // Replace ldc !0-ifne with goto and eliminate ldc 0-ifne if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() != 0) { return new Filter(new Instruction(Opcode.opcx_goto, second.operand())); } else { return new Filter(); } } } break; case opcx_iflt: // Replace ldc <0-iflt with goto and eliminate ldc >=0-iflt if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() < 0) { return new Filter(new Instruction(Opcode.opcx_goto, second.operand())); } else { return new Filter(); } } } break; case opcx_ifge: // Replace ldc >=0-ifge with goto and eliminate ldc <0-ifge if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() >= 0) { return new Filter(new Instruction(Opcode.opcx_goto, second.operand())); } else { return new Filter(); } } } break; case opcx_ifgt: // Replace ldc >0-ifgt with goto and eliminate ldc <=0-ifgt if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() > 0) { return new Filter(new Instruction(Opcode.opcx_goto, second.operand())); } else { return new Filter(); } } } break; case opcx_ifle: // Replace ldc <=0-ifle with goto and eliminate ldc >0-ifle if (first.opcodeClass() == Opcode.opcx_ldc) { final Object op = first.operand(); if (op instanceof Integer) { if (((Integer) op).intValue() <= 0) { return new Filter(new Instruction(Opcode.opcx_goto, second.operand())); } else { return new Filter(); } } } break; } switch (second.opcodeClass()) { // Replace store-store to same location with pop-store. case opcx_istore: case opcx_fstore: case opcx_astore: case opcx_lstore: case opcx_dstore: switch (first.opcodeClass()) { case opcx_istore: case opcx_fstore: case opcx_astore: if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_pop), first); } break; case opcx_lstore: case opcx_dstore: if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_pop2), first); } break; } break; } switch (second.opcodeClass()) { // Replace store-load with dup-store. // Replace load-load with load-dup. case opcx_iload: if (first.opcodeClass() == Opcode.opcx_istore) { if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_dup), first); } } if (first.opcodeClass() == Opcode.opcx_iload) { if (first.operand().equals(second.operand())) { return new Filter(first, new Instruction(Opcode.opcx_dup)); } } break; case opcx_fload: if (first.opcodeClass() == Opcode.opcx_fstore) { if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_dup), first); } } if (first.opcodeClass() == Opcode.opcx_fload) { if (first.operand().equals(second.operand())) { return new Filter(first, new Instruction(Opcode.opcx_dup)); } } break; case opcx_aload: if (first.opcodeClass() == Opcode.opcx_astore) { if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_dup), first); } } if (first.opcodeClass() == Opcode.opcx_aload) { if (first.operand().equals(second.operand())) { return new Filter(first, new Instruction(Opcode.opcx_dup)); } } break; case opcx_lload: if (first.opcodeClass() == Opcode.opcx_lstore) { if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_dup2), first); } } if (first.opcodeClass() == Opcode.opcx_lload) { if (first.operand().equals(second.operand())) { return new Filter(first, new Instruction(Opcode.opcx_dup2)); } } break; case opcx_dload: if (first.opcodeClass() == Opcode.opcx_dstore) { if (first.operand().equals(second.operand())) { return new Filter(new Instruction(Opcode.opcx_dup2), first); } } if (first.opcodeClass() == Opcode.opcx_dload) { if (first.operand().equals(second.operand())) { return new Filter(first, new Instruction(Opcode.opcx_dup2)); } } break; } return null; } }