diff options
Diffstat (limited to 'dx/src/com/android/jack/dx/ssa')
32 files changed, 10983 insertions, 0 deletions
diff --git a/dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java b/dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java new file mode 100644 index 00000000..f9bdb2fa --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/BasicRegisterMapper.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.IntList; + +/** + * This class maps one register space into another, with + * each mapping built up individually and added via addMapping() + */ +public class BasicRegisterMapper extends RegisterMapper { + /** indexed by old register, containing new name */ + private IntList oldToNew; + + /** running count of used registers in new namespace */ + private int runningCountNewRegisters; + + /** + * Creates a new OneToOneRegisterMapper. + * + * @param countOldRegisters the number of registers in the old name space + */ + public BasicRegisterMapper(int countOldRegisters) { + oldToNew = new IntList(countOldRegisters); + } + + /** {@inheritDoc} */ + @Override + public int getNewRegisterCount() { + return runningCountNewRegisters; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec == null) { + return null; + } + + int newReg; + try { + newReg = oldToNew.get(registerSpec.getReg()); + } catch (IndexOutOfBoundsException ex) { + newReg = -1; + } + + if (newReg < 0) { + throw new RuntimeException("no mapping specified for register"); + } + + return registerSpec.withReg(newReg); + } + + /** + * Returns the new-namespace mapping for the specified + * old-namespace register, or -1 if one exists. + * + * @param oldReg {@code >= 0;} old-namespace register + * @return new-namespace register or -1 if none + */ + public int oldToNew(int oldReg) { + if (oldReg >= oldToNew.size()) { + return -1; + } + + return oldToNew.get(oldReg); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("Old\tNew\n"); + int sz = oldToNew.size(); + + for (int i = 0; i < sz; i++) { + sb.append(i); + sb.append('\t'); + sb.append(oldToNew.get(i)); + sb.append('\n'); + } + + sb.append("new reg count:"); + + sb.append(runningCountNewRegisters); + sb.append('\n'); + + return sb.toString(); + } + + /** + * Adds a mapping to the mapper. If oldReg has already been mapped, + * overwrites previous mapping with new mapping. + * + * @param oldReg {@code >= 0;} old register + * @param newReg {@code >= 0;} new register + * @param category {@code 1..2;} width of reg + */ + public void addMapping(int oldReg, int newReg, int category) { + if (oldReg >= oldToNew.size()) { + // expand the array as necessary + for (int i = oldReg - oldToNew.size(); i >= 0; i--) { + oldToNew.add(-1); + } + } + + oldToNew.set(oldReg, newReg); + + if (runningCountNewRegisters < (newReg + category)) { + runningCountNewRegisters = newReg + category; + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/ConstCollector.java b/dx/src/com/android/jack/dx/ssa/ConstCollector.java new file mode 100644 index 00000000..ee12cb44 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/ConstCollector.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.code.ThrowingCstInsn; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.TypedConstant; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Collects constants that are used more than once at the top of the + * method block. This increases register usage by about 5% but decreases + * insn size by about 3%. + */ +public class ConstCollector { + /** Maximum constants to collect per method. Puts cap on reg use */ + private static final int MAX_COLLECTED_CONSTANTS = 5; + + /** + * Also collect string consts, although they can throw exceptions. + * This is off now because it just doesn't seem to gain a whole lot. + * TODO if you turn this on, you must change SsaInsn.hasSideEffect() + * to return false for const-string insns whose exceptions are not + * caught in the current method. + */ + private static boolean COLLECT_STRINGS = false; + + /** + * If true, allow one local var to be involved with a collected const. + * Turned off because it mostly just inserts more moves. + */ + private static boolean COLLECT_ONE_LOCAL = false; + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** + * Processes a method. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + ConstCollector cc = new ConstCollector(ssaMethod); + cc.run(); + } + + /** + * Constructs an instance. + * + * @param ssaMethod {@code non-null;} method to process + */ + private ConstCollector(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + } + + /** + * Applies the optimization. + */ + private void run() { + int regSz = ssaMeth.getRegCount(); + + ArrayList<TypedConstant> constantList + = getConstsSortedByCountUse(); + + int toCollect = Math.min(constantList.size(), MAX_COLLECTED_CONSTANTS); + + SsaBasicBlock start = ssaMeth.getEntryBlock(); + + // Constant to new register containing the constant + HashMap<TypedConstant, RegisterSpec> newRegs + = new HashMap<TypedConstant, RegisterSpec> (toCollect); + + for (int i = 0; i < toCollect; i++) { + TypedConstant cst = constantList.get(i); + RegisterSpec result + = RegisterSpec.make(ssaMeth.makeNewSsaReg(), cst); + + Rop constRop = Rops.opConst(cst); + + if (constRop.getBranchingness() == Rop.BRANCH_NONE) { + start.addInsnToHead( + new PlainCstInsn(Rops.opConst(cst), + SourcePosition.NO_INFO, result, + RegisterSpecList.EMPTY, cst)); + } else { + // We need two new basic blocks along with the new insn + SsaBasicBlock entryBlock = ssaMeth.getEntryBlock(); + SsaBasicBlock successorBlock + = entryBlock.getPrimarySuccessor(); + + // Insert a block containing the const insn. + SsaBasicBlock constBlock + = entryBlock.insertNewSuccessor(successorBlock); + + constBlock.replaceLastInsn( + new ThrowingCstInsn(constRop, SourcePosition.NO_INFO, + RegisterSpecList.EMPTY, + StdTypeList.EMPTY, cst)); + + // Insert a block containing the move-result-pseudo insn. + + SsaBasicBlock resultBlock + = constBlock.insertNewSuccessor(successorBlock); + PlainInsn insn + = new PlainInsn( + Rops.opMoveResultPseudo(result.getTypeBearer()), + SourcePosition.NO_INFO, + result, RegisterSpecList.EMPTY); + + resultBlock.addInsnToHead(insn); + } + + newRegs.put(cst, result); + } + + updateConstUses(newRegs, regSz); + } + + /** + * Gets all of the collectable constant values used in this method, + * sorted by most used first. Skips non-collectable consts, such as + * non-string object constants + * + * @return {@code non-null;} list of constants in most-to-least used order + */ + private ArrayList<TypedConstant> getConstsSortedByCountUse() { + int regSz = ssaMeth.getRegCount(); + + final HashMap<TypedConstant, Integer> countUses + = new HashMap<TypedConstant, Integer>(); + + /* + * Each collected constant can be used by just one local + * (used only if COLLECT_ONE_LOCAL is true). + */ + final HashSet<TypedConstant> usedByLocal + = new HashSet<TypedConstant>(); + + // Count how many times each const value is used. + for (int i = 0; i < regSz; i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + if (insn == null || insn.getOpcode() == null) continue; + + RegisterSpec result = insn.getResult(); + TypeBearer typeBearer = result.getTypeBearer(); + + if (!typeBearer.isConstant()) continue; + + TypedConstant cst = (TypedConstant) typeBearer; + + // Find defining instruction for move-result-pseudo instructions + if (insn.getOpcode().getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + int pred = insn.getBlock().getPredecessors().nextSetBit(0); + ArrayList<SsaInsn> predInsns; + predInsns = ssaMeth.getBlocks().get(pred).getInsns(); + insn = predInsns.get(predInsns.size()-1); + } + + if (insn.canThrow()) { + /* + * Don't move anything other than strings -- the risk + * of changing where an exception is thrown is too high. + */ + if (!(cst instanceof CstString) || !COLLECT_STRINGS) { + continue; + } + /* + * We can't move any throwable const whose throw will be + * caught, so don't count them. + */ + if (insn.getBlock().getSuccessors().cardinality() > 1) { + continue; + } + } + + /* + * TODO: Might be nice to try and figure out which local + * wins most when collected. + */ + if (ssaMeth.isRegALocal(result)) { + if (!COLLECT_ONE_LOCAL) { + continue; + } else { + if (usedByLocal.contains(cst)) { + // Count one local usage only. + continue; + } else { + usedByLocal.add(cst); + } + } + } + + Integer has = countUses.get(cst); + if (has == null) { + countUses.put(cst, 1); + } else { + countUses.put(cst, has + 1); + } + } + + // Collect constants that have been reused. + ArrayList<TypedConstant> constantList = new ArrayList<TypedConstant>(); + for (Map.Entry<TypedConstant, Integer> entry : countUses.entrySet()) { + if (entry.getValue() > 1) { + constantList.add(entry.getKey()); + } + } + + // Sort by use, with most used at the beginning of the list. + Collections.sort(constantList, new Comparator<Constant>() { + public int compare(Constant a, Constant b) { + int ret; + ret = countUses.get(b) - countUses.get(a); + + if (ret == 0) { + /* + * Provide sorting determinisim for constants with same + * usage count. + */ + ret = a.compareTo(b); + } + + return ret; + } + + @Override + public boolean equals (Object obj) { + return obj == this; + } + }); + + return constantList; + } + + /** + * Inserts mark-locals if necessary when changing a register. If + * the definition of {@code origReg} is associated with a local + * variable, then insert a mark-local for {@code newReg} just below + * it. We expect the definition of {@code origReg} to ultimately + * be removed by the dead code eliminator + * + * @param origReg {@code non-null;} original register + * @param newReg {@code non-null;} new register that will replace + * {@code origReg} + */ + private void fixLocalAssignment(RegisterSpec origReg, + RegisterSpec newReg) { + for (SsaInsn use : ssaMeth.getUseListForRegister(origReg.getReg())) { + RegisterSpec localAssignment = use.getLocalAssignment(); + if (localAssignment == null) { + continue; + } + + if (use.getResult() == null) { + /* + * This is a mark-local. it will be updated when all uses + * are updated. + */ + continue; + } + + LocalItem local = localAssignment.getLocalItem(); + + // Un-associate original use. + use.setResultLocal(null); + + // Now add a mark-local to the new reg immediately after. + newReg = newReg.withLocalItem(local); + + SsaInsn newInsn + = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMarkLocal(newReg), + SourcePosition.NO_INFO, null, + RegisterSpecList.make(newReg)), + use.getBlock()); + + ArrayList<SsaInsn> insns = use.getBlock().getInsns(); + + insns.add(insns.indexOf(use) + 1, newInsn); + } + } + + /** + * Updates all uses of various consts to use the values in the newly + * assigned registers. + * + * @param newRegs {@code non-null;} mapping between constant and new reg + * @param origRegCount {@code >=0;} original SSA reg count, not including + * newly added constant regs + */ + private void updateConstUses(HashMap<TypedConstant, RegisterSpec> newRegs, + int origRegCount) { + + /* + * set of constants associated with a local variable; used + * only if COLLECT_ONE_LOCAL is true. + */ + final HashSet<TypedConstant> usedByLocal + = new HashSet<TypedConstant>(); + + final ArrayList<SsaInsn>[] useList = ssaMeth.getUseListCopy(); + + for (int i = 0; i < origRegCount; i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + if (insn == null) { + continue; + } + + final RegisterSpec origReg = insn.getResult(); + TypeBearer typeBearer = insn.getResult().getTypeBearer(); + + if (!typeBearer.isConstant()) continue; + + TypedConstant cst = (TypedConstant) typeBearer; + final RegisterSpec newReg = newRegs.get(cst); + + if (newReg == null) { + continue; + } + + if (ssaMeth.isRegALocal(origReg)) { + if (!COLLECT_ONE_LOCAL) { + continue; + } else { + /* + * TODO: If the same local gets the same cst + * multiple times, it would be nice to reuse the + * register. + */ + if (usedByLocal.contains(cst)) { + continue; + } else { + usedByLocal.add(cst); + fixLocalAssignment(origReg, newRegs.get(cst)); + } + } + } + + // maps an original const register to the new collected register + RegisterMapper mapper = new RegisterMapper() { + @Override + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == origReg.getReg()) { + return newReg.withLocalItem( + registerSpec.getLocalItem()); + } + + return registerSpec; + } + }; + + for (SsaInsn use : useList[origReg.getReg()]) { + if (use.canThrow() + && use.getBlock().getSuccessors().cardinality() > 1) { + continue; + } + use.mapSourceRegisters(mapper); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java b/dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java new file mode 100644 index 00000000..70c00054 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/DeadCodeRemover.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * A variation on Appel Algorithm 19.12 "Dead code elimination in SSA form". + * + * TODO this algorithm is more efficient if run in reverse from exit + * block to entry block. + */ +public class DeadCodeRemover { + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** ssaMeth.getRegCount() */ + private final int regCount; + + /** + * indexed by register: whether reg should be examined + * (does it correspond to a no-side-effect insn?) + */ + private final BitSet worklist; + + /** use list indexed by register; modified during operation */ + private final ArrayList<SsaInsn>[] useList; + + /** + * Process a method with the dead-code remver + * + * @param ssaMethod method to process + */ + public static void process(SsaMethod ssaMethod) { + DeadCodeRemover dc = new DeadCodeRemover(ssaMethod); + dc.run(); + } + + /** + * Constructs an instance. + * + * @param ssaMethod method to process + */ + private DeadCodeRemover(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + + regCount = ssaMethod.getRegCount(); + worklist = new BitSet(regCount); + useList = ssaMeth.getUseListCopy(); + } + + /** + * Runs the dead code remover. + */ + private void run() { + pruneDeadInstructions(); + + HashSet<SsaInsn> deletedInsns = new HashSet<SsaInsn>(); + + ssaMeth.forEachInsn(new NoSideEffectVisitor(worklist)); + + int regV; + + while ( 0 <= (regV = worklist.nextSetBit(0)) ) { + worklist.clear(regV); + + if (useList[regV].size() == 0 + || isCircularNoSideEffect(regV, null)) { + + SsaInsn insnS = ssaMeth.getDefinitionForRegister(regV); + + // This insn has already been deleted. + if (deletedInsns.contains(insnS)) { + continue; + } + + RegisterSpecList sources = insnS.getSources(); + + int sz = sources.size(); + for (int i = 0; i < sz; i++) { + // Delete this insn from all usage lists. + RegisterSpec source = sources.get(i); + useList[source.getReg()].remove(insnS); + + if (!hasSideEffect( + ssaMeth.getDefinitionForRegister( + source.getReg()))) { + /* + * Only registers whose definition has no side effect + * should be added back to the worklist. + */ + worklist.set(source.getReg()); + } + } + + // Schedule this insn for later deletion. + deletedInsns.add(insnS); + } + } + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Removes all instructions from every unreachable block. + */ + private void pruneDeadInstructions() { + HashSet<SsaInsn> deletedInsns = new HashSet<SsaInsn>(); + + ssaMeth.computeReachability(); + + for (SsaBasicBlock block : ssaMeth.getBlocks()) { + if (block.isReachable()) continue; + + // Prune instructions from unreachable blocks + for (int i = 0; i < block.getInsns().size(); i++) { + SsaInsn insn = block.getInsns().get(i); + RegisterSpecList sources = insn.getSources(); + int sourcesSize = sources.size(); + + // Delete this instruction completely if it has sources + if (sourcesSize != 0) { + deletedInsns.add(insn); + } + + // Delete this instruction from all usage lists. + for (int j = 0; j < sourcesSize; j++) { + RegisterSpec source = sources.get(j); + useList[source.getReg()].remove(insn); + } + + // Remove this instruction result from the sources of any phis + RegisterSpec result = insn.getResult(); + if (result == null) continue; + for (SsaInsn use : useList[result.getReg()]) { + if (use instanceof PhiInsn) { + PhiInsn phiUse = (PhiInsn) use; + phiUse.removePhiRegister(result); + } + } + } + } + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Returns true if the only uses of this register form a circle of + * operations with no side effects. + * + * @param regV register to examine + * @param set a set of registers that we've already determined + * are only used as sources in operations with no side effect or null + * if this is the first recursion + * @return true if usage is circular without side effect + */ + private boolean isCircularNoSideEffect(int regV, BitSet set) { + if ((set != null) && set.get(regV)) { + return true; + } + + for (SsaInsn use : useList[regV]) { + if (hasSideEffect(use)) { + return false; + } + } + + if (set == null) { + set = new BitSet(regCount); + } + + // This register is only used in operations that have no side effect. + set.set(regV); + + for (SsaInsn use : useList[regV]) { + RegisterSpec result = use.getResult(); + + if (result == null + || !isCircularNoSideEffect(result.getReg(), set)) { + return false; + } + } + + return true; + } + + /** + * Returns true if this insn has a side-effect. Returns true + * if the insn is null for reasons stated in the code block. + * + * @param insn {@code null-ok;} instruction in question + * @return true if it has a side-effect + */ + private static boolean hasSideEffect(SsaInsn insn) { + if (insn == null) { + /* While false would seem to make more sense here, true + * prevents us from adding this back to a worklist unnecessarally. + */ + return true; + } + + return insn.hasSideEffect(); + } + + /** + * A callback class used to build up the initial worklist of + * registers defined by an instruction with no side effect. + */ + static private class NoSideEffectVisitor implements SsaInsn.Visitor { + BitSet noSideEffectRegs; + + /** + * Passes in data structures that will be filled out after + * ssaMeth.forEachInsn() is called with this instance. + * + * @param noSideEffectRegs to-build bitset of regs that are + * results of regs with no side effects + */ + public NoSideEffectVisitor(BitSet noSideEffectRegs) { + this.noSideEffectRegs = noSideEffectRegs; + } + + /** {@inheritDoc} */ + public void visitMoveInsn (NormalSsaInsn insn) { + // If we're tracking local vars, some moves have side effects. + if (!hasSideEffect(insn)) { + noSideEffectRegs.set(insn.getResult().getReg()); + } + } + + /** {@inheritDoc} */ + public void visitPhiInsn (PhiInsn phi) { + // If we're tracking local vars, then some phis have side effects. + if (!hasSideEffect(phi)) { + noSideEffectRegs.set(phi.getResult().getReg()); + } + } + + /** {@inheritDoc} */ + public void visitNonMoveInsn (NormalSsaInsn insn) { + RegisterSpec result = insn.getResult(); + if (!hasSideEffect(insn) && result != null) { + noSideEffectRegs.set(result.getReg()); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/DomFront.java b/dx/src/com/android/jack/dx/ssa/DomFront.java new file mode 100644 index 00000000..9e67f464 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/DomFront.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; +import com.android.jack.dx.util.ListIntSet; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; + +/** + * Calculates the dominance-frontiers of a methot's basic blocks. + * Algorithm from "A Simple, Fast Dominance Algorithm" by Cooper, + * Harvey, and Kennedy; transliterated to Java. + */ +public class DomFront { + /** local debug flag */ + private static boolean DEBUG = false; + + /** {@code non-null;} method being processed */ + private final SsaMethod meth; + + private final ArrayList<SsaBasicBlock> nodes; + + private final DomInfo[] domInfos; + + /** + * Dominance-frontier information for a single basic block. + */ + public static class DomInfo { + /** + * {@code null-ok;} the dominance frontier set indexed by + * block index + */ + public IntSet dominanceFrontiers; + + /** {@code >= 0 after run();} the index of the immediate dominator */ + public int idom = -1; + } + + /** + * Constructs instance. Call {@link DomFront#run} to process. + * + * @param meth {@code non-null;} method to process + */ + public DomFront(SsaMethod meth) { + this.meth = meth; + nodes = meth.getBlocks(); + + int szNodes = nodes.size(); + domInfos = new DomInfo[szNodes]; + + for (int i = 0; i < szNodes; i++) { + domInfos[i] = new DomInfo(); + } + } + + /** + * Calculates the dominance frontier information for the method. + * + * @return {@code non-null;} an array of DomInfo structures + */ + public DomInfo[] run() { + int szNodes = nodes.size(); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + SsaBasicBlock node = nodes.get(i); + System.out.println("pred[" + i + "]: " + + node.getPredecessors()); + } + } + + Dominators methDom = Dominators.make(meth, domInfos, false); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + DomInfo info = domInfos[i]; + System.out.println("idom[" + i + "]: " + + info.idom); + } + } + + buildDomTree(); + + if (DEBUG) { + debugPrintDomChildren(); + } + + for (int i = 0; i < szNodes; i++) { + domInfos[i].dominanceFrontiers + = SetFactory.makeDomFrontSet(szNodes); + } + + calcDomFronts(); + + if (DEBUG) { + for (int i = 0; i < szNodes; i++) { + System.out.println("df[" + i + "]: " + + domInfos[i].dominanceFrontiers); + } + } + + return domInfos; + } + + private void debugPrintDomChildren() { + int szNodes = nodes.size(); + + for (int i = 0; i < szNodes; i++) { + SsaBasicBlock node = nodes.get(i); + StringBuffer sb = new StringBuffer(); + + sb.append('{'); + boolean comma = false; + for (SsaBasicBlock child : node.getDomChildren()) { + if (comma) { + sb.append(','); + } + sb.append(child); + comma = true; + } + sb.append('}'); + + System.out.println("domChildren[" + node + "]: " + + sb); + } + } + + /** + * The dominators algorithm leaves us knowing who the immediate dominator + * is for each node. This sweeps the node list and builds the proper + * dominance tree. + */ + private void buildDomTree() { + int szNodes = nodes.size(); + + for (int i = 0; i < szNodes; i++) { + DomInfo info = domInfos[i]; + + if (info.idom == -1) continue; + + SsaBasicBlock domParent = nodes.get(info.idom); + domParent.addDomChild(nodes.get(i)); + } + } + + /** + * Calculates the dominance-frontier set. + * from "A Simple, Fast Dominance Algorithm" by Cooper, + * Harvey, and Kennedy; transliterated to Java. + */ + private void calcDomFronts() { + int szNodes = nodes.size(); + + for (int b = 0; b < szNodes; b++) { + SsaBasicBlock nb = nodes.get(b); + DomInfo nbInfo = domInfos[b]; + BitSet pred = nb.getPredecessors(); + + if (pred.cardinality() > 1) { + for (int i = pred.nextSetBit(0); i >= 0; + i = pred.nextSetBit(i + 1)) { + + for (int runnerIndex = i; + runnerIndex != nbInfo.idom; /* empty */) { + /* + * We can stop if we hit a block we already + * added label to, since we must be at a part + * of the dom tree we have seen before. + */ + if (runnerIndex == -1) break; + + DomInfo runnerInfo = domInfos[runnerIndex]; + + if (runnerInfo.dominanceFrontiers.has(b)) { + break; + } + + // Add b to runner's dominance frontier set. + runnerInfo.dominanceFrontiers.add(b); + runnerIndex = runnerInfo.idom; + } + } + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/Dominators.java b/dx/src/com/android/jack/dx/ssa/Dominators.java new file mode 100644 index 00000000..b3b4c3fd --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/Dominators.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; + +/** + * This class computes dominator and post-dominator information using the + * Lengauer-Tarjan method. + * + * See A Fast Algorithm for Finding Dominators in a Flowgraph + * T. Lengauer & R. Tarjan, ACM TOPLAS July 1979, pgs 121-141. + * + * This implementation runs in time O(n log n). The time bound + * could be changed to O(n * ack(n)) with a small change to the link and eval, + * and an addition of a child field to the DFS info. In reality, the constant + * overheads are high enough that the current method is faster in all but the + * strangest artificially constructed examples. + * + * The basic idea behind this algorithm is to perform a DFS walk, keeping track + * of various info about parents. We then use this info to calculate the + * dominators, using union-find structures to link together the DFS info, + * then finally evaluate the union-find results to get the dominators. + * This implementation is m log n because it does not perform union by + * rank to keep the union-find tree balanced. + */ +public final class Dominators { + /* postdom is true if we want post dominators */ + private final boolean postdom; + + /* {@code non-null;} method being processed */ + private final SsaMethod meth; + + /* Method's basic blocks. */ + private final ArrayList<SsaBasicBlock> blocks; + + /** indexed by basic block index */ + private final DFSInfo[] info; + + private final ArrayList<SsaBasicBlock> vertex; + + /** {@code non-null;} the raw dominator info */ + private final DomFront.DomInfo domInfos[]; + + /** + * Constructs an instance. + * + * @param meth {@code non-null;} method to process + * @param domInfos {@code non-null;} the raw dominator info + * @param postdom true for postdom information, false for normal dom info + */ + private Dominators(SsaMethod meth, DomFront.DomInfo[] domInfos, + boolean postdom) { + this.meth = meth; + this.domInfos = domInfos; + this.postdom = postdom; + this.blocks = meth.getBlocks(); + this.info = new DFSInfo[blocks.size() + 2]; + this.vertex = new ArrayList<SsaBasicBlock>(); + } + + /** + * Constructs a fully-initialized instance. (This method exists so as + * to avoid calling a large amount of code in the constructor.) + * + * @param meth {@code non-null;} method to process + * @param domInfos {@code non-null;} the raw dominator info + * @param postdom true for postdom information, false for normal dom info + */ + public static Dominators make(SsaMethod meth, DomFront.DomInfo[] domInfos, + boolean postdom) { + Dominators result = new Dominators(meth, domInfos, postdom); + + result.run(); + return result; + } + + private BitSet getSuccs(SsaBasicBlock block) { + if (postdom) { + return block.getPredecessors(); + } else { + return block.getSuccessors(); + } + } + + private BitSet getPreds(SsaBasicBlock block) { + if (postdom) { + return block.getSuccessors(); + } else { + return block.getPredecessors(); + } + } + + /** + * Performs path compress on the DFS info. + * + * @param in Basic block whose DFS info we are path compressing. + */ + private void compress(SsaBasicBlock in) { + DFSInfo bbInfo = info[in.getIndex()]; + DFSInfo ancestorbbInfo = info[bbInfo.ancestor.getIndex()]; + + if (ancestorbbInfo.ancestor != null) { + ArrayList<SsaBasicBlock> worklist = new ArrayList<SsaBasicBlock>(); + HashSet<SsaBasicBlock> visited = new HashSet<SsaBasicBlock>(); + worklist.add(in); + + while (!worklist.isEmpty()) { + int wsize = worklist.size(); + SsaBasicBlock v = worklist.get(wsize - 1); + DFSInfo vbbInfo = info[v.getIndex()]; + SsaBasicBlock vAncestor = vbbInfo.ancestor; + DFSInfo vabbInfo = info[vAncestor.getIndex()]; + + // Make sure we process our ancestor before ourselves. + if (visited.add(vAncestor) && vabbInfo.ancestor != null) { + worklist.add(vAncestor); + continue; + } + worklist.remove(wsize - 1); + + // Update based on ancestor info. + if (vabbInfo.ancestor == null) { + continue; + } + SsaBasicBlock vAncestorRep = vabbInfo.rep; + SsaBasicBlock vRep = vbbInfo.rep; + if (info[vAncestorRep.getIndex()].semidom + < info[vRep.getIndex()].semidom) { + vbbInfo.rep = vAncestorRep; + } + vbbInfo.ancestor = vabbInfo.ancestor; + } + } + } + + private SsaBasicBlock eval(SsaBasicBlock v) { + DFSInfo bbInfo = info[v.getIndex()]; + + if (bbInfo.ancestor == null) { + return v; + } + + compress(v); + return bbInfo.rep; + } + + /** + * Performs dominator/post-dominator calculation for the control + * flow graph. + * + * @param meth {@code non-null;} method to analyze + */ + private void run() { + SsaBasicBlock root = postdom + ? meth.getExitBlock() : meth.getEntryBlock(); + + if (root != null) { + vertex.add(root); + domInfos[root.getIndex()].idom = root.getIndex(); + } + + /* + * First we perform a DFS numbering of the blocks, by + * numbering the dfs tree roots. + */ + + DfsWalker walker = new DfsWalker(); + meth.forEachBlockDepthFirst(postdom, walker); + + // the largest semidom number assigned + int dfsMax = vertex.size() - 1; + + // Now calculate semidominators. + for (int i = dfsMax; i >= 2; --i) { + SsaBasicBlock w = vertex.get(i); + DFSInfo wInfo = info[w.getIndex()]; + + BitSet preds = getPreds(w); + for (int j = preds.nextSetBit(0); + j >= 0; + j = preds.nextSetBit(j + 1)) { + SsaBasicBlock predBlock = blocks.get(j); + DFSInfo predInfo = info[predBlock.getIndex()]; + + /* + * PredInfo may not exist in case the predecessor is + * not reachable. + */ + if (predInfo != null) { + int predSemidom = info[eval(predBlock).getIndex()].semidom; + if (predSemidom < wInfo.semidom) { + wInfo.semidom = predSemidom; + } + } + } + info[vertex.get(wInfo.semidom).getIndex()].bucket.add(w); + + /* + * Normally we would call link here, but in our O(m log n) + * implementation this is equivalent to the following + * single line. + */ + wInfo.ancestor = wInfo.parent; + + // Implicity define idom for each vertex. + ArrayList<SsaBasicBlock> wParentBucket; + wParentBucket = info[wInfo.parent.getIndex()].bucket; + + while (!wParentBucket.isEmpty()) { + int lastItem = wParentBucket.size() - 1; + SsaBasicBlock last = wParentBucket.remove(lastItem); + SsaBasicBlock U = eval(last); + if (info[U.getIndex()].semidom + < info[last.getIndex()].semidom) { + domInfos[last.getIndex()].idom = U.getIndex(); + } else { + domInfos[last.getIndex()].idom = wInfo.parent.getIndex(); + } + } + } + + // Now explicitly define the immediate dominator of each vertex + for (int i = 2; i <= dfsMax; ++i) { + SsaBasicBlock w = vertex.get(i); + if (domInfos[w.getIndex()].idom + != vertex.get(info[w.getIndex()].semidom).getIndex()) { + domInfos[w.getIndex()].idom + = domInfos[domInfos[w.getIndex()].idom].idom; + } + } + } + + /** + * Callback for depth-first walk through control flow graph (either + * from the entry block or the exit block). Records the traversal order + * in the {@code info}list. + */ + private class DfsWalker implements SsaBasicBlock.Visitor { + private int dfsNum = 0; + + public void visitBlock(SsaBasicBlock v, SsaBasicBlock parent) { + DFSInfo bbInfo = new DFSInfo(); + bbInfo.semidom = ++dfsNum; + bbInfo.rep = v; + bbInfo.parent = parent; + vertex.add(v); + info[v.getIndex()] = bbInfo; + } + } + + private static final class DFSInfo { + public int semidom; + public SsaBasicBlock parent; + + /** + * rep(resentative) is known as "label" in the paper. It is the node + * that our block's DFS info has been unioned to. + */ + public SsaBasicBlock rep; + + public SsaBasicBlock ancestor; + public ArrayList<SsaBasicBlock> bucket; + + public DFSInfo() { + bucket = new ArrayList<SsaBasicBlock>(); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java b/dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java new file mode 100644 index 00000000..791a3b52 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/EscapeAnalysis.java @@ -0,0 +1,843 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.Exceptions; +import com.android.jack.dx.rop.code.FillArrayDataInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.ThrowingCstInsn; +import com.android.jack.dx.rop.code.ThrowingInsn; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.cst.CstMethodRef; +import com.android.jack.dx.rop.cst.CstNat; +import com.android.jack.dx.rop.cst.CstString; +import com.android.jack.dx.rop.cst.CstType; +import com.android.jack.dx.rop.cst.TypedConstant; +import com.android.jack.dx.rop.cst.Zeroes; +import com.android.jack.dx.rop.type.StdTypeList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.List; + +/** + * Simple intraprocedural escape analysis. Finds new arrays that don't escape + * the method they are created in and replaces the array values with registers. + */ +public class EscapeAnalysis { + /** + * Struct used to generate and maintain escape analysis results. + */ + static class EscapeSet { + /** set containing all registers related to an object */ + BitSet regSet; + /** escape state of the object */ + EscapeState escape; + /** list of objects that are put into this object */ + ArrayList<EscapeSet> childSets; + /** list of objects that this object is put into */ + ArrayList<EscapeSet> parentSets; + /** flag to indicate this object is a scalar replaceable array */ + boolean replaceableArray; + + /** + * Constructs an instance of an EscapeSet + * + * @param reg the SSA register that defines the object + * @param size the number of registers in the method + * @param escState the lattice value to initially set this to + */ + EscapeSet(int reg, int size, EscapeState escState) { + regSet = new BitSet(size); + regSet.set(reg); + escape = escState; + childSets = new ArrayList<EscapeSet>(); + parentSets = new ArrayList<EscapeSet>(); + replaceableArray = false; + } + } + + /** + * Lattice values used to indicate escape state for an object. Analysis can + * only raise escape state values, not lower them. + * + * TOP - Used for objects that haven't been analyzed yet + * NONE - Object does not escape, and is eligible for scalar replacement. + * METHOD - Object remains local to method, but can't be scalar replaced. + * INTER - Object is passed between methods. (treated as globally escaping + * since this is an intraprocedural analysis) + * GLOBAL - Object escapes globally. + */ + public enum EscapeState { + TOP, NONE, METHOD, INTER, GLOBAL + } + + /** method we're processing */ + private SsaMethod ssaMeth; + /** ssaMeth.getRegCount() */ + private int regCount; + /** Lattice values for each object register group */ + private ArrayList<EscapeSet> latticeValues; + + /** + * Constructs an instance. + * + * @param ssaMeth method to process + */ + private EscapeAnalysis(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + this.regCount = ssaMeth.getRegCount(); + this.latticeValues = new ArrayList<EscapeSet>(); + } + + /** + * Finds the index in the lattice for a particular register. + * Returns the size of the lattice if the register wasn't found. + * + * @param reg {@code non-null;} register being looked up + * @return index of the register or size of the lattice if it wasn't found. + */ + private int findSetIndex(RegisterSpec reg) { + int i; + for (i = 0; i < latticeValues.size(); i++) { + EscapeSet e = latticeValues.get(i); + if (e.regSet.get(reg.getReg())) { + return i; + } + } + return i; + } + + /** + * Finds the corresponding instruction for a given move result + * + * @param moveInsn {@code non-null;} a move result instruction + * @return {@code non-null;} the instruction that produces the result for + * the move + */ + private SsaInsn getInsnForMove(SsaInsn moveInsn) { + int pred = moveInsn.getBlock().getPredecessors().nextSetBit(0); + ArrayList<SsaInsn> predInsns = ssaMeth.getBlocks().get(pred).getInsns(); + return predInsns.get(predInsns.size()-1); + } + + /** + * Finds the corresponding move result for a given instruction + * + * @param insn {@code non-null;} an instruction that must always be + * followed by a move result + * @return {@code non-null;} the move result for the given instruction + */ + private SsaInsn getMoveForInsn(SsaInsn insn) { + int succ = insn.getBlock().getSuccessors().nextSetBit(0); + ArrayList<SsaInsn> succInsns = ssaMeth.getBlocks().get(succ).getInsns(); + return succInsns.get(0); + } + + /** + * Creates a link in the lattice between two EscapeSets due to a put + * instruction. The object being put is the child and the object being put + * into is the parent. A child set must always have an escape state at + * least as high as its parent. + * + * @param parentSet {@code non-null;} the EscapeSet for the object being put + * into + * @param childSet {@code non-null;} the EscapeSet for the object being put + */ + private void addEdge(EscapeSet parentSet, EscapeSet childSet) { + if (!childSet.parentSets.contains(parentSet)) { + childSet.parentSets.add(parentSet); + } + if (!parentSet.childSets.contains(childSet)) { + parentSet.childSets.add(childSet); + } + } + + /** + * Merges all links in the lattice among two EscapeSets. On return, the + * newNode will have its old links as well as all links from the oldNode. + * The oldNode has all its links removed. + * + * @param newNode {@code non-null;} the EscapeSet to merge all links into + * @param oldNode {@code non-null;} the EscapeSet to remove all links from + */ + private void replaceNode(EscapeSet newNode, EscapeSet oldNode) { + for (EscapeSet e : oldNode.parentSets) { + e.childSets.remove(oldNode); + e.childSets.add(newNode); + newNode.parentSets.add(e); + } + for (EscapeSet e : oldNode.childSets) { + e.parentSets.remove(oldNode); + e.parentSets.add(newNode); + newNode.childSets.add(e); + } + } + + /** + * Performs escape analysis on a method. Finds scalar replaceable arrays and + * replaces them with equivalent registers. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + new EscapeAnalysis(ssaMethod).run(); + } + + /** + * Process a single instruction, looking for new objects resulting from + * move result or move param. + * + * @param insn {@code non-null;} instruction to process + */ + private void processInsn(SsaInsn insn) { + int op = insn.getOpcode().getOpcode(); + RegisterSpec result = insn.getResult(); + EscapeSet escSet; + + // Identify new objects + if (op == RegOps.MOVE_RESULT_PSEUDO && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Handle objects generated through move_result_pseudo + escSet = processMoveResultPseudoInsn(insn); + processRegister(result, escSet); + } else if (op == RegOps.MOVE_PARAM && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Track method arguments that are objects + escSet = new EscapeSet(result.getReg(), regCount, EscapeState.NONE); + latticeValues.add(escSet); + processRegister(result, escSet); + } else if (op == RegOps.MOVE_RESULT && + result.getTypeBearer().getBasicType() == Type.BT_OBJECT) { + // Track method return values that are objects + escSet = new EscapeSet(result.getReg(), regCount, EscapeState.NONE); + latticeValues.add(escSet); + processRegister(result, escSet); + } + } + + /** + * Determine the origin of a move result pseudo instruction that generates + * an object. Creates a new EscapeSet for the new object accordingly. + * + * @param insn {@code non-null;} move result pseudo instruction to process + * @return {@code non-null;} an EscapeSet for the object referred to by the + * move result pseudo instruction + */ + private EscapeSet processMoveResultPseudoInsn(SsaInsn insn) { + RegisterSpec result = insn.getResult(); + SsaInsn prevSsaInsn = getInsnForMove(insn); + int prevOpcode = prevSsaInsn.getOpcode().getOpcode(); + EscapeSet escSet; + RegisterSpec prevSource; + + switch(prevOpcode) { + // New instance / Constant + case RegOps.NEW_INSTANCE: + case RegOps.CONST: + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + break; + // New array + case RegOps.NEW_ARRAY: + case RegOps.FILLED_NEW_ARRAY: + prevSource = prevSsaInsn.getSources().get(0); + if (prevSource.getTypeBearer().isConstant()) { + // New fixed array + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + escSet.replaceableArray = true; + } else { + // New variable array + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + } + break; + // Loading a static object + case RegOps.GET_STATIC: + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + break; + // Type cast / load an object from a field or array + case RegOps.CHECK_CAST: + case RegOps.GET_FIELD: + case RegOps.AGET: + prevSource = prevSsaInsn.getSources().get(0); + int setIndex = findSetIndex(prevSource); + + // Set should already exist, try to find it + if (setIndex != latticeValues.size()) { + escSet = latticeValues.get(setIndex); + escSet.regSet.set(result.getReg()); + return escSet; + } + + // Set not found, must be either null or unknown + if (prevSource.getType() == Type.KNOWN_NULL) { + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.NONE); + } else { + escSet = new EscapeSet(result.getReg(), regCount, + EscapeState.GLOBAL); + } + break; + default: + return null; + } + + // Add the newly created escSet to the lattice and return it + latticeValues.add(escSet); + return escSet; + } + + /** + * Iterate through all the uses of a new object. + * + * @param result {@code non-null;} register where new object is stored + * @param escSet {@code non-null;} EscapeSet for the new object + */ + private void processRegister(RegisterSpec result, EscapeSet escSet) { + ArrayList<RegisterSpec> regWorklist = new ArrayList<RegisterSpec>(); + regWorklist.add(result); + + // Go through the worklist + while (!regWorklist.isEmpty()) { + int listSize = regWorklist.size() - 1; + RegisterSpec def = regWorklist.remove(listSize); + List<SsaInsn> useList = ssaMeth.getUseListForRegister(def.getReg()); + + // Handle all the uses of this register + for (SsaInsn use : useList) { + Rop useOpcode = use.getOpcode(); + + if (useOpcode == null) { + // Handle phis + processPhiUse(use, escSet, regWorklist); + } else { + // Handle other opcodes + processUse(def, use, escSet, regWorklist); + } + } + } + } + + /** + * Handles phi uses of new objects. Will merge together the sources of a phi + * into a single EscapeSet. Adds the result of the phi to the worklist so + * its uses can be followed. + * + * @param use {@code non-null;} phi use being processed + * @param escSet {@code non-null;} EscapeSet for the object + * @param regWorklist {@code non-null;} worklist of instructions left to + * process for this object + */ + private void processPhiUse(SsaInsn use, EscapeSet escSet, + ArrayList<RegisterSpec> regWorklist) { + int setIndex = findSetIndex(use.getResult()); + if (setIndex != latticeValues.size()) { + // Check if result is in a set already + EscapeSet mergeSet = latticeValues.get(setIndex); + if (mergeSet != escSet) { + // If it is, merge the sets and states, then delete the copy + escSet.replaceableArray = false; + escSet.regSet.or(mergeSet.regSet); + if (escSet.escape.compareTo(mergeSet.escape) < 0) { + escSet.escape = mergeSet.escape; + } + replaceNode(escSet, mergeSet); + latticeValues.remove(setIndex); + } + } else { + // If no set is found, add it to this escSet and the worklist + escSet.regSet.set(use.getResult().getReg()); + regWorklist.add(use.getResult()); + } + } + + /** + * Handles non-phi uses of new objects. Checks to see how instruction is + * used and updates the escape state accordingly. + * + * @param def {@code non-null;} register holding definition of new object + * @param use {@code non-null;} use of object being processed + * @param escSet {@code non-null;} EscapeSet for the object + * @param regWorklist {@code non-null;} worklist of instructions left to + * process for this object + */ + private void processUse(RegisterSpec def, SsaInsn use, EscapeSet escSet, + ArrayList<RegisterSpec> regWorklist) { + int useOpcode = use.getOpcode().getOpcode(); + switch (useOpcode) { + case RegOps.MOVE: + // Follow uses of the move by adding it to the worklist + escSet.regSet.set(use.getResult().getReg()); + regWorklist.add(use.getResult()); + break; + case RegOps.IF_EQ: + case RegOps.IF_NE: + case RegOps.CHECK_CAST: + // Compared objects can't be replaced, so promote if necessary + if (escSet.escape.compareTo(EscapeState.METHOD) < 0) { + escSet.escape = EscapeState.METHOD; + } + break; + case RegOps.APUT: + // For array puts, check for a constant array index + RegisterSpec putIndex = use.getSources().get(2); + if (!putIndex.getTypeBearer().isConstant()) { + // If not constant, array can't be replaced + escSet.replaceableArray = false; + } + // Intentional fallthrough + case RegOps.PUT_FIELD: + // Skip non-object puts + RegisterSpec putValue = use.getSources().get(0); + if (putValue.getTypeBearer().getBasicType() != Type.BT_OBJECT) { + break; + } + escSet.replaceableArray = false; + + // Raise 1st object's escape state to 2nd if 2nd is higher + RegisterSpecList sources = use.getSources(); + if (sources.get(0).getReg() == def.getReg()) { + int setIndex = findSetIndex(sources.get(1)); + if (setIndex != latticeValues.size()) { + EscapeSet parentSet = latticeValues.get(setIndex); + addEdge(parentSet, escSet); + if (escSet.escape.compareTo(parentSet.escape) < 0) { + escSet.escape = parentSet.escape; + } + } + } else { + int setIndex = findSetIndex(sources.get(0)); + if (setIndex != latticeValues.size()) { + EscapeSet childSet = latticeValues.get(setIndex); + addEdge(escSet, childSet); + if (childSet.escape.compareTo(escSet.escape) < 0) { + childSet.escape = escSet.escape; + } + } + } + break; + case RegOps.AGET: + // For array gets, check for a constant array index + RegisterSpec getIndex = use.getSources().get(1); + if (!getIndex.getTypeBearer().isConstant()) { + // If not constant, array can't be replaced + escSet.replaceableArray = false; + } + break; + case RegOps.PUT_STATIC: + // Static puts cause an object to escape globally + escSet.escape = EscapeState.GLOBAL; + break; + case RegOps.INVOKE_STATIC: + case RegOps.INVOKE_VIRTUAL: + case RegOps.INVOKE_SUPER: + case RegOps.INVOKE_DIRECT: + case RegOps.INVOKE_INTERFACE: + case RegOps.RETURN: + case RegOps.THROW: + // These operations cause an object to escape interprocedurally + escSet.escape = EscapeState.INTER; + break; + default: + break; + } + } + + /** + * Performs scalar replacement on all eligible arrays. + */ + private void scalarReplacement() { + // Iterate through lattice, looking for non-escaping replaceable arrays + for (EscapeSet escSet : latticeValues) { + if (!escSet.replaceableArray || escSet.escape != EscapeState.NONE) { + continue; + } + + // Get the instructions for the definition and move of the array + int e = escSet.regSet.nextSetBit(0); + SsaInsn def = ssaMeth.getDefinitionForRegister(e); + SsaInsn prev = getInsnForMove(def); + + // Create a map for the new registers that will be created + TypeBearer lengthReg = prev.getSources().get(0).getTypeBearer(); + int length = ((CstLiteralBits) lengthReg).getIntBits(); + ArrayList<RegisterSpec> newRegs = + new ArrayList<RegisterSpec>(length); + HashSet<SsaInsn> deletedInsns = new HashSet<SsaInsn>(); + + // Replace the definition of the array with registers + replaceDef(def, prev, length, newRegs); + + // Mark definition instructions for deletion + deletedInsns.add(prev); + deletedInsns.add(def); + + // Go through all uses of the array + List<SsaInsn> useList = ssaMeth.getUseListForRegister(e); + for (SsaInsn use : useList) { + // Replace the use with scalars and then mark it for deletion + replaceUse(use, prev, newRegs, deletedInsns); + deletedInsns.add(use); + } + + // Delete all marked instructions + ssaMeth.deleteInsns(deletedInsns); + ssaMeth.onInsnsChanged(); + + // Convert the method back to SSA form + SsaConverter.updateSsaMethod(ssaMeth, regCount); + + // Propagate and remove extra moves added by scalar replacement + movePropagate(); + } + } + + /** + * Replaces the instructions that define an array with equivalent registers. + * For each entry in the array, a register is created, initialized to zero. + * A mapping between this register and the corresponding array index is + * added. + * + * @param def {@code non-null;} move result instruction for array + * @param prev {@code non-null;} instruction for instantiating new array + * @param length size of the new array + * @param newRegs {@code non-null;} mapping of array indices to new + * registers to be populated + */ + private void replaceDef(SsaInsn def, SsaInsn prev, int length, + ArrayList<RegisterSpec> newRegs) { + Type resultType = def.getResult().getType(); + + // Create new zeroed out registers for each element in the array + for (int i = 0; i < length; i++) { + Constant newZero = Zeroes.zeroFor(resultType.getComponentType()); + TypedConstant typedZero = (TypedConstant) newZero; + RegisterSpec newReg = + RegisterSpec.make(ssaMeth.makeNewSsaReg(), typedZero); + newRegs.add(newReg); + insertPlainInsnBefore(def, RegisterSpecList.EMPTY, newReg, + RegOps.CONST, newZero); + } + } + + /** + * Replaces the use for a scalar replaceable array. Gets and puts become + * move instructions, and array lengths and fills are handled. Can also + * identify ArrayIndexOutOfBounds exceptions and throw them if detected. + * + * @param use {@code non-null;} move result instruction for array + * @param prev {@code non-null;} instruction for instantiating new array + * @param newRegs {@code non-null;} mapping of array indices to new + * registers + * @param deletedInsns {@code non-null;} set of instructions marked for + * deletion + */ + private void replaceUse(SsaInsn use, SsaInsn prev, + ArrayList<RegisterSpec> newRegs, + HashSet<SsaInsn> deletedInsns) { + int index; + int length = newRegs.size(); + SsaInsn next; + RegisterSpecList sources; + RegisterSpec source, result; + CstLiteralBits indexReg; + + switch (use.getOpcode().getOpcode()) { + case RegOps.AGET: + // Replace array gets with moves + next = getMoveForInsn(use); + sources = use.getSources(); + indexReg = ((CstLiteralBits) sources.get(1).getTypeBearer()); + index = indexReg.getIntBits(); + if (index < length) { + source = newRegs.get(index); + result = source.withReg(next.getResult().getReg()); + insertPlainInsnBefore(next, RegisterSpecList.make(source), + result, RegOps.MOVE, null); + } else { + // Throw an exception if the index is out of bounds + insertExceptionThrow(next, sources.get(1), deletedInsns); + deletedInsns.add(next.getBlock().getInsns().get(2)); + } + deletedInsns.add(next); + break; + case RegOps.APUT: + // Replace array puts with moves + sources = use.getSources(); + indexReg = ((CstLiteralBits) sources.get(2).getTypeBearer()); + index = indexReg.getIntBits(); + if (index < length) { + source = sources.get(0); + result = source.withReg(newRegs.get(index).getReg()); + insertPlainInsnBefore(use, RegisterSpecList.make(source), + result, RegOps.MOVE, null); + // Update the newReg entry to mark value as unknown now + newRegs.set(index, result.withSimpleType()); + } else { + // Throw an exception if the index is out of bounds + insertExceptionThrow(use, sources.get(2), deletedInsns); + } + break; + case RegOps.ARRAY_LENGTH: + // Replace array lengths with const instructions + TypeBearer lengthReg = prev.getSources().get(0).getTypeBearer(); + //CstInteger lengthReg = CstInteger.make(length); + next = getMoveForInsn(use); + insertPlainInsnBefore(next, RegisterSpecList.EMPTY, + next.getResult(), RegOps.CONST, + (Constant) lengthReg); + deletedInsns.add(next); + break; + case RegOps.MARK_LOCAL: + // Remove mark local instructions + break; + case RegOps.FILL_ARRAY_DATA: + // Create const instructions for each fill value + Insn ropUse = use.getOriginalRopInsn(); + FillArrayDataInsn fill = (FillArrayDataInsn) ropUse; + ArrayList<Constant> constList = fill.getInitValues(); + for (int i = 0; i < length; i++) { + RegisterSpec newFill = + RegisterSpec.make(newRegs.get(i).getReg(), + (TypeBearer) constList.get(i)); + insertPlainInsnBefore(use, RegisterSpecList.EMPTY, newFill, + RegOps.CONST, constList.get(i)); + // Update the newRegs to hold the new const value + newRegs.set(i, newFill); + } + break; + default: + } + } + + /** + * Identifies extra moves added by scalar replacement and propagates the + * source of the move to any users of the result. + */ + private void movePropagate() { + for (int i = 0; i < ssaMeth.getRegCount(); i++) { + SsaInsn insn = ssaMeth.getDefinitionForRegister(i); + + // Look for move instructions only + if (insn == null || insn.getOpcode() == null || + insn.getOpcode().getOpcode() != RegOps.MOVE) { + continue; + } + + final ArrayList<SsaInsn>[] useList = ssaMeth.getUseListCopy(); + final RegisterSpec source = insn.getSources().get(0); + final RegisterSpec result = insn.getResult(); + + // Ignore moves that weren't added due to scalar replacement + if (source.getReg() < regCount && result.getReg() < regCount) { + continue; + } + + // Create a mapping from source to result + RegisterMapper mapper = new RegisterMapper() { + @Override + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == result.getReg()) { + return source; + } + + return registerSpec; + } + }; + + // Modify all uses of the move to use the source of the move instead + for (SsaInsn use : useList[result.getReg()]) { + use.mapSourceRegisters(mapper); + } + } + } + + /** + * Runs escape analysis and scalar replacement of arrays. + */ + private void run() { + ssaMeth.forEachBlockDepthFirstDom(new SsaBasicBlock.Visitor() { + public void visitBlock (SsaBasicBlock block, + SsaBasicBlock unused) { + block.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn(NormalSsaInsn insn) { + // do nothing + } + + public void visitPhiInsn(PhiInsn insn) { + // do nothing + } + + public void visitNonMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + }); + } + }); + + // Go through lattice and promote fieldSets as necessary + for (EscapeSet e : latticeValues) { + if (e.escape != EscapeState.NONE) { + for (EscapeSet field : e.childSets) { + if (e.escape.compareTo(field.escape) > 0) { + field.escape = e.escape; + } + } + } + } + + // Perform scalar replacement for arrays + scalarReplacement(); + } + + /** + * Replaces instructions that trigger an ArrayIndexOutofBounds exception + * with an actual throw of the exception. + * + * @param insn {@code non-null;} instruction causing the exception + * @param index {@code non-null;} index value that is out of bounds + * @param deletedInsns {@code non-null;} set of instructions marked for + * deletion + */ + private void insertExceptionThrow(SsaInsn insn, RegisterSpec index, + HashSet<SsaInsn> deletedInsns) { + // Create a new ArrayIndexOutOfBoundsException + CstType exception = + new CstType(Exceptions.TYPE_ArrayIndexOutOfBoundsException); + insertThrowingInsnBefore(insn, RegisterSpecList.EMPTY, null, + RegOps.NEW_INSTANCE, exception); + + // Add a successor block with a move result pseudo for the exception + SsaBasicBlock currBlock = insn.getBlock(); + SsaBasicBlock newBlock = + currBlock.insertNewSuccessor(currBlock.getPrimarySuccessor()); + SsaInsn newInsn = newBlock.getInsns().get(0); + RegisterSpec newReg = + RegisterSpec.make(ssaMeth.makeNewSsaReg(), exception); + insertPlainInsnBefore(newInsn, RegisterSpecList.EMPTY, newReg, + RegOps.MOVE_RESULT_PSEUDO, null); + + // Add another successor block to initialize the exception + SsaBasicBlock newBlock2 = + newBlock.insertNewSuccessor(newBlock.getPrimarySuccessor()); + SsaInsn newInsn2 = newBlock2.getInsns().get(0); + CstNat newNat = new CstNat(new CstString("<init>"), new CstString("(I)V")); + CstMethodRef newRef = new CstMethodRef(exception, newNat); + insertThrowingInsnBefore(newInsn2, RegisterSpecList.make(newReg, index), + null, RegOps.INVOKE_DIRECT, newRef); + deletedInsns.add(newInsn2); + + // Add another successor block to throw the new exception + SsaBasicBlock newBlock3 = + newBlock2.insertNewSuccessor(newBlock2.getPrimarySuccessor()); + SsaInsn newInsn3 = newBlock3.getInsns().get(0); + insertThrowingInsnBefore(newInsn3, RegisterSpecList.make(newReg), null, + RegOps.THROW, null); + newBlock3.replaceSuccessor(newBlock3.getPrimarySuccessorIndex(), + ssaMeth.getExitBlock().getIndex()); + deletedInsns.add(newInsn3); + } + + /** + * Inserts a new PlainInsn before the given instruction. + * TODO: move this somewhere more appropriate + * + * @param insn {@code non-null;} instruction to insert before + * @param newSources {@code non-null;} sources of new instruction + * @param newResult {@code non-null;} result of new instruction + * @param newOpcode opcode of new instruction + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void insertPlainInsnBefore(SsaInsn insn, + RegisterSpecList newSources, RegisterSpec newResult, int newOpcode, + Constant cst) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop newRop; + if (newOpcode == RegOps.MOVE_RESULT_PSEUDO) { + newRop = Rops.opMoveResultPseudo(newResult.getType()); + } else { + newRop = Rops.ropFor(newOpcode, newResult, newSources, cst); + } + + Insn newRopInsn; + if (cst == null) { + newRopInsn = new PlainInsn(newRop, + originalRopInsn.getPosition(), newResult, newSources); + } else { + newRopInsn = new PlainCstInsn(newRop, + originalRopInsn.getPosition(), newResult, newSources, cst); + } + + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + List<SsaInsn> insns = insn.getBlock().getInsns(); + + insns.add(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } + + /** + * Inserts a new ThrowingInsn before the given instruction. + * TODO: move this somewhere more appropriate + * + * @param insn {@code non-null;} instruction to insert before + * @param newSources {@code non-null;} sources of new instruction + * @param newResult {@code non-null;} result of new instruction + * @param newOpcode opcode of new instruction + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void insertThrowingInsnBefore(SsaInsn insn, + RegisterSpecList newSources, RegisterSpec newResult, int newOpcode, + Constant cst) { + + Insn origRopInsn = insn.getOriginalRopInsn(); + Rop newRop = Rops.ropFor(newOpcode, newResult, newSources, cst); + Insn newRopInsn; + if (cst == null) { + newRopInsn = new ThrowingInsn(newRop, + origRopInsn.getPosition(), newSources, StdTypeList.EMPTY); + } else { + newRopInsn = new ThrowingCstInsn(newRop, + origRopInsn.getPosition(), newSources, StdTypeList.EMPTY, cst); + } + + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + List<SsaInsn> insns = insn.getBlock().getInsns(); + + insns.add(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java b/dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java new file mode 100644 index 00000000..589e2f28 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/InterferenceRegisterMapper.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.ssa.back.InterferenceGraph; +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; + +import java.util.ArrayList; +import java.util.BitSet; + +/** + * A register mapper that keeps track of the accumulated interference + * information for the registers in the new namespace. + * + * Please note that this mapper requires that the old namespace does not + * have variable register widths/categories, and the new namespace does. + */ +public class InterferenceRegisterMapper extends BasicRegisterMapper { + /** + * Array of interference sets. ArrayList is indexed by new namespace + * and BitIntSet's are indexed by old namespace. The list expands + * as needed and missing items are assumed to interfere with nothing. + * + * Bit sets are always used here, unlike elsewhere, because the max + * size of this matrix will be (countSsaRegs * countRopRegs), which may + * grow to hundreds of K but not megabytes. + */ + private final ArrayList<BitIntSet> newRegInterference; + + /** the interference graph for the old namespace */ + private final InterferenceGraph oldRegInterference; + + /** + * Constructs an instance + * + * @param countOldRegisters number of registers in old namespace + */ + public InterferenceRegisterMapper(InterferenceGraph oldRegInterference, + int countOldRegisters) { + super(countOldRegisters); + + newRegInterference = new ArrayList<BitIntSet>(); + this.oldRegInterference = oldRegInterference; + } + + /** {@inheritDoc} */ + @Override + public void addMapping(int oldReg, int newReg, int category) { + super.addMapping(oldReg, newReg, category); + + addInterfence(newReg, oldReg); + + if (category == 2) { + addInterfence(newReg + 1, oldReg); + } + } + + /** + * Checks to see if old namespace reg {@code oldReg} interferes + * with what currently maps to {@code newReg}. + * + * @param oldReg old namespace register + * @param newReg new namespace register + * @param category category of old namespace register + * @return true if oldReg will interfere with newReg + */ + public boolean interferes(int oldReg, int newReg, int category) { + if (newReg >= newRegInterference.size()) { + return false; + } else { + IntSet existing = newRegInterference.get(newReg); + + if (existing == null) { + return false; + } else if (category == 1) { + return existing.has(oldReg); + } else { + return existing.has(oldReg) + || (interferes(oldReg, newReg+1, category-1)); + } + } + } + + /** + * Checks to see if old namespace reg {@code oldReg} interferes + * with what currently maps to {@code newReg}. + * + * @param oldSpec {@code non-null;} old namespace register + * @param newReg new namespace register + * @return true if oldReg will interfere with newReg + */ + public boolean interferes(RegisterSpec oldSpec, int newReg) { + return interferes(oldSpec.getReg(), newReg, oldSpec.getCategory()); + } + + /** + * Adds a register's interference set to the interference list, + * growing it if necessary. + * + * @param newReg register in new namespace + * @param oldReg register in old namespace + */ + private void addInterfence(int newReg, int oldReg) { + newRegInterference.ensureCapacity(newReg + 1); + + while (newReg >= newRegInterference.size()) { + newRegInterference.add(new BitIntSet(newReg +1)); + } + + oldRegInterference.mergeInterferenceSet( + oldReg, newRegInterference.get(newReg)); + } + + /** + * Checks to see if any of a set of old-namespace registers are + * pinned to the specified new-namespace reg + category. Takes into + * account the category of the old-namespace registers. + * + * @param oldSpecs {@code non-null;} set of old-namespace regs + * @param newReg {@code >= 0;} new-namespace register + * @param targetCategory {@code 1..2;} the number of adjacent new-namespace + * registers (starting at ropReg) to consider + * @return true if any of the old-namespace register have been mapped + * to the new-namespace register + category + */ + public boolean areAnyPinned(RegisterSpecList oldSpecs, + int newReg, int targetCategory) { + int sz = oldSpecs.size(); + + for (int i = 0; i < sz; i++) { + RegisterSpec oldSpec = oldSpecs.get(i); + int r = oldToNew(oldSpec.getReg()); + + /* + * If oldSpec is a category-2 register, then check both newReg + * and newReg - 1. + */ + if (r == newReg + || (oldSpec.getCategory() == 2 && (r + 1) == newReg) + || (targetCategory == 2 && (r == newReg + 1))) { + return true; + } + } + + return false; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java b/dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java new file mode 100644 index 00000000..ebf94019 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/LiteralOpUpgrader.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainCstInsn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.TranslationAdvice; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstLiteralBits; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Upgrades insn to their literal (constant-immediate) equivalent if possible. + * Also switches IF instructions that compare with a constant zero or null + * to be their IF_*Z equivalents. + */ +public class LiteralOpUpgrader { + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** + * Process a method. + * + * @param ssaMethod {@code non-null;} method to process + */ + public static void process(SsaMethod ssaMethod) { + LiteralOpUpgrader dc; + + dc = new LiteralOpUpgrader(ssaMethod); + + dc.run(); + } + + private LiteralOpUpgrader(SsaMethod ssaMethod) { + this.ssaMeth = ssaMethod; + } + + /** + * Returns true if the register contains an integer 0 or a known-null + * object reference + * + * @param spec non-null spec + * @return true for 0 or null type bearers + */ + private static boolean isConstIntZeroOrKnownNull(RegisterSpec spec) { + TypeBearer tb = spec.getTypeBearer(); + if (tb instanceof CstLiteralBits) { + CstLiteralBits clb = (CstLiteralBits) tb; + return (clb.getLongBits() == 0); + } + return false; + } + + /** + * Run the literal op upgrader + */ + private void run() { + final TranslationAdvice advice = Optimizer.getAdvice(); + + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn(NormalSsaInsn insn) { + // do nothing + } + + public void visitPhiInsn(PhiInsn insn) { + // do nothing + } + + public void visitNonMoveInsn(NormalSsaInsn insn) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop opcode = originalRopInsn.getOpcode(); + RegisterSpecList sources = insn.getSources(); + + // Replace insns with constant results with const insns + if (tryReplacingWithConstant(insn)) return; + + if (sources.size() != 2 ) { + // We're only dealing with two-source insns here. + return; + } + + if (opcode.getBranchingness() == Rop.BRANCH_IF) { + /* + * An if instruction can become an if-*z instruction. + */ + if (isConstIntZeroOrKnownNull(sources.get(0))) { + replacePlainInsn(insn, sources.withoutFirst(), + RegOps.flippedIfOpcode(opcode.getOpcode()), null); + } else if (isConstIntZeroOrKnownNull(sources.get(1))) { + replacePlainInsn(insn, sources.withoutLast(), + opcode.getOpcode(), null); + } + } else if (advice.hasConstantOperation( + opcode, sources.get(0), sources.get(1))) { + insn.upgradeToLiteral(); + } else if (opcode.isCommutative() + && advice.hasConstantOperation( + opcode, sources.get(1), sources.get(0))) { + /* + * An instruction can be commuted to a literal operation + */ + + insn.setNewSources( + RegisterSpecList.make( + sources.get(1), sources.get(0))); + + insn.upgradeToLiteral(); + } + } + }); + } + + /** + * Tries to replace an instruction with a const instruction. The given + * instruction must have a constant result for it to be replaced. + * + * @param insn {@code non-null;} instruction to try to replace + * @return true if the instruction was replaced + */ + private boolean tryReplacingWithConstant(NormalSsaInsn insn) { + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop opcode = originalRopInsn.getOpcode(); + RegisterSpec result = insn.getResult(); + + if (result != null && !ssaMeth.isRegALocal(result) && + opcode.getOpcode() != RegOps.CONST) { + TypeBearer type = insn.getResult().getTypeBearer(); + if (type.isConstant() && type.getBasicType() == Type.BT_INT) { + // Replace the instruction with a constant + replacePlainInsn(insn, RegisterSpecList.EMPTY, + RegOps.CONST, (Constant) type); + + // Remove the source as well if this is a move-result-pseudo + if (opcode.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + int pred = insn.getBlock().getPredecessors().nextSetBit(0); + ArrayList<SsaInsn> predInsns = + ssaMeth.getBlocks().get(pred).getInsns(); + NormalSsaInsn sourceInsn = + (NormalSsaInsn) predInsns.get(predInsns.size()-1); + replacePlainInsn(sourceInsn, RegisterSpecList.EMPTY, + RegOps.GOTO, null); + } + return true; + } + } + return false; + } + + /** + * Replaces an SsaInsn containing a PlainInsn with a new PlainInsn. The + * new PlainInsn is constructed with a new RegOp and new sources. + * + * TODO move this somewhere else. + * + * @param insn {@code non-null;} an SsaInsn containing a PlainInsn + * @param newSources {@code non-null;} new sources list for new insn + * @param newOpcode A RegOp from {@link RegOps} + * @param cst {@code null-ok;} constant for new instruction, if any + */ + private void replacePlainInsn(NormalSsaInsn insn, + RegisterSpecList newSources, int newOpcode, Constant cst) { + + Insn originalRopInsn = insn.getOriginalRopInsn(); + Rop newRop = Rops.ropFor(newOpcode, insn.getResult(), newSources, cst); + Insn newRopInsn; + if (cst == null) { + newRopInsn = new PlainInsn(newRop, originalRopInsn.getPosition(), + insn.getResult(), newSources); + } else { + newRopInsn = new PlainCstInsn(newRop, originalRopInsn.getPosition(), + insn.getResult(), newSources, cst); + } + NormalSsaInsn newInsn = new NormalSsaInsn(newRopInsn, insn.getBlock()); + + List<SsaInsn> insns = insn.getBlock().getInsns(); + + ssaMeth.onInsnRemoved(insn); + insns.set(insns.lastIndexOf(insn), newInsn); + ssaMeth.onInsnAdded(newInsn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java b/dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java new file mode 100644 index 00000000..a16eeb19 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/LocalVariableExtractor.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Code to figure out which local variables are active at which points in + * a method. Stolen and retrofitted from + * com.android.jack.dx.rop.code.LocalVariableExtractor + * + * TODO remove this. Allow Rop-form LocalVariableInfo to be passed in, + * converted, and adapted through edge-splitting. + */ +public class LocalVariableExtractor { + /** {@code non-null;} method being extracted from */ + private final SsaMethod method; + + /** {@code non-null;} block list for the method */ + private final ArrayList<SsaBasicBlock> blocks; + + /** {@code non-null;} result in-progress */ + private final LocalVariableInfo resultInfo; + + /** {@code non-null;} work set indicating blocks needing to be processed */ + private final BitSet workSet; + + /** + * Extracts out all the local variable information from the given method. + * + * @param method {@code non-null;} the method to extract from + * @return {@code non-null;} the extracted information + */ + public static LocalVariableInfo extract(SsaMethod method) { + LocalVariableExtractor lve = new LocalVariableExtractor(method); + return lve.doit(); + } + + /** + * Constructs an instance. This method is private. Use {@link #extract}. + * + * @param method {@code non-null;} the method to extract from + */ + private LocalVariableExtractor(SsaMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + ArrayList<SsaBasicBlock> blocks = method.getBlocks(); + + this.method = method; + this.blocks = blocks; + this.resultInfo = new LocalVariableInfo(method); + this.workSet = new BitSet(blocks.size()); + } + + /** + * Does the extraction. + * + * @return {@code non-null;} the extracted information + */ + private LocalVariableInfo doit() { + + //FIXME why is this needed here? + if (method.getRegCount() > 0 ) { + for (int bi = method.getEntryBlockIndex(); + bi >= 0; + bi = workSet.nextSetBit(0)) { + workSet.clear(bi); + processBlock(bi); + } + } + + resultInfo.setImmutable(); + return resultInfo; + } + + /** + * Processes a single block. + * + * @param blockIndex {@code >= 0;} block index of the block to process + */ + private void processBlock(int blockIndex) { + RegisterSpecSet primaryState + = resultInfo.mutableCopyOfStarts(blockIndex); + SsaBasicBlock block = blocks.get(blockIndex); + List<SsaInsn> insns = block.getInsns(); + int insnSz = insns.size(); + + // The exit block has no insns and no successors + if (blockIndex == method.getExitBlockIndex()) { + return; + } + + /* + * We may have to treat the last instruction specially: If it + * can (but doesn't always) throw, and the exception can be + * caught within the same method, then we need to use the + * state *before* executing it to be what is merged into + * exception targets. + */ + SsaInsn lastInsn = insns.get(insnSz - 1); + boolean hasExceptionHandlers + = lastInsn.getOriginalRopInsn().getCatches().size() !=0 ; + boolean canThrowDuringLastInsn = hasExceptionHandlers + && (lastInsn.getResult() != null); + int freezeSecondaryStateAt = insnSz - 1; + RegisterSpecSet secondaryState = primaryState; + + /* + * Iterate over the instructions, adding information for each place + * that the active variable set changes. + */ + + for (int i = 0; i < insnSz; i++) { + if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) { + // Until this point, primaryState == secondaryState. + primaryState.setImmutable(); + primaryState = primaryState.mutableCopy(); + } + + SsaInsn insn = insns.get(i); + RegisterSpec result; + + result = insn.getLocalAssignment(); + + if (result == null) { + // We may be nuking an existing local + + result = insn.getResult(); + + if (result != null && primaryState.get(result.getReg()) != null) { + primaryState.remove(primaryState.get(result.getReg())); + } + continue; + } + + result = result.withSimpleType(); + + RegisterSpec already = primaryState.get(result); + /* + * The equals() check ensures we only add new info if + * the instruction causes a change to the set of + * active variables. + */ + if (!result.equals(already)) { + /* + * If this insn represents a local moving from one register + * to another, remove the association between the old register + * and the local. + */ + RegisterSpec previous + = primaryState.localItemToSpec(result.getLocalItem()); + + if (previous != null + && (previous.getReg() != result.getReg())) { + + primaryState.remove(previous); + } + + resultInfo.addAssignment(insn, result); + primaryState.put(result); + } + } + + primaryState.setImmutable(); + + /* + * Merge this state into the start state for each successor, + * and update the work set where required (that is, in cases + * where the start state for a block changes). + */ + + IntList successors = block.getSuccessorList(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessorIndex(); + + for (int i = 0; i < succSz; i++) { + int succ = successors.get(i); + RegisterSpecSet state = (succ == primarySuccessor) ? + primaryState : secondaryState; + + if (resultInfo.mergeStarts(succ, state)) { + workSet.set(succ); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java b/dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java new file mode 100644 index 00000000..0ff957c0 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/LocalVariableInfo.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecSet; +import com.android.jack.dx.util.MutabilityControl; + +import java.util.HashMap; +import java.util.List; + +/** + * Container for local variable information for a particular {@link + * com.android.jack.dx.ssa.SsaMethod}. + * Stolen from {@link com.android.jack.dx.rop.code.LocalVariableInfo}. + */ +public class LocalVariableInfo extends MutabilityControl { + /** {@code >= 0;} the register count for the method */ + private final int regCount; + + /** + * {@code non-null;} {@link com.android.jack.dx.rop.code.RegisterSpecSet} to use when indicating a block + * that has no locals; it is empty and immutable but has an appropriate + * max size for the method + */ + private final RegisterSpecSet emptySet; + + /** + * {@code non-null;} array consisting of register sets representing the + * sets of variables already assigned upon entry to each block, + * where array indices correspond to block indices + */ + private final RegisterSpecSet[] blockStarts; + + /** {@code non-null;} map from instructions to the variable each assigns */ + private final HashMap<SsaInsn, RegisterSpec> insnAssignments; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method being represented by this instance + */ + public LocalVariableInfo(SsaMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + List<SsaBasicBlock> blocks = method.getBlocks(); + + this.regCount = method.getRegCount(); + this.emptySet = new RegisterSpecSet(regCount); + this.blockStarts = new RegisterSpecSet[blocks.size()]; + this.insnAssignments = + new HashMap<SsaInsn, RegisterSpec>(/*hint here*/); + + emptySet.setImmutable(); + } + + /** + * Sets the register set associated with the start of the block with + * the given index. + * + * @param index {@code >= 0;} the block index + * @param specs {@code non-null;} the register set to associate with the block + */ + public void setStarts(int index, RegisterSpecSet specs) { + throwIfImmutable(); + + if (specs == null) { + throw new NullPointerException("specs == null"); + } + + try { + blockStarts[index] = specs; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus index"); + } + } + + /** + * Merges the given register set into the set for the block with the + * given index. If there was not already an associated set, then this + * is the same as calling {@link #setStarts}. Otherwise, this will + * merge the two sets and call {@link #setStarts} on the result of the + * merge. + * + * @param index {@code >= 0;} the block index + * @param specs {@code non-null;} the register set to merge into the start set + * for the block + * @return {@code true} if the merge resulted in an actual change + * to the associated set (including storing one for the first time) or + * {@code false} if there was no change + */ + public boolean mergeStarts(int index, RegisterSpecSet specs) { + RegisterSpecSet start = getStarts0(index); + boolean changed = false; + + if (start == null) { + setStarts(index, specs); + return true; + } + + RegisterSpecSet newStart = start.mutableCopy(); + newStart.intersect(specs, true); + + if (start.equals(newStart)) { + return false; + } + + newStart.setImmutable(); + setStarts(index, newStart); + + return true; + } + + /** + * Gets the register set associated with the start of the block + * with the given index. This returns an empty set with the appropriate + * max size if no set was associated with the block in question. + * + * @param index {@code >= 0;} the block index + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(int index) { + RegisterSpecSet result = getStarts0(index); + + return (result != null) ? result : emptySet; + } + + /** + * Gets the register set associated with the start of the given + * block. This is just convenient shorthand for + * {@code getStarts(block.getLabel())}. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(SsaBasicBlock block) { + return getStarts(block.getIndex()); + } + + /** + * Gets a mutable copy of the register set associated with the + * start of the block with the given index. This returns a + * newly-allocated empty {@link RegisterSpecSet} of appropriate + * max size if there is not yet any set associated with the block. + * + * @param index {@code >= 0;} the block index + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet mutableCopyOfStarts(int index) { + RegisterSpecSet result = getStarts0(index); + + return (result != null) ? + result.mutableCopy() : new RegisterSpecSet(regCount); + } + + /** + * Adds an assignment association for the given instruction and + * register spec. This throws an exception if the instruction + * doesn't actually perform a named variable assignment. + * + * <b>Note:</b> Although the instruction contains its own spec for + * the result, it still needs to be passed in explicitly to this + * method, since the spec that is stored here should always have a + * simple type and the one in the instruction can be an arbitrary + * {@link com.android.jack.dx.rop.type.TypeBearer} (such as a constant value). + * + * @param insn {@code non-null;} the instruction in question + * @param spec {@code non-null;} the associated register spec + */ + public void addAssignment(SsaInsn insn, RegisterSpec spec) { + throwIfImmutable(); + + if (insn == null) { + throw new NullPointerException("insn == null"); + } + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + insnAssignments.put(insn, spec); + } + + /** + * Gets the named register being assigned by the given instruction, if + * previously stored in this instance. + * + * @param insn {@code non-null;} instruction in question + * @return {@code null-ok;} the named register being assigned, if any + */ + public RegisterSpec getAssignment(SsaInsn insn) { + return insnAssignments.get(insn); + } + + /** + * Gets the number of assignments recorded by this instance. + * + * @return {@code >= 0;} the number of assignments + */ + public int getAssignmentCount() { + return insnAssignments.size(); + } + + public void debugDump() { + for (int index = 0 ; index < blockStarts.length; index++) { + if (blockStarts[index] == null) { + continue; + } + + if (blockStarts[index] == emptySet) { + System.out.printf("%04x: empty set\n", index); + } else { + System.out.printf("%04x: %s\n", index, blockStarts[index]); + } + } + } + + /** + * Helper method, to get the starts for a index, throwing the + * right exception for range problems. + * + * @param index {@code >= 0;} the block index + * @return {@code null-ok;} associated register set or {@code null} if there + * is none + */ + private RegisterSpecSet getStarts0(int index) { + try { + return blockStarts[index]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus index"); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java b/dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java new file mode 100644 index 00000000..dc05a6e4 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/MoveParamCombiner.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.cst.CstInteger; + +import java.util.HashSet; +import java.util.ArrayList; +import java.util.List; + +/** + * Combine identical move-param insns, which may result from Ropper's + * handling of synchronized methods. + */ +public class MoveParamCombiner { + + /** method to process */ + private final SsaMethod ssaMeth; + + /** + * Processes a method with this optimization step. + * + * @param ssaMethod method to process + */ + public static void process(SsaMethod ssaMethod) { + new MoveParamCombiner(ssaMethod).run(); + } + + private MoveParamCombiner(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + } + + /** + * Runs this optimization step. + */ + private void run() { + // This will contain the definition specs for each parameter + final RegisterSpec[] paramSpecs + = new RegisterSpec[ssaMeth.getParamWidth()]; + + // Insns to delete when all done + final HashSet<SsaInsn> deletedInsns = new HashSet(); + + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn (NormalSsaInsn insn) { + } + public void visitPhiInsn (PhiInsn phi) { + } + public void visitNonMoveInsn (NormalSsaInsn insn) { + if (insn.getOpcode().getOpcode() != RegOps.MOVE_PARAM) { + return; + } + + int param = getParamIndex(insn); + + if (paramSpecs[param] == null) { + paramSpecs[param] = insn.getResult(); + } else { + final RegisterSpec specA = paramSpecs[param]; + final RegisterSpec specB = insn.getResult(); + LocalItem localA = specA.getLocalItem(); + LocalItem localB = specB.getLocalItem(); + LocalItem newLocal; + + /* + * Is there local information to preserve? + */ + + if (localA == null) { + newLocal = localB; + } else if (localB == null) { + newLocal = localA; + } else if (localA.equals(localB)) { + newLocal = localA; + } else { + /* + * Oddly, these two identical move-params have distinct + * debug info. We'll just keep them distinct. + */ + return; + } + + ssaMeth.getDefinitionForRegister(specA.getReg()) + .setResultLocal(newLocal); + + /* + * Map all uses of specB to specA + */ + + RegisterMapper mapper = new RegisterMapper() { + /** @inheritDoc */ + public int getNewRegisterCount() { + return ssaMeth.getRegCount(); + } + + /** @inheritDoc */ + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec.getReg() == specB.getReg()) { + return specA; + } + + return registerSpec; + } + }; + + List<SsaInsn> uses + = ssaMeth.getUseListForRegister(specB.getReg()); + + // Use list is modified by mapSourceRegisters + for (int i = uses.size() - 1; i >= 0; i--) { + SsaInsn use = uses.get(i); + use.mapSourceRegisters(mapper); + } + + deletedInsns.add(insn); + } + + } + }); + + ssaMeth.deleteInsns(deletedInsns); + } + + /** + * Returns the parameter index associated with a move-param insn. Does + * not verify that the insn is a move-param insn. + * + * @param insn {@code non-null;} a move-param insn + * @return {@code >=0;} parameter index + */ + private int getParamIndex(NormalSsaInsn insn) { + CstInsn cstInsn = (CstInsn)(insn.getOriginalRopInsn()); + + int param = ((CstInteger)cstInsn.getConstant()).getValue(); + return param; + } + +} diff --git a/dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java b/dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java new file mode 100644 index 00000000..c6695664 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/NormalSsaInsn.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.*; + +/** + * A "normal" (non-phi) instruction in SSA form. Always wraps a rop insn. + */ +public final class NormalSsaInsn extends SsaInsn implements Cloneable { + /** {@code non-null;} rop insn that we're wrapping */ + private Insn insn; + + /** + * Creates an instance. + * + * @param insn Rop insn to wrap + * @param block block that contains this insn + */ + NormalSsaInsn(final Insn insn, final SsaBasicBlock block) { + super(insn.getResult(), block); + this.insn = insn; + } + + /** {@inheritDoc} */ + @Override + public final void mapSourceRegisters(RegisterMapper mapper) { + RegisterSpecList oldSources = insn.getSources(); + RegisterSpecList newSources = mapper.map(oldSources); + + if (newSources != oldSources) { + insn = insn.withNewRegisters(getResult(), newSources); + getBlock().getParent().onSourcesChanged(this, oldSources); + } + } + + /** + * Changes one of the insn's sources. New source should be of same type + * and category. + * + * @param index {@code >=0;} index of source to change + * @param newSpec spec for new source + */ + public final void changeOneSource(int index, RegisterSpec newSpec) { + RegisterSpecList origSources = insn.getSources(); + int sz = origSources.size(); + RegisterSpecList newSources = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + newSources.set(i, i == index ? newSpec : origSources.get(i)); + } + + newSources.setImmutable(); + + RegisterSpec origSpec = origSources.get(index); + if (origSpec.getReg() != newSpec.getReg()) { + /* + * If the register remains unchanged, we're only changing + * the type or local var name so don't update use list + */ + getBlock().getParent().onSourceChanged(this, origSpec, newSpec); + } + + insn = insn.withNewRegisters(getResult(), newSources); + } + + /** + * Changes the source list of the insn. New source list should be the + * same size and consist of sources of identical types. + * + * @param newSources non-null new sources list. + */ + public final void setNewSources (RegisterSpecList newSources) { + RegisterSpecList origSources = insn.getSources(); + + if (origSources.size() != newSources.size()) { + throw new RuntimeException("Sources counts don't match"); + } + + insn = insn.withNewRegisters(getResult(), newSources); + } + + /** {@inheritDoc} */ + @Override + public NormalSsaInsn clone() { + return (NormalSsaInsn) super.clone(); + } + + /** + * Like rop.Insn.getSources(). + * + * @return {@code null-ok;} sources list + */ + @Override + public RegisterSpecList getSources() { + return insn.getSources(); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toRopInsn().toHuman(); + } + + /** {@inheritDoc} */ + @Override + public Insn toRopInsn() { + return insn.withNewRegisters(getResult(), insn.getSources()); + } + + /** + * @return the Rop opcode for this insn + */ + @Override + public Rop getOpcode() { + return insn.getOpcode(); + } + + /** {@inheritDoc} */ + @Override + public Insn getOriginalRopInsn() { + return insn; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec getLocalAssignment() { + RegisterSpec assignment; + + if (insn.getOpcode().getOpcode() == RegOps.MARK_LOCAL) { + assignment = insn.getSources().get(0); + } else { + assignment = getResult(); + } + + if (assignment == null) { + return null; + } + + LocalItem local = assignment.getLocalItem(); + + if (local == null) { + return null; + } + + return assignment; + } + + /** + * Upgrades this insn to a version that represents the constant source + * literally. If the upgrade is not possible, this does nothing. + * + * @see Insn#withSourceLiteral + */ + public void upgradeToLiteral() { + RegisterSpecList oldSources = insn.getSources(); + + insn = insn.withSourceLiteral(); + getBlock().getParent().onSourcesChanged(this, oldSources); + } + + /** + * @return true if this is a move (but not a move-operand) instruction + */ + @Override + public boolean isNormalMoveInsn() { + return insn.getOpcode().getOpcode() == RegOps.MOVE; + } + + /** {@inheritDoc} */ + @Override + public boolean isMoveException() { + return insn.getOpcode().getOpcode() == RegOps.MOVE_EXCEPTION; + } + + /** {@inheritDoc} */ + @Override + public boolean canThrow() { + return insn.canThrow(); + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor v) { + if (isNormalMoveInsn()) { + v.visitMoveInsn(this); + } else { + v.visitNonMoveInsn(this); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isPhiOrMove() { + return isNormalMoveInsn(); + } + + /** + * {@inheritDoc} + * + * TODO: Increase the scope of this. + */ + @Override + public boolean hasSideEffect() { + Rop opcode = getOpcode(); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + return true; + } + + boolean hasLocalSideEffect + = Optimizer.getPreserveLocals() && getLocalAssignment() != null; + + switch (opcode.getOpcode()) { + case RegOps.MOVE_RESULT: + case RegOps.MOVE: + case RegOps.CONST: + return hasLocalSideEffect; + default: + return true; + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/Optimizer.java b/dx/src/com/android/jack/dx/ssa/Optimizer.java new file mode 100644 index 00000000..799b7042 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/Optimizer.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.TranslationAdvice; +import com.android.jack.dx.ssa.back.LivenessAnalyzer; +import com.android.jack.dx.ssa.back.SsaToRop; + +import java.util.EnumSet; + +/** + * Runs a method through the SSA form conversion, any optimization algorithms, + * and returns it to rop form. + */ +public class Optimizer { + private static boolean preserveLocals = true; + + private static TranslationAdvice advice; + + /** optional optimizer steps */ + public enum OptionalStep { + MOVE_PARAM_COMBINER, SCCP, LITERAL_UPGRADE, CONST_COLLECTOR, + ESCAPE_ANALYSIS + } + + /** + * @return true if local variable information should be preserved, even + * at code size/register size cost + */ + public static boolean getPreserveLocals() { + return preserveLocals; + } + + /** + * @return {@code non-null;} translation advice + */ + public static TranslationAdvice getAdvice() { + return advice; + } + + /** + * Runs optimization algorthims over this method, and returns a new + * instance of RopMethod with the changes. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param inPreserveLocals true if local variable info should be preserved, + * at the cost of some registers and insns + * @param inAdvice {@code non-null;} translation advice + * @return optimized method + */ + public static RopMethod optimize(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + return optimize(rmeth, paramWidth, isStatic, inPreserveLocals, inAdvice, + EnumSet.allOf(OptionalStep.class)); + } + + /** + * Runs optimization algorthims over this method, and returns a new + * instance of RopMethod with the changes. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param inPreserveLocals true if local variable info should be preserved, + * at the cost of some registers and insns + * @param inAdvice {@code non-null;} translation advice + * @param steps set of optional optimization steps to run + * @return optimized method + */ + public static RopMethod optimize(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice, EnumSet<OptionalStep> steps) { + SsaMethod ssaMeth = null; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + runSsaFormSteps(ssaMeth, steps); + + RopMethod resultMeth = SsaToRop.convertToRopMethod(ssaMeth, false); + + if (resultMeth.getBlocks().getRegCount() + > advice.getMaxOptimalRegisterCount()) { + // Try to see if we can squeeze it under the register count bar + resultMeth = optimizeMinimizeRegisters(rmeth, paramWidth, isStatic, + steps); + } + return resultMeth; + } + + /** + * Runs the optimizer with a strategy to minimize the number of rop-form + * registers used by the end result. Dex bytecode does not have instruction + * forms that take register numbers larger than 15 for all instructions. + * If we've produced a method that uses more than 16 registers, try again + * with a different strategy to see if we can get under the bar. The end + * result will be much more efficient. + * + * @param rmeth method to process + * @param paramWidth the total width, in register-units, of this method's + * parameters + * @param isStatic true if this method has no 'this' pointer argument. + * @param steps set of optional optimization steps to run + * @return optimized method + */ + private static RopMethod optimizeMinimizeRegisters(RopMethod rmeth, + int paramWidth, boolean isStatic, + EnumSet<OptionalStep> steps) { + SsaMethod ssaMeth; + RopMethod resultMeth; + + ssaMeth = SsaConverter.convertToSsaMethod( + rmeth, paramWidth, isStatic); + + EnumSet<OptionalStep> newSteps = steps.clone(); + + /* + * CONST_COLLECTOR trades insns for registers, which is not an + * appropriate strategy here. + */ + newSteps.remove(OptionalStep.CONST_COLLECTOR); + + runSsaFormSteps(ssaMeth, newSteps); + + resultMeth = SsaToRop.convertToRopMethod(ssaMeth, true); + return resultMeth; + } + + private static void runSsaFormSteps(SsaMethod ssaMeth, + EnumSet<OptionalStep> steps) { + boolean needsDeadCodeRemover = true; + + if (steps.contains(OptionalStep.MOVE_PARAM_COMBINER)) { + MoveParamCombiner.process(ssaMeth); + } + + if (steps.contains(OptionalStep.SCCP)) { + SCCP.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + if (steps.contains(OptionalStep.LITERAL_UPGRADE)) { + LiteralOpUpgrader.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + /* + * ESCAPE_ANALYSIS impacts debuggability, so left off by default + */ + steps.remove(OptionalStep.ESCAPE_ANALYSIS); + if (steps.contains(OptionalStep.ESCAPE_ANALYSIS)) { + EscapeAnalysis.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + if (steps.contains(OptionalStep.CONST_COLLECTOR)) { + ConstCollector.process(ssaMeth); + DeadCodeRemover.process(ssaMeth); + needsDeadCodeRemover = false; + } + + // dead code remover must be run before phi type resolver + if (needsDeadCodeRemover) { + DeadCodeRemover.process(ssaMeth); + } + + PhiTypeResolver.process(ssaMeth); + } + + public static SsaMethod debugEdgeSplit(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.testEdgeSplit(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugPhiPlacement(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.testPhiPlacement(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugRenaming(RopMethod rmeth, int paramWidth, + boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + return SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + } + + public static SsaMethod debugDeadCodeRemover(RopMethod rmeth, + int paramWidth, boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice) { + + SsaMethod ssaMeth; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + DeadCodeRemover.process(ssaMeth); + + return ssaMeth; + } + + public static SsaMethod debugNoRegisterAllocation(RopMethod rmeth, + int paramWidth, boolean isStatic, boolean inPreserveLocals, + TranslationAdvice inAdvice, EnumSet<OptionalStep> steps) { + + SsaMethod ssaMeth; + + preserveLocals = inPreserveLocals; + advice = inAdvice; + + ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic); + + runSsaFormSteps(ssaMeth, steps); + + LivenessAnalyzer.constructInterferenceGraph(ssaMeth); + + return ssaMeth; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/PhiInsn.java b/dx/src/com/android/jack/dx/ssa/PhiInsn.java new file mode 100644 index 00000000..9372305d --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/PhiInsn.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.*; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; +import com.android.jack.dx.util.Hex; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Phi instruction (magical post-control-flow-merge) instruction + * in SSA form. Will be converted to moves in predecessor blocks before + * conversion back to ROP form. + */ +public final class PhiInsn extends SsaInsn { + /** + * result register. The original result register of the phi insn + * is needed during the renaming process after the new result + * register has already been chosen. + */ + private final int ropResultReg; + + /** + * {@code non-null;} operands of the instruction; built up by + * {@link #addPhiOperand} + */ + private final ArrayList<Operand> operands = new ArrayList<Operand>(); + + /** {@code null-ok;} source registers; constructed lazily */ + private RegisterSpecList sources; + + /** + * Constructs a new phi insn with no operands. + * + * @param resultReg the result reg for this phi insn + * @param block block containing this insn. + */ + public PhiInsn(RegisterSpec resultReg, SsaBasicBlock block) { + super(resultReg, block); + ropResultReg = resultReg.getReg(); + } + + /** + * Makes a phi insn with a void result type. + * + * @param resultReg the result register for this phi insn. + * @param block block containing this insn. + */ + public PhiInsn(final int resultReg, final SsaBasicBlock block) { + /* + * The result type here is bogus: The type depends on the + * operand and will be derived later. + */ + super(RegisterSpec.make(resultReg, Type.VOID), block); + ropResultReg = resultReg; + } + + /** {@inheritDoc} */ + @Override + public PhiInsn clone() { + throw new UnsupportedOperationException("can't clone phi"); + } + + /** + * Updates the TypeBearers of all the sources (phi operands) to be + * the current TypeBearer of the register-defining instruction's result. + * This is used during phi-type resolution.<p> + * + * Note that local association of operands are preserved in this step. + * + * @param ssaMeth method that contains this insn + */ + public void updateSourcesToDefinitions(SsaMethod ssaMeth) { + for (Operand o : operands) { + RegisterSpec def + = ssaMeth.getDefinitionForRegister( + o.regSpec.getReg()).getResult(); + + o.regSpec = o.regSpec.withType(def.getType()); + } + + sources = null; + } + + /** + * Changes the result type. Used during phi type resolution + * + * @param type {@code non-null;} new TypeBearer + * @param local {@code null-ok;} new local info, if available + */ + public void changeResultType(TypeBearer type, LocalItem local) { + setResult(RegisterSpec.makeLocalOptional( + getResult().getReg(), type, local)); + } + + /** + * Gets the original rop-form result reg. This is useful during renaming. + * + * @return the original rop-form result reg + */ + public int getRopResultReg() { + return ropResultReg; + } + + /** + * Adds an operand to this phi instruction. + * + * @param registerSpec register spec, including type and reg of operand + * @param predBlock predecessor block to be associated with this operand + */ + public void addPhiOperand(RegisterSpec registerSpec, + SsaBasicBlock predBlock) { + operands.add(new Operand(registerSpec, predBlock.getIndex(), + predBlock.getRopLabel())); + + // Un-cache sources, in case someone has already called getSources(). + sources = null; + } + + /** + * Removes all operand uses of a register from this phi instruction. + * + * @param registerSpec register spec, including type and reg of operand + */ + public void removePhiRegister(RegisterSpec registerSpec) { + ArrayList<Operand> operandsToRemove = new ArrayList<Operand>(); + for (Operand o : operands) { + if (o.regSpec.getReg() == registerSpec.getReg()) { + operandsToRemove.add(o); + } + } + + operands.removeAll(operandsToRemove); + + // Un-cache sources, in case someone has already called getSources(). + sources = null; + } + + /** + * Gets the index of the pred block associated with the RegisterSpec + * at the particular getSources() index. + * + * @param sourcesIndex index of source in getSources() + * @return block index + */ + public int predBlockIndexForSourcesIndex(int sourcesIndex) { + return operands.get(sourcesIndex).blockIndex; + } + + /** + * {@inheritDoc} + * + * Always returns null for {@code PhiInsn}s. + */ + @Override + public Rop getOpcode() { + return null; + } + + /** + * {@inheritDoc} + * + * Always returns null for {@code PhiInsn}s. + */ + @Override + public Insn getOriginalRopInsn() { + return null; + } + + /** + * {@inheritDoc} + * + * Always returns false for {@code PhiInsn}s. + */ + @Override + public boolean canThrow() { + return false; + } + + /** + * Gets sources. Constructed lazily from phi operand data structures and + * then cached. + * + * @return {@code non-null;} sources list + */ + @Override + public RegisterSpecList getSources() { + if (sources != null) { + return sources; + } + + if (operands.size() == 0) { + // How'd this happen? A phi insn with no operand? + return RegisterSpecList.EMPTY; + } + + int szSources = operands.size(); + sources = new RegisterSpecList(szSources); + + for (int i = 0; i < szSources; i++) { + Operand o = operands.get(i); + + sources.set(i, o.regSpec); + } + + sources.setImmutable(); + return sources; + } + + /** {@inheritDoc} */ + @Override + public boolean isRegASource(int reg) { + /* + * Avoid creating a sources list in case it has not already been + * created. + */ + + for (Operand o : operands) { + if (o.regSpec.getReg() == reg) { + return true; + } + } + + return false; + } + + /** + * @return true if all operands use the same register + */ + public boolean areAllOperandsEqual() { + if (operands.size() == 0 ) { + // This should never happen. + return true; + } + + int firstReg = operands.get(0).regSpec.getReg(); + for (Operand o : operands) { + if (firstReg != o.regSpec.getReg()) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void mapSourceRegisters(RegisterMapper mapper) { + for (Operand o : operands) { + RegisterSpec old = o.regSpec; + o.regSpec = mapper.map(old); + if (old != o.regSpec) { + getBlock().getParent().onSourceChanged(this, old, o.regSpec); + } + } + sources = null; + } + + /** + * Always throws an exeption, since a phi insn may not be + * converted back to rop form. + * + * @return always throws exception + */ + @Override + public Insn toRopInsn() { + throw new IllegalArgumentException( + "Cannot convert phi insns to rop form"); + } + + /** + * Returns the list of predecessor blocks associated with all operands + * that have {@code reg} as an operand register. + * + * @param reg register to look up + * @param ssaMeth method we're operating on + * @return list of predecessor blocks, empty if none + */ + public List<SsaBasicBlock> predBlocksForReg(int reg, SsaMethod ssaMeth) { + ArrayList<SsaBasicBlock> ret = new ArrayList<SsaBasicBlock>(); + + for (Operand o : operands) { + if (o.regSpec.getReg() == reg) { + ret.add(ssaMeth.getBlocks().get(o.blockIndex)); + } + } + + return ret; + } + + /** {@inheritDoc} */ + @Override + public boolean isPhiOrMove() { + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean hasSideEffect() { + return Optimizer.getPreserveLocals() && getLocalAssignment() != null; + } + + /** {@inheritDoc} */ + @Override + public void accept(SsaInsn.Visitor v) { + v.visitPhiInsn(this); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toHumanWithInline(null); + } + + /** + * Returns human-readable string for listing dumps. This method + * allows sub-classes to specify extra text. + * + * @param extra {@code null-ok;} the argument to print after the opcode + * @return human-readable string for listing dumps + */ + protected final String toHumanWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append(SourcePosition.NO_INFO); + sb.append(": phi"); + + if (extra != null) { + sb.append("("); + sb.append(extra); + sb.append(")"); + } + + RegisterSpec result = getResult(); + + if (result == null) { + sb.append(" ."); + } else { + sb.append(" "); + sb.append(result.toHuman()); + } + + sb.append(" <-"); + + int sz = getSources().size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(sources.get(i).toHuman() + + "[b=" + + Hex.u2(operands.get(i).ropLabel) + "]"); + } + } + + return sb.toString(); + } + + /** + * A single phi operand, consiting of source register and block index + * for move. + */ + private static class Operand { + public RegisterSpec regSpec; + public final int blockIndex; + public final int ropLabel; // only used for debugging + + public Operand(RegisterSpec regSpec, int blockIndex, int ropLabel) { + this.regSpec = regSpec; + this.blockIndex = blockIndex; + this.ropLabel = ropLabel; + } + } + + /** + * Visitor interface for instances of this (outer) class. + */ + public static interface Visitor { + public void visitPhiInsn(PhiInsn insn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java b/dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java new file mode 100644 index 00000000..abc12534 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/PhiTypeResolver.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.BitSet; +import java.util.List; + +/** + * Resolves the result types of phi instructions. When phi instructions + * are inserted, their result types are set to BT_VOID (which is a nonsensical + * type for a register) but must be resolve to a real type before converting + * out of SSA form.<p> + * + * The resolve is done as an iterative merge of each phi's operand types. + * Phi operands may be themselves be the result of unresolved phis, + * and the algorithm tries to find the most-fit type (for example, if every + * operand is the same constant value or the same local variable info, we want + * that to be reflected).<p> + * + * This algorithm assumes a dead-code remover has already removed all + * circular-only phis that may have been inserted. + */ +public class PhiTypeResolver { + + SsaMethod ssaMeth; + /** indexed by register; all registers still defined by unresolved phis */ + private final BitSet worklist; + + /** + * Resolves all phi types in the method + * @param ssaMeth method to process + */ + public static void process (SsaMethod ssaMeth) { + new PhiTypeResolver(ssaMeth).run(); + } + + private PhiTypeResolver(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + worklist = new BitSet(ssaMeth.getRegCount()); + } + + /** + * Runs the phi-type resolver. + */ + private void run() { + + int regCount = ssaMeth.getRegCount(); + + for (int reg = 0; reg < regCount; reg++) { + SsaInsn definsn = ssaMeth.getDefinitionForRegister(reg); + + if (definsn != null + && (definsn.getResult().getBasicType() == Type.BT_VOID)) { + worklist.set(reg); + } + } + + int reg; + while ( 0 <= (reg = worklist.nextSetBit(0))) { + worklist.clear(reg); + + /* + * definitions on the worklist have a type of BT_VOID, which + * must have originated from a PhiInsn. + */ + PhiInsn definsn = (PhiInsn)ssaMeth.getDefinitionForRegister(reg); + + if (resolveResultType(definsn)) { + /* + * If the result type has changed, re-resolve all phis + * that use this. + */ + + List<SsaInsn> useList = ssaMeth.getUseListForRegister(reg); + + int sz = useList.size(); + for (int i = 0; i < sz; i++ ) { + SsaInsn useInsn = useList.get(i); + RegisterSpec resultReg = useInsn.getResult(); + if (resultReg != null && useInsn instanceof PhiInsn) { + worklist.set(resultReg.getReg()); + } + } + } + } + } + + /** + * Returns true if a and b are equal, whether + * or not either of them are null. + * @param a + * @param b + * @return true if equal + */ + private static boolean equalsHandlesNulls(LocalItem a, LocalItem b) { + return (a == b) || ((a != null) && a.equals(b)); + } + + /** + * Resolves the result of a phi insn based on its operands. The "void" + * type, which is a nonsensical type for a register, is used for + * registers defined by as-of-yet-unresolved phi operations. + * + * @return true if the result type changed, false if no change + */ + boolean resolveResultType(PhiInsn insn) { + insn.updateSourcesToDefinitions(ssaMeth); + + RegisterSpecList sources = insn.getSources(); + + // Start by finding the first non-void operand + RegisterSpec first = null; + int firstIndex = -1; + + int szSources = sources.size(); + for (int i = 0 ; i <szSources ; i++) { + RegisterSpec rs = sources.get(i); + + if (rs.getBasicType() != Type.BT_VOID) { + first = rs; + firstIndex = i; + } + } + + if (first == null) { + // All operands are void -- we're not ready to resolve yet + return false; + } + + LocalItem firstLocal = first.getLocalItem(); + TypeBearer mergedType = first.getType(); + boolean sameLocals = true; + for (int i = 0 ; i < szSources ; i++) { + if (i == firstIndex) { + continue; + } + + RegisterSpec rs = sources.get(i); + + // Just skip void (unresolved phi results) for now + if (rs.getBasicType() == Type.BT_VOID){ + continue; + } + + sameLocals = sameLocals + && equalsHandlesNulls(firstLocal, rs.getLocalItem()); + + mergedType = mergeType(mergedType, rs.getType()); + } + + TypeBearer newResultType; + + if (mergedType != null) { + newResultType = mergedType; + } else { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < szSources; i++) { + sb.append(sources.get(i).toString()); + sb.append(' '); + } + + throw new RuntimeException ("Couldn't map types in phi insn:" + sb); + } + + LocalItem newLocal = sameLocals ? firstLocal : null; + + RegisterSpec result = insn.getResult(); + + if ((result.getTypeBearer() == newResultType) + && equalsHandlesNulls(newLocal, result.getLocalItem())) { + return false; + } + + insn.changeResultType(newResultType, newLocal); + + return true; + } + + /** + * Merges two frame types. + * + * @param ft1 {@code non-null;} a frame type + * @param ft2 {@code non-null;} another frame type + * @return {@code non-null;} the result of merging the two types + */ + private static TypeBearer mergeType(TypeBearer ft1, TypeBearer ft2) { + if ((ft1 == null) || ft1.equals(ft2)) { + return ft1; + } else if (ft2 == null) { + return null; + } else { + Type type1 = ft1.getType(); + Type type2 = ft2.getType(); + + if (type1 == type2) { + return type1; + } else if (type1.isReference() && type2.isReference()) { + if (type1 == Type.KNOWN_NULL) { + /* + * A known-null merges with any other reference type to + * be that reference type. + */ + return type2; + } else if (type2 == Type.KNOWN_NULL) { + /* + * The same as above, but this time it's type2 that's + * the known-null. + */ + return type1; + } else if (type1.isArray() && type2.isArray()) { + TypeBearer componentUnion = + mergeType(type1.getComponentType(), + type2.getComponentType()); + if (componentUnion == null) { + /* + * At least one of the types is a primitive type, + * so the merged result is just Object. + */ + return Type.OBJECT; + } + return ((Type) componentUnion).getArrayType(); + } else { + /* + * All other unequal reference types get merged to be + * Object in this phase. This is fine here, but it + * won't be the right thing to do in the verifier. + */ + return Type.OBJECT; + } + } else if (type1.isIntlike() && type2.isIntlike()) { + /* + * Merging two non-identical int-like types results in + * the type int. + */ + return Type.INT; + } else { + return null; + } + } + } + +} diff --git a/dx/src/com/android/jack/dx/ssa/RegisterMapper.java b/dx/src/com/android/jack/dx/ssa/RegisterMapper.java new file mode 100644 index 00000000..300d956b --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/RegisterMapper.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.util.ToHuman; + +/** + * Represents a mapping between two register numbering schemes. + * Subclasses of this may be mutable, and as such the mapping provided + * is only valid for the lifetime of the method call in which + * instances of this class are passed. + */ +public abstract class RegisterMapper { + /** + * Gets the count of registers (really, the total register width, since + * category width is counted) in the new namespace. + * @return >= 0 width of new namespace. + */ + public abstract int getNewRegisterCount(); + + /** + * @param registerSpec old register + * @return register in new space + */ + public abstract RegisterSpec map(RegisterSpec registerSpec); + + /** + * + * @param sources old register list + * @return new mapped register list, or old if nothing has changed. + */ + public final RegisterSpecList map(RegisterSpecList sources) { + int sz = sources.size(); + RegisterSpecList newSources = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + newSources.set(i, map(sources.get(i))); + } + + newSources.setImmutable(); + + // Return the old sources if nothing has changed. + return newSources.equals(sources) ? sources : newSources; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SCCP.java b/dx/src/com/android/jack/dx/ssa/SCCP.java new file mode 100644 index 00000000..95753535 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SCCP.java @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.cst.Constant; +import com.android.jack.dx.rop.cst.CstBoolean; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.rop.cst.TypedConstant; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.rop.type.TypeBearer; + +import java.util.ArrayList; +import java.util.BitSet; + +/** + * A small variant of Wegman and Zadeck's Sparse Conditional Constant + * Propagation algorithm. + */ +public class SCCP { + /** Lattice values */ + private static final int TOP = 0; + private static final int CONSTANT = 1; + private static final int VARYING = 2; + /** method we're processing */ + private SsaMethod ssaMeth; + /** ssaMeth.getRegCount() */ + private int regCount; + /** Lattice values for each SSA register */ + private int[] latticeValues; + /** For those registers that are constant, this is the constant value */ + private Constant[] latticeConstants; + /** Worklist of basic blocks to be processed */ + private ArrayList<SsaBasicBlock> cfgWorklist; + /** Worklist of executed basic blocks with phis to be processed */ + private ArrayList<SsaBasicBlock> cfgPhiWorklist; + /** Bitset containing bits for each block that has been found executable */ + private BitSet executableBlocks; + /** Worklist for SSA edges. This is a list of registers to process */ + private ArrayList<SsaInsn> ssaWorklist; + /** + * Worklist for SSA edges that represent varying values. It makes the + * algorithm much faster if you move all values to VARYING as fast as + * possible. + */ + private ArrayList<SsaInsn> varyingWorklist; + /** Worklist of potential branches to convert to gotos */ + private ArrayList<SsaInsn> branchWorklist; + + private SCCP(SsaMethod ssaMeth) { + this.ssaMeth = ssaMeth; + this.regCount = ssaMeth.getRegCount(); + this.latticeValues = new int[this.regCount]; + this.latticeConstants = new Constant[this.regCount]; + this.cfgWorklist = new ArrayList<SsaBasicBlock>(); + this.cfgPhiWorklist = new ArrayList<SsaBasicBlock>(); + this.executableBlocks = new BitSet(ssaMeth.getBlocks().size()); + this.ssaWorklist = new ArrayList<SsaInsn>(); + this.varyingWorklist = new ArrayList<SsaInsn>(); + this.branchWorklist = new ArrayList<SsaInsn>(); + for (int i = 0; i < this.regCount; i++) { + latticeValues[i] = TOP; + latticeConstants[i] = null; + } + } + + /** + * Performs sparse conditional constant propagation on a method. + * @param ssaMethod Method to process + */ + public static void process (SsaMethod ssaMethod) { + new SCCP(ssaMethod).run(); + } + + /** + * Adds a SSA basic block to the CFG worklist if it's unexecuted, or + * to the CFG phi worklist if it's already executed. + * @param ssaBlock Block to add + */ + private void addBlockToWorklist(SsaBasicBlock ssaBlock) { + if (!executableBlocks.get(ssaBlock.getIndex())) { + cfgWorklist.add(ssaBlock); + executableBlocks.set(ssaBlock.getIndex()); + } else { + cfgPhiWorklist.add(ssaBlock); + } + } + + /** + * Adds an SSA register's uses to the SSA worklist. + * @param reg SSA register + * @param latticeValue new lattice value for @param reg. + */ + private void addUsersToWorklist(int reg, int latticeValue) { + if (latticeValue == VARYING) { + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + varyingWorklist.add(insn); + } + } else { + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + ssaWorklist.add(insn); + } + } + } + + /** + * Sets a lattice value for a register to value. + * @param reg SSA register + * @param value Lattice value + * @param cst Constant value (may be null) + * @return true if the lattice value changed. + */ + private boolean setLatticeValueTo(int reg, int value, Constant cst) { + if (value != CONSTANT) { + if (latticeValues[reg] != value) { + latticeValues[reg] = value; + return true; + } + return false; + } else { + if (latticeValues[reg] != value + || !latticeConstants[reg].equals(cst)) { + latticeValues[reg] = value; + latticeConstants[reg] = cst; + return true; + } + return false; + } + } + + /** + * Simulates a PHI node and set the lattice for the result + * to the appropriate value. + * Meet values: + * TOP x anything = TOP + * VARYING x anything = VARYING + * CONSTANT x CONSTANT = CONSTANT if equal constants, VARYING otherwise + * @param insn PHI to simulate. + */ + private void simulatePhi(PhiInsn insn) { + int phiResultReg = insn.getResult().getReg(); + + if (latticeValues[phiResultReg] == VARYING) { + return; + } + + RegisterSpecList sources = insn.getSources(); + int phiResultValue = TOP; + Constant phiConstant = null; + int sourceSize = sources.size(); + + for (int i = 0; i < sourceSize; i++) { + int predBlockIndex = insn.predBlockIndexForSourcesIndex(i); + int sourceReg = sources.get(i).getReg(); + int sourceRegValue = latticeValues[sourceReg]; + + if (!executableBlocks.get(predBlockIndex)) { + continue; + } + + if (sourceRegValue == CONSTANT) { + if (phiConstant == null) { + phiConstant = latticeConstants[sourceReg]; + phiResultValue = CONSTANT; + } else if (!latticeConstants[sourceReg].equals(phiConstant)){ + phiResultValue = VARYING; + break; + } + } else { + phiResultValue = sourceRegValue; + break; + } + } + if (setLatticeValueTo(phiResultReg, phiResultValue, phiConstant)) { + addUsersToWorklist(phiResultReg, phiResultValue); + } + } + + /** + * Simulate a block and note the results in the lattice. + * @param block Block to visit + */ + private void simulateBlock(SsaBasicBlock block) { + for (SsaInsn insn : block.getInsns()) { + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn) insn); + } else { + simulateStmt(insn); + } + } + } + + /** + * Simulate the phis in a block and note the results in the lattice. + * @param block Block to visit + */ + private void simulatePhiBlock(SsaBasicBlock block) { + for (SsaInsn insn : block.getInsns()) { + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn) insn); + } else { + return; + } + } + } + + private static String latticeValName(int latticeVal) { + switch (latticeVal) { + case TOP: return "TOP"; + case CONSTANT: return "CONSTANT"; + case VARYING: return "VARYING"; + default: return "UNKNOWN"; + } + } + + /** + * Simulates branch insns, if possible. Adds reachable successor blocks + * to the CFG worklists. + * @param insn branch to simulate + */ + private void simulateBranch(SsaInsn insn) { + Rop opcode = insn.getOpcode(); + RegisterSpecList sources = insn.getSources(); + + boolean constantBranch = false; + boolean constantSuccessor = false; + + // Check if the insn is a branch with a constant condition + if (opcode.getBranchingness() == Rop.BRANCH_IF) { + Constant cA = null; + Constant cB = null; + + RegisterSpec specA = sources.get(0); + int regA = specA.getReg(); + if (!ssaMeth.isRegALocal(specA) && + latticeValues[regA] == CONSTANT) { + cA = latticeConstants[regA]; + } + + if (sources.size() == 2) { + RegisterSpec specB = sources.get(1); + int regB = specB.getReg(); + if (!ssaMeth.isRegALocal(specB) && + latticeValues[regB] == CONSTANT) { + cB = latticeConstants[regB]; + } + } + + // Calculate the result of the condition + if (cA != null && sources.size() == 1) { + switch (((TypedConstant) cA).getBasicType()) { + case Type.BT_BOOLEAN: { + constantBranch = true; + boolean vA = ((CstBoolean) cA).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = !vA; + break; + case RegOps.IF_NE: + constantSuccessor = vA; + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + } + case Type.BT_INT: + constantBranch = true; + int vA = ((CstInteger) cA).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = (vA == 0); + break; + case RegOps.IF_NE: + constantSuccessor = (vA != 0); + break; + case RegOps.IF_LT: + constantSuccessor = (vA < 0); + break; + case RegOps.IF_GE: + constantSuccessor = (vA >= 0); + break; + case RegOps.IF_LE: + constantSuccessor = (vA <= 0); + break; + case RegOps.IF_GT: + constantSuccessor = (vA > 0); + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + default: + // not yet supported + } + } else if (cA != null && cB != null) { + switch (((TypedConstant) cA).getBasicType()) { + case Type.BT_INT: + constantBranch = true; + int vA = ((CstInteger) cA).getValue(); + int vB = ((CstInteger) cB).getValue(); + switch (opcode.getOpcode()) { + case RegOps.IF_EQ: + constantSuccessor = (vA == vB); + break; + case RegOps.IF_NE: + constantSuccessor = (vA != vB); + break; + case RegOps.IF_LT: + constantSuccessor = (vA < vB); + break; + case RegOps.IF_GE: + constantSuccessor = (vA >= vB); + break; + case RegOps.IF_LE: + constantSuccessor = (vA <= vB); + break; + case RegOps.IF_GT: + constantSuccessor = (vA > vB); + break; + default: + throw new RuntimeException("Unexpected op"); + } + break; + default: + // not yet supported + } + } + } + + /* + * If condition is constant, add only the target block to the + * worklist. Otherwise, add all successors to the worklist. + */ + SsaBasicBlock block = insn.getBlock(); + + if (constantBranch) { + int successorBlock; + if (constantSuccessor) { + successorBlock = block.getSuccessorList().get(1); + } else { + successorBlock = block.getSuccessorList().get(0); + } + addBlockToWorklist(ssaMeth.getBlocks().get(successorBlock)); + branchWorklist.add(insn); + } else { + for (int i = 0; i < block.getSuccessorList().size(); i++) { + int successorBlock = block.getSuccessorList().get(i); + addBlockToWorklist(ssaMeth.getBlocks().get(successorBlock)); + } + } + } + + /** + * Simulates math insns, if possible. + * + * @param insn non-null insn to simulate + * @param resultType basic type of the result + * @return constant result or null if not simulatable. + */ + private Constant simulateMath(SsaInsn insn, int resultType) { + Insn ropInsn = insn.getOriginalRopInsn(); + int opcode = insn.getOpcode().getOpcode(); + RegisterSpecList sources = insn.getSources(); + int regA = sources.get(0).getReg(); + Constant cA; + Constant cB; + + if (latticeValues[regA] != CONSTANT) { + cA = null; + } else { + cA = latticeConstants[regA]; + } + + if (sources.size() == 1) { + CstInsn cstInsn = (CstInsn) ropInsn; + cB = cstInsn.getConstant(); + } else { /* sources.size() == 2 */ + int regB = sources.get(1).getReg(); + if (latticeValues[regB] != CONSTANT) { + cB = null; + } else { + cB = latticeConstants[regB]; + } + } + + if (cA == null || cB == null) { + //TODO handle a constant of 0 with MUL or AND + return null; + } + + switch (resultType) { + case Type.BT_INT: + int vR; + boolean skip=false; + + int vA = ((CstInteger) cA).getValue(); + int vB = ((CstInteger) cB).getValue(); + + switch (opcode) { + case RegOps.ADD: + vR = vA + vB; + break; + case RegOps.SUB: + // 1 source for reverse sub, 2 sources for regular sub + if (sources.size() == 1) { + vR = vB - vA; + } else { + vR = vA - vB; + } + break; + case RegOps.MUL: + vR = vA * vB; + break; + case RegOps.DIV: + if (vB == 0) { + skip = true; + vR = 0; // just to hide a warning + } else { + vR = vA / vB; + } + break; + case RegOps.AND: + vR = vA & vB; + break; + case RegOps.OR: + vR = vA | vB; + break; + case RegOps.XOR: + vR = vA ^ vB; + break; + case RegOps.SHL: + vR = vA << vB; + break; + case RegOps.SHR: + vR = vA >> vB; + break; + case RegOps.USHR: + vR = vA >>> vB; + break; + case RegOps.REM: + if (vB == 0) { + skip = true; + vR = 0; // just to hide a warning + } else { + vR = vA % vB; + } + break; + default: + throw new RuntimeException("Unexpected op"); + } + + return skip ? null : CstInteger.make(vR); + + default: + // not yet supported + return null; + } + } + + /** + * Simulates a statement and set the result lattice value. + * @param insn instruction to simulate + */ + private void simulateStmt(SsaInsn insn) { + Insn ropInsn = insn.getOriginalRopInsn(); + if (ropInsn.getOpcode().getBranchingness() != Rop.BRANCH_NONE + || ropInsn.getOpcode().isCallLike()) { + simulateBranch(insn); + } + + int opcode = insn.getOpcode().getOpcode(); + RegisterSpec result = insn.getResult(); + + if (result == null) { + // Find move-result-pseudo result for int div and int rem + if (opcode == RegOps.DIV || opcode == RegOps.REM) { + SsaBasicBlock succ = insn.getBlock().getPrimarySuccessor(); + result = succ.getInsns().get(0).getResult(); + } else { + return; + } + } + + int resultReg = result.getReg(); + int resultValue = VARYING; + Constant resultConstant = null; + + switch (opcode) { + case RegOps.CONST: { + CstInsn cstInsn = (CstInsn)ropInsn; + resultValue = CONSTANT; + resultConstant = cstInsn.getConstant(); + break; + } + case RegOps.MOVE: { + if (insn.getSources().size() == 1) { + int sourceReg = insn.getSources().get(0).getReg(); + resultValue = latticeValues[sourceReg]; + resultConstant = latticeConstants[sourceReg]; + } + break; + } + case RegOps.ADD: + case RegOps.SUB: + case RegOps.MUL: + case RegOps.DIV: + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + case RegOps.SHL: + case RegOps.SHR: + case RegOps.USHR: + case RegOps.REM: { + resultConstant = simulateMath(insn, result.getBasicType()); + if (resultConstant != null) { + resultValue = CONSTANT; + } + break; + } + case RegOps.MOVE_RESULT_PSEUDO: { + if (latticeValues[resultReg] == CONSTANT) { + resultValue = latticeValues[resultReg]; + resultConstant = latticeConstants[resultReg]; + } + break; + } + // TODO: Handle non-int arithmetic. + // TODO: Eliminate check casts that we can prove the type of. + default: {} + } + if (setLatticeValueTo(resultReg, resultValue, resultConstant)) { + addUsersToWorklist(resultReg, resultValue); + } + } + + private void run() { + SsaBasicBlock firstBlock = ssaMeth.getEntryBlock(); + addBlockToWorklist(firstBlock); + + /* Empty all the worklists by propagating our values */ + while (!cfgWorklist.isEmpty() + || !cfgPhiWorklist.isEmpty() + || !ssaWorklist.isEmpty() + || !varyingWorklist.isEmpty()) { + while (!cfgWorklist.isEmpty()) { + int listSize = cfgWorklist.size() - 1; + SsaBasicBlock block = cfgWorklist.remove(listSize); + simulateBlock(block); + } + + while (!cfgPhiWorklist.isEmpty()) { + int listSize = cfgPhiWorklist.size() - 1; + SsaBasicBlock block = cfgPhiWorklist.remove(listSize); + simulatePhiBlock(block); + } + + while (!varyingWorklist.isEmpty()) { + int listSize = varyingWorklist.size() - 1; + SsaInsn insn = varyingWorklist.remove(listSize); + + if (!executableBlocks.get(insn.getBlock().getIndex())) { + continue; + } + + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn)insn); + } else { + simulateStmt(insn); + } + } + while (!ssaWorklist.isEmpty()) { + int listSize = ssaWorklist.size() - 1; + SsaInsn insn = ssaWorklist.remove(listSize); + + if (!executableBlocks.get(insn.getBlock().getIndex())) { + continue; + } + + if (insn instanceof PhiInsn) { + simulatePhi((PhiInsn)insn); + } else { + simulateStmt(insn); + } + } + } + + replaceConstants(); + replaceBranches(); + } + + /** + * Replaces TypeBearers in source register specs with constant type + * bearers if possible. These are then referenced in later optimization + * steps. + */ + private void replaceConstants() { + for (int reg = 0; reg < regCount; reg++) { + if (latticeValues[reg] != CONSTANT) { + continue; + } + if (!(latticeConstants[reg] instanceof TypedConstant)) { + // We can't do much with these + continue; + } + + SsaInsn defn = ssaMeth.getDefinitionForRegister(reg); + TypeBearer typeBearer = defn.getResult().getTypeBearer(); + + if (typeBearer.isConstant()) { + /* + * The definition was a constant already. + * The uses should be as well. + */ + continue; + } + + // Update the destination RegisterSpec with the constant value + RegisterSpec dest = defn.getResult(); + RegisterSpec newDest + = dest.withType((TypedConstant)latticeConstants[reg]); + defn.setResult(newDest); + + /* + * Update the sources RegisterSpec's of all non-move uses. + * These will be used in later steps. + */ + for (SsaInsn insn : ssaMeth.getUseListForRegister(reg)) { + if (insn.isPhiOrMove()) { + continue; + } + + NormalSsaInsn nInsn = (NormalSsaInsn) insn; + RegisterSpecList sources = insn.getSources(); + + int index = sources.indexOfRegister(reg); + + RegisterSpec spec = sources.get(index); + RegisterSpec newSpec + = spec.withType((TypedConstant)latticeConstants[reg]); + + nInsn.changeOneSource(index, newSpec); + } + } + } + + /** + * Replaces branches that have constant conditions with gotos + */ + private void replaceBranches() { + for (SsaInsn insn : branchWorklist) { + // Find if a successor block is never executed + int oldSuccessor = -1; + SsaBasicBlock block = insn.getBlock(); + int successorSize = block.getSuccessorList().size(); + for (int i = 0; i < successorSize; i++) { + int successorBlock = block.getSuccessorList().get(i); + if (!executableBlocks.get(successorBlock)) { + oldSuccessor = successorBlock; + } + } + + /* + * Prune branches that have already been handled and ones that no + * longer have constant conditions (no nonexecutable successors) + */ + if (successorSize != 2 || oldSuccessor == -1) continue; + + // Replace branch with goto + Insn originalRopInsn = insn.getOriginalRopInsn(); + block.replaceLastInsn(new PlainInsn(Rops.GOTO, + originalRopInsn.getPosition(), null, RegisterSpecList.EMPTY)); + block.removeSuccessor(oldSuccessor); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SetFactory.java b/dx/src/com/android/jack/dx/ssa/SetFactory.java new file mode 100644 index 00000000..9c24b8ba --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SetFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; +import com.android.jack.dx.util.ListIntSet; + + +/** + * Makes int sets for various parts of the optimizer. + */ +public final class SetFactory { + + /** + * BitIntSet/ListIntSet threshold for dominance frontier sets. These + * sets are kept per basic block until phi placement and tend to be, + * like the CFG itself, very sparse at large sizes. + * + * A value of 3072 here is somewhere around 1.125mb of total bitset size. + */ + private static final int DOMFRONT_SET_THRESHOLD_SIZE = 3072; + + /** + * BitIntSet/ListIntSet threshold for interference graph sets. These + * sets are kept per register until register allocation is done. + * + * A value of 3072 here is somewhere around 1.125mb of total bitset size. + */ + private static final int INTERFERENCE_SET_THRESHOLD_SIZE = 3072; + + /** + * BitIntSet/ListIntSet threshold for the live in/out sets kept by + * {@link SsaBasicBlock}. These are sets of SSA registers kept per basic + * block during register allocation. + * + * The total size of a bitset for this would be the count of blocks + * times the size of registers. The threshold value here is merely + * the register count, which is typically on the order of the block + * count as well. + */ + private static final int LIVENESS_SET_THRESHOLD_SIZE = 3072; + + + /** + * Make IntSet for the dominance-frontier sets. + * + * @param szBlocks {@code >=0;} count of basic blocks in method + * @return {@code non-null;} appropriate set + */ + /*package*/ static IntSet makeDomFrontSet(int szBlocks) { + return szBlocks <= DOMFRONT_SET_THRESHOLD_SIZE + ? new BitIntSet(szBlocks) + : new ListIntSet(); + } + + /** + * Make IntSet for the interference graph sets. Public because + * InterferenceGraph is in another package. + * + * @param countRegs {@code >=0;} count of SSA registers used in method + * @return {@code non-null;} appropriate set + */ + public static IntSet makeInterferenceSet(int countRegs) { + return countRegs <= INTERFERENCE_SET_THRESHOLD_SIZE + ? new BitIntSet(countRegs) + : new ListIntSet(); + } + + /** + * Make IntSet for register live in/out sets. + * + * @param countRegs {@code >=0;} count of SSA registers used in method + * @return {@code non-null;} appropriate set + */ + /*package*/ static IntSet makeLivenessSet(int countRegs) { + return countRegs <= LIVENESS_SET_THRESHOLD_SIZE + ? new BitIntSet(countRegs) + : new ListIntSet(); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java b/dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java new file mode 100644 index 00000000..4217184d --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaBasicBlock.java @@ -0,0 +1,1032 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.InsnList; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; +import com.android.jack.dx.util.IntSet; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * An SSA representation of a basic block. + */ +public final class SsaBasicBlock { + /** + * {@code non-null;} comparator for instances of this class that + * just compares block labels + */ + public static final Comparator<SsaBasicBlock> LABEL_COMPARATOR = + new LabelComparator(); + + /** {@code non-null;} insn list associated with this instance */ + private ArrayList<SsaInsn> insns; + + /** {@code non-null;} predecessor set (by block list index) */ + private BitSet predecessors; + + /** {@code non-null;} successor set (by block list index) */ + private BitSet successors; + + /** + * {@code non-null;} ordered successor list + * (same block may be listed more than once) + */ + private IntList successorList; + + /** + * block list index of primary successor, or {@code -1} for no primary + * successor + */ + private int primarySuccessor = -1; + + /** label of block in rop form */ + private int ropLabel; + + /** {@code non-null;} method we belong to */ + private SsaMethod parent; + + /** our index into parent.getBlock() */ + private int index; + + /** list of dom children */ + private final ArrayList<SsaBasicBlock> domChildren; + + /** + * the number of moves added to the end of the block during the + * phi-removal process. Retained for subsequent move scheduling. + */ + private int movesFromPhisAtEnd = 0; + + /** + * the number of moves added to the beginning of the block during the + * phi-removal process. Retained for subsequent move scheduling. + */ + private int movesFromPhisAtBeginning = 0; + + /** + * contains last computed value of reachability of this block, or -1 + * if reachability hasn't been calculated yet + */ + private int reachable = -1; + + /** + * {@code null-ok;} indexed by reg: the regs that are live-in at + * this block + */ + private IntSet liveIn; + + /** + * {@code null-ok;} indexed by reg: the regs that are live-out at + * this block + */ + private IntSet liveOut; + + /** + * Creates a new empty basic block. + * + * @param basicBlockIndex index this block will have + * @param ropLabel original rop-form label + * @param parent method of this block + */ + public SsaBasicBlock(final int basicBlockIndex, final int ropLabel, + final SsaMethod parent) { + this.parent = parent; + this.index = basicBlockIndex; + this.insns = new ArrayList<SsaInsn>(); + this.ropLabel = ropLabel; + + this.predecessors = new BitSet(parent.getBlocks().size()); + this.successors = new BitSet(parent.getBlocks().size()); + this.successorList = new IntList(); + + domChildren = new ArrayList<SsaBasicBlock>(); + } + + /** + * Creates a new SSA basic block from a ROP form basic block. + * + * @param rmeth original method + * @param basicBlockIndex index this block will have + * @param parent method of this block predecessor set will be + * updated + * @return new instance + */ + public static SsaBasicBlock newFromRop(RopMethod rmeth, + int basicBlockIndex, final SsaMethod parent) { + BasicBlockList ropBlocks = rmeth.getBlocks(); + BasicBlock bb = ropBlocks.get(basicBlockIndex); + SsaBasicBlock result = + new SsaBasicBlock(basicBlockIndex, bb.getLabel(), parent); + InsnList ropInsns = bb.getInsns(); + + result.insns.ensureCapacity(ropInsns.size()); + + for (int i = 0, sz = ropInsns.size() ; i < sz ; i++) { + result.insns.add(new NormalSsaInsn (ropInsns.get(i), result)); + } + + result.predecessors = SsaMethod.bitSetFromLabelList( + ropBlocks, + rmeth.labelToPredecessors(bb.getLabel())); + + result.successors + = SsaMethod.bitSetFromLabelList(ropBlocks, bb.getSuccessors()); + + result.successorList + = SsaMethod.indexListFromLabelList(ropBlocks, + bb.getSuccessors()); + + if (result.successorList.size() != 0) { + int primarySuccessor = bb.getPrimarySuccessor(); + + result.primarySuccessor = (primarySuccessor < 0) + ? -1 : ropBlocks.indexOfLabel(primarySuccessor); + } + + return result; + } + + /** + * Adds a basic block as a dom child for this block. Used when constructing + * the dom tree. + * + * @param child {@code non-null;} new dom child + */ + public void addDomChild(SsaBasicBlock child) { + domChildren.add(child); + } + + /** + * Gets the dom children for this node. Don't modify this list. + * + * @return {@code non-null;} list of dom children + */ + public ArrayList<SsaBasicBlock> getDomChildren() { + return domChildren; + } + + /** + * Adds a phi insn to the beginning of this block. The result type of + * the phi will be set to void, to indicate that it's currently unknown. + * + * @param reg {@code >=0;} result reg + */ + public void addPhiInsnForReg(int reg) { + insns.add(0, new PhiInsn(reg, this)); + } + + /** + * Adds a phi insn to the beginning of this block. This is to be used + * when the result type or local-association can be determined at phi + * insert time. + * + * @param resultSpec {@code non-null;} reg + */ + public void addPhiInsnForReg(RegisterSpec resultSpec) { + insns.add(0, new PhiInsn(resultSpec, this)); + } + + /** + * Adds an insn to the head of this basic block, just after any phi + * insns. + * + * @param insn {@code non-null;} rop-form insn to add + */ + public void addInsnToHead(Insn insn) { + SsaInsn newInsn = SsaInsn.makeFromRop(insn, this); + insns.add(getCountPhiInsns(), newInsn); + parent.onInsnAdded(newInsn); + } + + /** + * Replaces the last insn in this block. The provided insn must have + * some branchingness. + * + * @param insn {@code non-null;} rop-form insn to add, which must branch. + */ + public void replaceLastInsn(Insn insn) { + if (insn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) { + throw new IllegalArgumentException("last insn must branch"); + } + + SsaInsn oldInsn = insns.get(insns.size() - 1); + SsaInsn newInsn = SsaInsn.makeFromRop(insn, this); + + insns.set(insns.size() - 1, newInsn); + + parent.onInsnRemoved(oldInsn); + parent.onInsnAdded(newInsn); + } + + /** + * Visits each phi insn. + * + * @param v {@code non-null;} the callback + */ + public void forEachPhiInsn(PhiInsn.Visitor v) { + int sz = insns.size(); + + for (int i = 0; i < sz; i++) { + SsaInsn insn = insns.get(i); + if (insn instanceof PhiInsn) { + v.visitPhiInsn((PhiInsn) insn); + } else { + /* + * Presently we assume PhiInsn's are in a continuous + * block at the top of the list + */ + break; + } + } + } + + /** + * Deletes all phi insns. Do this after adding appropriate move insns. + */ + public void removeAllPhiInsns() { + /* + * Presently we assume PhiInsn's are in a continuous + * block at the top of the list. + */ + + insns.subList(0, getCountPhiInsns()).clear(); + } + + /** + * Gets the number of phi insns at the top of this basic block. + * + * @return count of phi insns + */ + private int getCountPhiInsns() { + int countPhiInsns; + + int sz = insns.size(); + for (countPhiInsns = 0; countPhiInsns < sz; countPhiInsns++) { + SsaInsn insn = insns.get(countPhiInsns); + if (!(insn instanceof PhiInsn)) { + break; + } + } + + return countPhiInsns; + } + + /** + * @return {@code non-null;} the (mutable) instruction list for this block, + * with phi insns at the beginning + */ + public ArrayList<SsaInsn> getInsns() { + return insns; + } + + /** + * @return {@code non-null;} the (mutable) list of phi insns for this block + */ + public List<SsaInsn> getPhiInsns() { + return insns.subList(0, getCountPhiInsns()); + } + + /** + * @return the block index of this block + */ + public int getIndex() { + return index; + } + + /** + * @return the label of this block in rop form + */ + public int getRopLabel() { + return ropLabel; + } + + /** + * @return the label of this block in rop form as a hex string + */ + public String getRopLabelString() { + return Hex.u2(ropLabel); + } + + /** + * @return {@code non-null;} predecessors set, indexed by block index + */ + public BitSet getPredecessors() { + return predecessors; + } + + /** + * @return {@code non-null;} successors set, indexed by block index + */ + public BitSet getSuccessors() { + return successors; + } + + /** + * @return {@code non-null;} ordered successor list, containing block + * indicies + */ + public IntList getSuccessorList() { + return successorList; + } + + /** + * @return {@code >= -1;} block index of primary successor or + * {@code -1} if no primary successor + */ + public int getPrimarySuccessorIndex() { + return primarySuccessor; + } + + /** + * @return rop label of primary successor + */ + public int getPrimarySuccessorRopLabel() { + return parent.blockIndexToRopLabel(primarySuccessor); + } + + /** + * @return {@code null-ok;} the primary successor block or {@code null} + * if there is none + */ + public SsaBasicBlock getPrimarySuccessor() { + if (primarySuccessor < 0) { + return null; + } else { + return parent.getBlocks().get(primarySuccessor); + } + } + + /** + * @return successor list of rop labels + */ + public IntList getRopLabelSuccessorList() { + IntList result = new IntList(successorList.size()); + + int sz = successorList.size(); + + for (int i = 0; i < sz; i++) { + result.add(parent.blockIndexToRopLabel(successorList.get(i))); + } + return result; + } + + /** + * @return {@code non-null;} method that contains this block + */ + public SsaMethod getParent() { + return parent; + } + + /** + * Inserts a new empty GOTO block as a predecessor to this block. + * All previous predecessors will be predecessors to the new block. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public SsaBasicBlock insertNewPredecessor() { + SsaBasicBlock newPred = parent.makeNewGotoBlock(); + + // Update the new block. + newPred.predecessors = predecessors; + newPred.successors.set(index) ; + newPred.successorList.add(index); + newPred.primarySuccessor = index; + + + // Update us. + predecessors = new BitSet(parent.getBlocks().size()); + predecessors.set(newPred.index); + + // Update our (soon-to-be) old predecessors. + for (int i = newPred.predecessors.nextSetBit(0); i >= 0; + i = newPred.predecessors.nextSetBit(i + 1)) { + + SsaBasicBlock predBlock = parent.getBlocks().get(i); + + predBlock.replaceSuccessor(index, newPred.index); + } + + return newPred; + } + + /** + * Constructs and inserts a new empty GOTO block {@code Z} between + * this block ({@code A}) and a current successor block + * ({@code B}). The new block will replace B as A's successor and + * A as B's predecessor. A and B will no longer be directly connected. + * If B is listed as a successor multiple times, all references + * are replaced. + * + * @param other current successor (B) + * @return {@code non-null;} an appropriately-constructed instance + */ + public SsaBasicBlock insertNewSuccessor(SsaBasicBlock other) { + SsaBasicBlock newSucc = parent.makeNewGotoBlock(); + + if (!successors.get(other.index)) { + throw new RuntimeException("Block " + other.getRopLabelString() + + " not successor of " + getRopLabelString()); + } + + // Update the new block. + newSucc.predecessors.set(this.index); + newSucc.successors.set(other.index) ; + newSucc.successorList.add(other.index); + newSucc.primarySuccessor = other.index; + + // Update us. + for (int i = successorList.size() - 1 ; i >= 0; i--) { + if (successorList.get(i) == other.index) { + successorList.set(i, newSucc.index); + } + } + + if (primarySuccessor == other.index) { + primarySuccessor = newSucc.index; + } + successors.clear(other.index); + successors.set(newSucc.index); + + // Update "other". + other.predecessors.set(newSucc.index); + other.predecessors.set(index, successors.get(other.index)); + + return newSucc; + } + + /** + * Replaces an old successor with a new successor. This will throw + * RuntimeException if {@code oldIndex} was not a successor. + * + * @param oldIndex index of old successor block + * @param newIndex index of new successor block + */ + public void replaceSuccessor(int oldIndex, int newIndex) { + if (oldIndex == newIndex) { + return; + } + + // Update us. + successors.set(newIndex); + + if (primarySuccessor == oldIndex) { + primarySuccessor = newIndex; + } + + for (int i = successorList.size() - 1 ; i >= 0; i--) { + if (successorList.get(i) == oldIndex) { + successorList.set(i, newIndex); + } + } + + successors.clear(oldIndex); + + // Update new successor. + parent.getBlocks().get(newIndex).predecessors.set(index); + + // Update old successor. + parent.getBlocks().get(oldIndex).predecessors.clear(index); + } + + /** + * Removes a successor from this block's successor list. + * + * @param oldIndex index of successor block to remove + */ + public void removeSuccessor(int oldIndex) { + int removeIndex = 0; + + for (int i = successorList.size() - 1; i >= 0; i--) { + if (successorList.get(i) == oldIndex) { + removeIndex = i; + } else { + primarySuccessor = successorList.get(i); + } + } + + successorList.removeIndex(removeIndex); + successors.clear(oldIndex); + parent.getBlocks().get(oldIndex).predecessors.clear(index); + } + + /** + * Attaches block to an exit block if necessary. If this block + * is not an exit predecessor or is the exit block, this block does + * nothing. For use by {@link com.android.jack.dx.ssa.SsaMethod#makeExitBlock} + * + * @param exitBlock {@code non-null;} exit block + */ + public void exitBlockFixup(SsaBasicBlock exitBlock) { + if (this == exitBlock) { + return; + } + + if (successorList.size() == 0) { + /* + * This is an exit predecessor. + * Set the successor to the exit block + */ + successors.set(exitBlock.index); + successorList.add(exitBlock.index); + primarySuccessor = exitBlock.index; + exitBlock.predecessors.set(this.index); + } + } + + /** + * Adds a move instruction to the end of this basic block, just + * before the last instruction. If the result of the final instruction + * is the source in question, then the move is placed at the beginning of + * the primary successor block. This is for unversioned registers. + * + * @param result move destination + * @param source move source + */ + public void addMoveToEnd(RegisterSpec result, RegisterSpec source) { + + if (result.getReg() == source.getReg()) { + // Sometimes we end up with no-op moves. Ignore them here. + return; + } + + /* + * The last Insn has to be a normal SSA insn: a phi can't branch + * or return or cause an exception, etc. + */ + NormalSsaInsn lastInsn; + lastInsn = (NormalSsaInsn)insns.get(insns.size()-1); + + if (lastInsn.getResult() != null || lastInsn.getSources().size() > 0) { + /* + * The final insn in this block has a source or result + * register, and the moves we may need to place and + * schedule may interfere. We need to insert this + * instruction at the beginning of the primary successor + * block instead. We know this is safe, because when we + * edge-split earlier, we ensured that each successor has + * only us as a predecessor. + */ + + for (int i = successors.nextSetBit(0) + ; i >= 0 + ; i = successors.nextSetBit(i + 1)) { + + SsaBasicBlock succ; + + succ = parent.getBlocks().get(i); + succ.addMoveToBeginning(result, source); + } + } else { + /* + * We can safely add a move to the end of the block just + * before the last instruction, because the final insn does + * not assign to anything. + */ + RegisterSpecList sources = RegisterSpecList.make(source); + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, result, sources), this); + + insns.add(insns.size() - 1, toAdd); + + movesFromPhisAtEnd++; + } + } + + /** + * Adds a move instruction after the phi insn block. + * + * @param result move destination + * @param source move source + */ + public void addMoveToBeginning (RegisterSpec result, RegisterSpec source) { + if (result.getReg() == source.getReg()) { + // Sometimes we end up with no-op moves. Ignore them here. + return; + } + + RegisterSpecList sources = RegisterSpecList.make(source); + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, result, sources), this); + + insns.add(getCountPhiInsns(), toAdd); + movesFromPhisAtBeginning++; + } + + /** + * Sets the register as used in a bitset, taking into account its + * category/width. + * + * @param regsUsed set, indexed by register number + * @param rs register to mark as used + */ + private static void setRegsUsed (BitSet regsUsed, RegisterSpec rs) { + regsUsed.set(rs.getReg()); + if (rs.getCategory() > 1) { + regsUsed.set(rs.getReg() + 1); + } + } + + /** + * Checks to see if the register is used in a bitset, taking + * into account its category/width. + * + * @param regsUsed set, indexed by register number + * @param rs register to mark as used + * @return true if register is fully or partially (for the case of wide + * registers) used. + */ + private static boolean checkRegUsed (BitSet regsUsed, RegisterSpec rs) { + int reg = rs.getReg(); + int category = rs.getCategory(); + + return regsUsed.get(reg) + || (category == 2 ? regsUsed.get(reg + 1) : false); + } + + /** + * Ensures that all move operations in this block occur such that + * reads of any register happen before writes to that register. + * NOTE: caller is expected to returnSpareRegisters()! + * + * TODO: See Briggs, et al "Practical Improvements to the Construction and + * Destruction of Static Single Assignment Form" section 5. a) This can + * be done in three passes. + * + * @param toSchedule List of instructions. Must consist only of moves. + */ + private void scheduleUseBeforeAssigned(List<SsaInsn> toSchedule) { + BitSet regsUsedAsSources = new BitSet(parent.getRegCount()); + + // TODO: Get rid of this. + BitSet regsUsedAsResults = new BitSet(parent.getRegCount()); + + int sz = toSchedule.size(); + + int insertPlace = 0; + + while (insertPlace < sz) { + int oldInsertPlace = insertPlace; + + // Record all registers used as sources in this block. + for (int i = insertPlace; i < sz; i++) { + setRegsUsed(regsUsedAsSources, + toSchedule.get(i).getSources().get(0)); + + setRegsUsed(regsUsedAsResults, + toSchedule.get(i).getResult()); + } + + /* + * If there are no circular dependencies, then there exists + * n instructions where n > 1 whose result is not used as a source. + */ + for (int i = insertPlace; i <sz; i++) { + SsaInsn insn = toSchedule.get(i); + + /* + * Move these n registers to the front, since they overwrite + * nothing. + */ + if (!checkRegUsed(regsUsedAsSources, insn.getResult())) { + Collections.swap(toSchedule, i, insertPlace++); + } + } + + /* + * If we've made no progress in this iteration, there's a + * circular dependency. Split it using the temp reg. + */ + if (oldInsertPlace == insertPlace) { + + SsaInsn insnToSplit = null; + + // Find an insn whose result is used as a source. + for (int i = insertPlace; i < sz; i++) { + SsaInsn insn = toSchedule.get(i); + if (checkRegUsed(regsUsedAsSources, insn.getResult()) + && checkRegUsed(regsUsedAsResults, + insn.getSources().get(0))) { + + insnToSplit = insn; + /* + * We're going to split this insn; move it to the + * front. + */ + Collections.swap(toSchedule, insertPlace, i); + break; + } + } + + // At least one insn will be set above. + + RegisterSpec result = insnToSplit.getResult(); + RegisterSpec tempSpec = result.withReg( + parent.borrowSpareRegister(result.getCategory())); + + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, + tempSpec, + insnToSplit.getSources()), this); + + toSchedule.add(insertPlace++, toAdd); + + RegisterSpecList newSources = RegisterSpecList.make(tempSpec); + + NormalSsaInsn toReplace = new NormalSsaInsn( + new PlainInsn(Rops.opMove(result.getType()), + SourcePosition.NO_INFO, + result, + newSources), this); + + toSchedule.set(insertPlace, toReplace); + + // The size changed. + sz = toSchedule.size(); + } + + regsUsedAsSources.clear(); + regsUsedAsResults.clear(); + } + } + + /** + * Adds {@code regV} to the live-out list for this block. This is called + * by the liveness analyzer. + * + * @param regV register that is live-out for this block. + */ + public void addLiveOut (int regV) { + if (liveOut == null) { + liveOut = SetFactory.makeLivenessSet(parent.getRegCount()); + } + + liveOut.add(regV); + } + + /** + * Adds {@code regV} to the live-in list for this block. This is + * called by the liveness analyzer. + * + * @param regV register that is live-in for this block. + */ + public void addLiveIn (int regV) { + if (liveIn == null) { + liveIn = SetFactory.makeLivenessSet(parent.getRegCount()); + } + + liveIn.add(regV); + } + + /** + * Returns the set of live-in registers. Valid after register + * interference graph has been generated, otherwise empty. + * + * @return {@code non-null;} live-in register set. + */ + public IntSet getLiveInRegs() { + if (liveIn == null) { + liveIn = SetFactory.makeLivenessSet(parent.getRegCount()); + } + return liveIn; + } + + /** + * Returns the set of live-out registers. Valid after register + * interference graph has been generated, otherwise empty. + * + * @return {@code non-null;} live-out register set + */ + public IntSet getLiveOutRegs() { + if (liveOut == null) { + liveOut = SetFactory.makeLivenessSet(parent.getRegCount()); + } + return liveOut; + } + + /** + * @return true if this is the one-and-only exit block for this method + */ + public boolean isExitBlock() { + return index == parent.getExitBlockIndex(); + } + + /** + * Returns true if this block was last calculated to be reachable. + * Recalculates reachability if value has never been computed. + * + * @return {@code true} if reachable + */ + public boolean isReachable() { + if (reachable == -1) { + parent.computeReachability(); + } + return (reachable == 1); + } + + /** + * Sets reachability of block to specified value + * + * @param reach new value of reachability for block + */ + public void setReachable(int reach) { + reachable = reach; + } + + /** + * Sorts move instructions added via {@code addMoveToEnd} during + * phi removal so that results don't overwrite sources that are used. + * For use after all phis have been removed and all calls to + * addMoveToEnd() have been made.<p> + * + * This is necessary because copy-propogation may have left us in a state + * where the same basic block has the same register as a phi operand + * and a result. In this case, the register in the phi operand always + * refers value before any other phis have executed. + */ + public void scheduleMovesFromPhis() { + if (movesFromPhisAtBeginning > 1) { + List<SsaInsn> toSchedule; + + toSchedule = insns.subList(0, movesFromPhisAtBeginning); + + scheduleUseBeforeAssigned(toSchedule); + + SsaInsn firstNonPhiMoveInsn = insns.get(movesFromPhisAtBeginning); + + /* + * TODO: It's actually possible that this case never happens, + * because a move-exception block, having only one predecessor + * in SSA form, perhaps is never on a dominance frontier. + */ + if (firstNonPhiMoveInsn.isMoveException()) { + if (true) { + /* + * We've yet to observe this case, and if it can + * occur the code written to handle it probably + * does not work. + */ + throw new RuntimeException( + "Unexpected: moves from " + +"phis before move-exception"); + } else { + /* + * A move-exception insn must be placed first in this block + * We need to move it there, and deal with possible + * interference. + */ + boolean moveExceptionInterferes = false; + + int moveExceptionResult + = firstNonPhiMoveInsn.getResult().getReg(); + + /* + * Does the move-exception result reg interfere with the + * phi moves? + */ + for (SsaInsn insn : toSchedule) { + if (insn.isResultReg(moveExceptionResult) + || insn.isRegASource(moveExceptionResult)) { + moveExceptionInterferes = true; + break; + } + } + + if (!moveExceptionInterferes) { + // This is the easy case. + insns.remove(movesFromPhisAtBeginning); + insns.add(0, firstNonPhiMoveInsn); + } else { + /* + * We need to move the result to a spare reg + * and move it back. + */ + RegisterSpec originalResultSpec + = firstNonPhiMoveInsn.getResult(); + int spareRegister = parent.borrowSpareRegister( + originalResultSpec.getCategory()); + + // We now move it to a spare register. + firstNonPhiMoveInsn.changeResultReg(spareRegister); + RegisterSpec tempSpec = + firstNonPhiMoveInsn.getResult(); + + insns.add(0, firstNonPhiMoveInsn); + + // And here we move it back. + + NormalSsaInsn toAdd = new NormalSsaInsn( + new PlainInsn( + Rops.opMove(tempSpec.getType()), + SourcePosition.NO_INFO, + originalResultSpec, + RegisterSpecList.make(tempSpec)), + this); + + + /* + * Place it immediately after the phi-moves, + * overwriting the move-exception that was there. + */ + insns.set(movesFromPhisAtBeginning + 1, toAdd); + } + } + } + } + + if (movesFromPhisAtEnd > 1) { + scheduleUseBeforeAssigned( + insns.subList(insns.size() - movesFromPhisAtEnd - 1, + insns.size() - 1)); + } + + // Return registers borrowed here and in scheduleUseBeforeAssigned(). + parent.returnSpareRegisters(); + + } + + /** + * Visits all insns in this block. + * + * @param visitor {@code non-null;} callback interface + */ + public void forEachInsn(SsaInsn.Visitor visitor) { + // This gets called a LOT, and not using an iterator + // saves a lot of allocations and reduces memory usage + int len = insns.size(); + for (int i = 0; i < len; i++) { + insns.get(i).accept(visitor); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "{" + index + ":" + Hex.u2(ropLabel) + '}'; + } + + /** + * Visitor interface for basic blocks. + */ + public interface Visitor { + /** + * Indicates a block has been visited by an iterator method. + * + * @param v {@code non-null;} block visited + * @param parent {@code null-ok;} parent node if applicable + */ + void visitBlock (SsaBasicBlock v, SsaBasicBlock parent); + } + + /** + * Label comparator. + */ + public static final class LabelComparator + implements Comparator<SsaBasicBlock> { + /** {@inheritDoc} */ + public int compare(SsaBasicBlock b1, SsaBasicBlock b2) { + int label1 = b1.ropLabel; + int label2 = b2.ropLabel; + + if (label1 < label2) { + return -1; + } else if (label1 > label2) { + return 1; + } else { + return 0; + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaConverter.java b/dx/src/com/android/jack/dx/ssa/SsaConverter.java new file mode 100644 index 00000000..89dd19dd --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaConverter.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.util.IntIterator; + +import java.util.ArrayList; +import java.util.BitSet; + +/** + * Converts ROP methods to SSA Methods + */ +public class SsaConverter { + public static final boolean DEBUG = false; + + /** + * Returns an SSA representation, edge-split and with phi + * functions placed. + * + * @param rmeth input + * @param paramWidth the total width, in register-units, of the method's + * parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return output in SSA form + */ + public static SsaMethod convertToSsaMethod(RopMethod rmeth, + int paramWidth, boolean isStatic) { + SsaMethod result + = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + + LocalVariableInfo localInfo = LocalVariableExtractor.extract(result); + + placePhiFunctions(result, localInfo, 0); + new SsaRenamer(result).run(); + + /* + * The exit block, added here, is not considered for edge splitting + * or phi placement since no actual control flows to it. + */ + result.makeExitBlock(); + + return result; + } + + /** + * Updates an SSA representation, placing phi functions and renaming all + * registers above a certain threshold number. + * + * @param ssaMeth input + * @param threshold registers below this number are unchanged + */ + public static void updateSsaMethod(SsaMethod ssaMeth, int threshold) { + LocalVariableInfo localInfo = LocalVariableExtractor.extract(ssaMeth); + placePhiFunctions(ssaMeth, localInfo, threshold); + new SsaRenamer(ssaMeth, threshold).run(); + } + + /** + * Returns an SSA represention with only the edge-splitter run. + * + * @param rmeth method to process + * @param paramWidth width of all arguments in the method + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return an SSA represention with only the edge-splitter run + */ + public static SsaMethod testEdgeSplit (RopMethod rmeth, int paramWidth, + boolean isStatic) { + SsaMethod result; + + result = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + return result; + } + + /** + * Returns an SSA represention with only the steps through the + * phi placement run. + * + * @param rmeth method to process + * @param paramWidth width of all arguments in the method + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + * @return an SSA represention with only the edge-splitter run + */ + public static SsaMethod testPhiPlacement (RopMethod rmeth, int paramWidth, + boolean isStatic) { + SsaMethod result; + + result = SsaMethod.newFromRopMethod(rmeth, paramWidth, isStatic); + + edgeSplit(result); + + LocalVariableInfo localInfo = LocalVariableExtractor.extract(result); + + placePhiFunctions(result, localInfo, 0); + return result; + } + + /** + * See Appel section 19.1: + * + * Converts CFG into "edge-split" form, such that each node either a + * unique successor or unique predecessor.<p> + * + * In addition, the SSA form we use enforces a further constraint, + * requiring each block with a final instruction that returns a + * value to have a primary successor that has no other + * predecessor. This ensures move statements can always be + * inserted correctly when phi statements are removed. + * + * @param result method to process + */ + private static void edgeSplit(SsaMethod result) { + edgeSplitPredecessors(result); + edgeSplitMoveExceptionsAndResults(result); + edgeSplitSuccessors(result); + } + + /** + * Inserts Z nodes as new predecessors for every node that has multiple + * successors and multiple predecessors. + * + * @param result {@code non-null;} method to process + */ + private static void edgeSplitPredecessors(SsaMethod result) { + ArrayList<SsaBasicBlock> blocks = result.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + if (nodeNeedsUniquePredecessor(block)) { + block.insertNewPredecessor(); + } + } + } + + /** + * @param block {@code non-null;} block in question + * @return {@code true} if this node needs to have a unique + * predecessor created for it + */ + private static boolean nodeNeedsUniquePredecessor(SsaBasicBlock block) { + /* + * Any block with that has both multiple successors and multiple + * predecessors needs a new predecessor node. + */ + + int countPredecessors = block.getPredecessors().cardinality(); + int countSuccessors = block.getSuccessors().cardinality(); + + return (countPredecessors > 1 && countSuccessors > 1); + } + + /** + * In ROP form, move-exception must occur as the first insn in a block + * immediately succeeding the insn that could thrown an exception. + * We may need room to insert move insns later, so make sure to split + * any block that starts with a move-exception such that there is a + * unique move-exception block for each predecessor. + * + * @param ssaMeth method to process + */ + private static void edgeSplitMoveExceptionsAndResults(SsaMethod ssaMeth) { + ArrayList<SsaBasicBlock> blocks = ssaMeth.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + + /* + * Any block that starts with a move-exception and has more than + * one predecessor... + */ + if (!block.isExitBlock() + && block.getPredecessors().cardinality() > 1 + && block.getInsns().get(0).isMoveException()) { + + // block.getPredecessors() is changed in the loop below. + BitSet preds = (BitSet)block.getPredecessors().clone(); + for (int j = preds.nextSetBit(0); j >= 0; + j = preds.nextSetBit(j + 1)) { + SsaBasicBlock predecessor = blocks.get(j); + SsaBasicBlock zNode + = predecessor.insertNewSuccessor(block); + + /* + * Make sure to place the move-exception as the + * first insn. + */ + zNode.getInsns().add(0, block.getInsns().get(0).clone()); + } + + // Remove the move-exception from the original block. + block.getInsns().remove(0); + } + } + } + + /** + * Inserts Z nodes for every node that needs a new + * successor. + * + * @param result {@code non-null;} method to process + */ + private static void edgeSplitSuccessors(SsaMethod result) { + ArrayList<SsaBasicBlock> blocks = result.getBlocks(); + + /* + * New blocks are added to the end of the block list during + * this iteration. + */ + for (int i = blocks.size() - 1; i >= 0; i-- ) { + SsaBasicBlock block = blocks.get(i); + + // Successors list is modified in loop below. + BitSet successors = (BitSet)block.getSuccessors().clone(); + for (int j = successors.nextSetBit(0); + j >= 0; j = successors.nextSetBit(j+1)) { + + SsaBasicBlock succ = blocks.get(j); + + if (needsNewSuccessor(block, succ)) { + block.insertNewSuccessor(succ); + } + } + } + } + + /** + * Returns {@code true} if block and successor need a Z-node + * between them. Presently, this is {@code true} if the final + * instruction has any sources or results and the current + * successor block has more than one predecessor. + * + * @param block predecessor node + * @param succ successor node + * @return {@code true} if a Z node is needed + */ + private static boolean needsNewSuccessor(SsaBasicBlock block, + SsaBasicBlock succ) { + ArrayList<SsaInsn> insns = block.getInsns(); + SsaInsn lastInsn = insns.get(insns.size() - 1); + + return ((lastInsn.getResult() != null) + || (lastInsn.getSources().size() > 0)) + && succ.getPredecessors().cardinality() > 1; + } + + /** + * See Appel algorithm 19.6: + * + * Place Phi functions in appropriate locations. + * + * @param ssaMeth {@code non-null;} method to process. + * Modifications are made in-place. + * @param localInfo {@code non-null;} local variable info, used + * when placing phis + * @param threshold registers below this number are ignored + */ + private static void placePhiFunctions (SsaMethod ssaMeth, + LocalVariableInfo localInfo, int threshold) { + ArrayList<SsaBasicBlock> ssaBlocks; + int regCount; + int blockCount; + + ssaBlocks = ssaMeth.getBlocks(); + blockCount = ssaBlocks.size(); + regCount = ssaMeth.getRegCount() - threshold; + + DomFront df = new DomFront(ssaMeth); + DomFront.DomInfo[] domInfos = df.run(); + + // Bit set of registers vs block index "definition sites" + BitSet[] defsites = new BitSet[regCount]; + + // Bit set of registers vs block index "phi placement sites" + BitSet[] phisites = new BitSet[regCount]; + + for (int i = 0; i < regCount; i++) { + defsites[i] = new BitSet(blockCount); + phisites[i] = new BitSet(blockCount); + } + + /* + * For each register, build a set of all basic blocks where + * containing an assignment to that register. + */ + for (int bi = 0, s = ssaBlocks.size(); bi < s; bi++) { + SsaBasicBlock b = ssaBlocks.get(bi); + + for (SsaInsn insn : b.getInsns()) { + RegisterSpec rs = insn.getResult(); + + if (rs != null && rs.getReg() - threshold >= 0) { + defsites[rs.getReg() - threshold].set(bi); + } + } + } + + if (DEBUG) { + System.out.println("defsites"); + + for (int i = 0; i < regCount; i++) { + StringBuilder sb = new StringBuilder(); + sb.append('v').append(i).append(": "); + sb.append(defsites[i].toString()); + System.out.println(sb); + } + } + + BitSet worklist; + + /* + * For each register, compute all locations for phi placement + * based on dominance-frontier algorithm. + */ + for (int reg = 0, s = regCount; reg < s; reg++) { + int workBlockIndex; + + /* Worklist set starts out with each node where reg is assigned. */ + + worklist = (BitSet) (defsites[reg].clone()); + + while (0 <= (workBlockIndex = worklist.nextSetBit(0))) { + worklist.clear(workBlockIndex); + IntIterator dfIterator + = domInfos[workBlockIndex].dominanceFrontiers.iterator(); + + while (dfIterator.hasNext()) { + int dfBlockIndex = dfIterator.next(); + + if (!phisites[reg].get(dfBlockIndex)) { + phisites[reg].set(dfBlockIndex); + + int tReg = reg + threshold; + RegisterSpec rs + = localInfo.getStarts(dfBlockIndex).get(tReg); + + if (rs == null) { + ssaBlocks.get(dfBlockIndex).addPhiInsnForReg(tReg); + } else { + ssaBlocks.get(dfBlockIndex).addPhiInsnForReg(rs); + } + + if (!defsites[reg].get(dfBlockIndex)) { + worklist.set(dfBlockIndex); + } + } + } + } + } + + if (DEBUG) { + System.out.println("phisites"); + + for (int i = 0; i < regCount; i++) { + StringBuilder sb = new StringBuilder(); + sb.append('v').append(i).append(": "); + sb.append(phisites[i].toString()); + System.out.println(sb); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaInsn.java b/dx/src/com/android/jack/dx/ssa/SsaInsn.java new file mode 100644 index 00000000..bf76db26 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaInsn.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.*; +import com.android.jack.dx.util.ToHuman; + +/** + * An instruction in SSA form + */ +public abstract class SsaInsn implements ToHuman, Cloneable { + /** {@code non-null;} the block that contains this instance */ + private final SsaBasicBlock block; + + /** {@code null-ok;} result register */ + private RegisterSpec result; + + /** + * Constructs an instance. + * + * @param result {@code null-ok;} initial result register. May be changed. + * @param block {@code non-null;} block containing this insn. Can + * never change. + */ + protected SsaInsn(RegisterSpec result, SsaBasicBlock block) { + if (block == null) { + throw new NullPointerException("block == null"); + } + + this.block = block; + this.result = result; + } + + /** + * Makes a new SSA insn form a rop insn. + * + * @param insn {@code non-null;} rop insn + * @param block {@code non-null;} owning block + * @return {@code non-null;} an appropriately constructed instance + */ + public static SsaInsn makeFromRop(Insn insn, SsaBasicBlock block) { + return new NormalSsaInsn(insn, block); + } + + /** {@inheritDoc} */ + @Override + public SsaInsn clone() { + try { + return (SsaInsn)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException ("unexpected", ex); + } + } + + /** + * Like {@link com.android.jack.dx.rop.code.Insn getResult()}. + * + * @return result register + */ + public RegisterSpec getResult() { + return result; + } + + /** + * Set the result register. + * + * @param result {@code non-null;} the new result register + */ + protected void setResult(RegisterSpec result) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + this.result = result; + } + + /** + * Like {@link com.android.jack.dx.rop.code.Insn getSources()}. + * + * @return {@code non-null;} sources list + */ + abstract public RegisterSpecList getSources(); + + /** + * Gets the block to which this insn instance belongs. + * + * @return owning block + */ + public SsaBasicBlock getBlock() { + return block; + } + + /** + * Returns whether or not the specified reg is the result reg. + * + * @param reg register to test + * @return true if there is a result and it is stored in the specified + * register + */ + public boolean isResultReg(int reg) { + return result != null && result.getReg() == reg; + } + + + /** + * Changes the result register if this insn has a result. This is used + * during renaming. + * + * @param reg new result register + */ + public void changeResultReg(int reg) { + if (result != null) { + result = result.withReg(reg); + } + } + + /** + * Sets the local association for the result of this insn. This is + * sometimes updated during the SsaRenamer process. + * + * @param local {@code null-ok;} new debug/local variable info + */ + public final void setResultLocal(LocalItem local) { + LocalItem oldItem = result.getLocalItem(); + + if (local != oldItem && (local == null + || !local.equals(result.getLocalItem()))) { + result = RegisterSpec.makeLocalOptional( + result.getReg(), result.getType(), local); + } + } + + /** + * Map registers after register allocation. + * + * @param mapper {@code non-null;} mapping from old to new registers + */ + public final void mapRegisters(RegisterMapper mapper) { + RegisterSpec oldResult = result; + + result = mapper.map(result); + block.getParent().updateOneDefinition(this, oldResult); + mapSourceRegisters(mapper); + } + + /** + * Maps only source registers. + * + * @param mapper new mapping + */ + abstract public void mapSourceRegisters(RegisterMapper mapper); + + /** + * Returns the Rop opcode for this insn, or null if this is a phi insn. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code null-ok;} Rop opcode if there is one. + */ + abstract public Rop getOpcode(); + + /** + * Returns the original Rop insn for this insn, or null if this is + * a phi insn. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code null-ok;} Rop insn if there is one. + */ + abstract public Insn getOriginalRopInsn(); + + /** + * Gets the spec of a local variable assignment that occurs at this + * instruction, or null if no local variable assignment occurs. This + * may be the result register, or for {@code mark-local} insns + * it may be the source. + * + * @see com.android.jack.dx.rop.code.Insn#getLocalAssignment() + * + * @return {@code null-ok;} a local-associated register spec or null + */ + public RegisterSpec getLocalAssignment() { + if (result != null && result.getLocalItem() != null) { + return result; + } + + return null; + } + + /** + * Indicates whether the specified register is amongst the registers + * used as sources for this instruction. + * + * @param reg the register in question + * @return true if the reg is a source + */ + public boolean isRegASource(int reg) { + return null != getSources().specForRegister(reg); + } + + /** + * Transform back to ROP form. + * + * TODO: Move this up into NormalSsaInsn. + * + * @return {@code non-null;} a ROP representation of this instruction, with + * updated registers. + */ + public abstract Insn toRopInsn(); + + /** + * @return true if this is a PhiInsn or a normal move insn + */ + public abstract boolean isPhiOrMove(); + + /** + * Returns true if this insn is considered to have a side effect beyond + * that of assigning to the result reg. + * + * @return true if this insn is considered to have a side effect beyond + * that of assigning to the result reg. + */ + public abstract boolean hasSideEffect(); + + /** + * @return true if this is a move (but not a move-operand or + * move-exception) instruction + */ + public boolean isNormalMoveInsn() { + return false; + } + + /** + * @return true if this is a move-exception instruction. + * These instructions must immediately follow a preceeding invoke* + */ + public boolean isMoveException() { + return false; + } + + /** + * @return true if this instruction can throw. + */ + abstract public boolean canThrow(); + + /** + * Accepts a visitor. + * + * @param v {@code non-null} the visitor + */ + public abstract void accept(Visitor v); + + /** + * Visitor interface for this class. + */ + public static interface Visitor { + /** + * Any non-phi move instruction + * @param insn {@code non-null;} the instruction to visit + */ + public void visitMoveInsn(NormalSsaInsn insn); + + /** + * Any phi insn + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPhiInsn(PhiInsn insn); + + /** + * Any insn that isn't a move or a phi (which is also a move). + * @param insn {@code non-null;} the instruction to visit + */ + public void visitNonMoveInsn(NormalSsaInsn insn); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaMethod.java b/dx/src/com/android/jack/dx/ssa/SsaMethod.java new file mode 100644 index 00000000..f0166df3 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaMethod.java @@ -0,0 +1,873 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +/** + * A method in SSA form. + */ +public final class SsaMethod { + /** basic blocks, indexed by block index */ + private ArrayList<SsaBasicBlock> blocks; + + /** Index of first executed block in method */ + private int entryBlockIndex; + + /** + * Index of exit block, which exists only in SSA form, + * or or {@code -1} if there is none + */ + private int exitBlockIndex; + + /** total number of registers required */ + private int registerCount; + + /** first register number to use for any temporary "spares" */ + private int spareRegisterBase; + + /** current count of spare registers used */ + private int borrowedSpareRegisters; + + /** really one greater than the max label */ + private int maxLabel; + + /** the total width, in register-units, of the method's parameters */ + private final int paramWidth; + + /** true if this method has no {@code this} pointer argument */ + private final boolean isStatic; + + /** + * indexed by register: the insn where said register is defined or null + * if undefined. null until (lazily) created. + */ + private SsaInsn[] definitionList; + + /** indexed by register: the list of all insns that use a register */ + private ArrayList<SsaInsn>[] useList; + + /** A version of useList with each List unmodifiable */ + private List<SsaInsn>[] unmodifiableUseList; + + /** + * "back-convert mode". Set during back-conversion when registers + * are about to be mapped into a non-SSA namespace. When true, + * use and def lists are unavailable. + * + * TODO: Remove this mode, and place the functionality elsewhere + */ + private boolean backMode; + + /** + * @param ropMethod rop-form method to convert from + * @param paramWidth the total width, in register-units, of the + * method's parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + */ + public static SsaMethod newFromRopMethod(RopMethod ropMethod, + int paramWidth, boolean isStatic) { + SsaMethod result = new SsaMethod(ropMethod, paramWidth, isStatic); + + result.convertRopToSsaBlocks(ropMethod); + + return result; + } + + /** + * Constructs an instance. + * + * @param ropMethod {@code non-null;} the original rop-form method that + * this instance is based on + * @param paramWidth the total width, in register-units, of the + * method's parameters + * @param isStatic {@code true} if this method has no {@code this} + * pointer argument + */ + private SsaMethod(RopMethod ropMethod, int paramWidth, boolean isStatic) { + this.paramWidth = paramWidth; + this.isStatic = isStatic; + this.backMode = false; + this.maxLabel = ropMethod.getBlocks().getMaxLabel(); + this.registerCount = ropMethod.getBlocks().getRegCount(); + this.spareRegisterBase = registerCount; + } + + /** + * Builds a BitSet of block indices from a basic block list and a list + * of labels taken from Rop form. + * + * @param blocks Rop blocks + * @param labelList list of rop block labels + * @return BitSet of block indices + */ + static BitSet bitSetFromLabelList(BasicBlockList blocks, + IntList labelList) { + BitSet result = new BitSet(blocks.size()); + + for (int i = 0, sz = labelList.size(); i < sz; i++) { + result.set(blocks.indexOfLabel(labelList.get(i))); + } + + return result; + } + + /** + * Builds an IntList of block indices from a basic block list and a list + * of labels taken from Rop form. + * + * @param ropBlocks Rop blocks + * @param labelList list of rop block labels + * @return IntList of block indices + */ + public static IntList indexListFromLabelList(BasicBlockList ropBlocks, + IntList labelList) { + + IntList result = new IntList(labelList.size()); + + for (int i = 0, sz = labelList.size(); i < sz; i++) { + result.add(ropBlocks.indexOfLabel(labelList.get(i))); + } + + return result; + } + + private void convertRopToSsaBlocks(RopMethod rmeth) { + BasicBlockList ropBlocks = rmeth.getBlocks(); + int sz = ropBlocks.size(); + + blocks = new ArrayList<SsaBasicBlock>(sz + 2); + + for (int i = 0; i < sz; i++) { + SsaBasicBlock sbb = SsaBasicBlock.newFromRop(rmeth, i, this); + blocks.add(sbb); + } + + // Add an no-op entry block. + int origEntryBlockIndex = rmeth.getBlocks() + .indexOfLabel(rmeth.getFirstLabel()); + + SsaBasicBlock entryBlock + = blocks.get(origEntryBlockIndex).insertNewPredecessor(); + + entryBlockIndex = entryBlock.getIndex(); + exitBlockIndex = -1; // This gets made later. + } + + /** + * Creates an exit block and attaches it to the CFG if this method + * exits. Methods that never exit will not have an exit block. This + * is called after edge-splitting and phi insertion, since the edges + * going into the exit block should not be considered in those steps. + */ + /*package*/ void makeExitBlock() { + if (exitBlockIndex >= 0) { + throw new RuntimeException("must be called at most once"); + } + + exitBlockIndex = blocks.size(); + SsaBasicBlock exitBlock + = new SsaBasicBlock(exitBlockIndex, maxLabel++, this); + + blocks.add(exitBlock); + + for (SsaBasicBlock block : blocks) { + block.exitBlockFixup(exitBlock); + } + + if (exitBlock.getPredecessors().cardinality() == 0) { + // In cases where there is no exit... + blocks.remove(exitBlockIndex); + exitBlockIndex = -1; + maxLabel--; + } + } + + /** + * Gets a new {@code GOTO} insn. + * + * @param block block to which this GOTO will be added + * (not it's destination!) + * @return an appropriately-constructed instance. + */ + private static SsaInsn getGoto(SsaBasicBlock block) { + return new NormalSsaInsn ( + new PlainInsn(Rops.GOTO, SourcePosition.NO_INFO, + null, RegisterSpecList.EMPTY), block); + } + + /** + * Makes a new basic block for this method, which is empty besides + * a single {@code GOTO}. Successors and predecessors are not yet + * set. + * + * @return new block + */ + public SsaBasicBlock makeNewGotoBlock() { + int newIndex = blocks.size(); + SsaBasicBlock newBlock = new SsaBasicBlock(newIndex, maxLabel++, this); + + newBlock.getInsns().add(getGoto(newBlock)); + blocks.add(newBlock); + + return newBlock; + } + + /** + * @return block index of first execution block + */ + public int getEntryBlockIndex() { + return entryBlockIndex; + } + + /** + * @return first execution block + */ + public SsaBasicBlock getEntryBlock() { + return blocks.get(entryBlockIndex); + } + + /** + * @return block index of exit block or {@code -1} if there is none + */ + public int getExitBlockIndex() { + return exitBlockIndex; + } + + /** + * @return {@code null-ok;} block of exit block or {@code null} if + * there is none + */ + public SsaBasicBlock getExitBlock() { + return exitBlockIndex < 0 ? null : blocks.get(exitBlockIndex); + } + + /** + * @param bi block index or {@code -1} for none + * @return rop label or {code -1} if {@code bi} was {@code -1} + */ + public int blockIndexToRopLabel(int bi) { + if (bi < 0) { + return -1; + } + return blocks.get(bi).getRopLabel(); + } + + /** + * @return count of registers used in this method + */ + public int getRegCount() { + return registerCount; + } + + /** + * @return the total width, in register units, of the method's + * parameters + */ + public int getParamWidth() { + return paramWidth; + } + + /** + * Returns {@code true} if this is a static method. + * + * @return {@code true} if this is a static method + */ + public boolean isStatic() { + return isStatic; + } + + /** + * Borrows a register to use as a temp. Used in the phi removal process. + * Call returnSpareRegisters() when done. + * + * @param category width (1 or 2) of the register + * @return register number to use + */ + public int borrowSpareRegister(int category) { + int result = spareRegisterBase + borrowedSpareRegisters; + + borrowedSpareRegisters += category; + registerCount = Math.max(registerCount, result + category); + + return result; + } + + /** + * Returns all borrowed registers. + */ + public void returnSpareRegisters() { + borrowedSpareRegisters = 0; + } + + /** + * @return {@code non-null;} basic block list. Do not modify. + */ + public ArrayList<SsaBasicBlock> getBlocks() { + return blocks; + } + + /** + * Returns the count of reachable blocks in this method: blocks that have + * predecessors (or are the start block) + * + * @return {@code >= 0;} number of reachable basic blocks + */ + public int getCountReachableBlocks() { + int ret = 0; + + for (SsaBasicBlock b : blocks) { + // Blocks that have been disconnected don't count. + if (b.isReachable()) { + ret++; + } + } + + return ret; + } + + /** + * Computes reachability for all blocks in the method. First clears old + * values from all blocks, then starts with the entry block and walks down + * the control flow graph, marking all blocks it finds as reachable. + */ + public void computeReachability() { + for (SsaBasicBlock block : blocks) { + block.setReachable(0); + } + + ArrayList<SsaBasicBlock> blockList = new ArrayList<SsaBasicBlock>(); + blockList.add(this.getEntryBlock()); + + while (!blockList.isEmpty()) { + SsaBasicBlock block = blockList.remove(0); + if (block.isReachable()) continue; + + block.setReachable(1); + BitSet succs = block.getSuccessors(); + for (int i = succs.nextSetBit(0); i >= 0; + i = succs.nextSetBit(i + 1)) { + blockList.add(blocks.get(i)); + } + } + } + + /** + * Remaps unversioned registers. + * + * @param mapper maps old registers to new. + */ + public void mapRegisters(RegisterMapper mapper) { + for (SsaBasicBlock block : getBlocks()) { + for (SsaInsn insn : block.getInsns()) { + insn.mapRegisters(mapper); + } + } + + registerCount = mapper.getNewRegisterCount(); + spareRegisterBase = registerCount; + } + + /** + * Returns the insn that defines the given register + * @param reg register in question + * @return insn (actual instance from code) that defined this reg or null + * if reg is not defined. + */ + public SsaInsn getDefinitionForRegister(int reg) { + if (backMode) { + throw new RuntimeException("No def list in back mode"); + } + + if (definitionList != null) { + return definitionList[reg]; + } + + definitionList = new SsaInsn[getRegCount()]; + + forEachInsn(new SsaInsn.Visitor() { + public void visitMoveInsn (NormalSsaInsn insn) { + definitionList[insn.getResult().getReg()] = insn; + } + public void visitPhiInsn (PhiInsn phi) { + definitionList[phi.getResult().getReg()] = phi; + } + public void visitNonMoveInsn (NormalSsaInsn insn) { + RegisterSpec result = insn.getResult(); + if (result != null) { + definitionList[insn.getResult().getReg()] = insn; + } + } + }); + + return definitionList[reg]; + } + + /** + * Builds useList and unmodifiableUseList. + */ + private void buildUseList() { + if (backMode) { + throw new RuntimeException("No use list in back mode"); + } + + useList = new ArrayList[registerCount]; + + for (int i = 0; i < registerCount; i++) { + useList[i] = new ArrayList(); + } + + forEachInsn(new SsaInsn.Visitor() { + /** {@inheritDoc} */ + public void visitMoveInsn (NormalSsaInsn insn) { + addToUses(insn); + } + /** {@inheritDoc} */ + public void visitPhiInsn (PhiInsn phi) { + addToUses(phi); + } + /** {@inheritDoc} */ + public void visitNonMoveInsn (NormalSsaInsn insn) { + addToUses(insn); + } + /** + * Adds specified insn to the uses list for all of its sources. + * @param insn {@code non-null;} insn to process + */ + private void addToUses(SsaInsn insn) { + RegisterSpecList rl = insn.getSources(); + int sz = rl.size(); + + for (int i = 0; i < sz; i++) { + useList[rl.get(i).getReg()].add(insn); + } + } + }); + + unmodifiableUseList = new List[registerCount]; + + for (int i = 0; i < registerCount; i++) { + unmodifiableUseList[i] = Collections.unmodifiableList(useList[i]); + } + } + + /** + * Updates the use list for a single change in source register. + * + * @param insn {@code non-null;} insn being changed + * @param oldSource {@code null-ok;} The source that was used, if + * applicable + * @param newSource {@code non-null;} the new source being used + */ + /*package*/ void onSourceChanged(SsaInsn insn, + RegisterSpec oldSource, RegisterSpec newSource) { + if (useList == null) return; + + if (oldSource != null) { + int reg = oldSource.getReg(); + useList[reg].remove(insn); + } + + int reg = newSource.getReg(); + if (useList.length <= reg) { + useList = null; + return; + } + useList[reg].add(insn); + } + + /** + * Updates the use list for a source list change. + * + * @param insn {@code insn non-null;} insn being changed. + * {@code insn.getSources()} must return the new source list. + * @param oldSources {@code null-ok;} list of sources that were + * previously used + */ + /*package*/ void onSourcesChanged(SsaInsn insn, + RegisterSpecList oldSources) { + if (useList == null) return; + + if (oldSources != null) { + removeFromUseList(insn, oldSources); + } + + RegisterSpecList sources = insn.getSources(); + int szNew = sources.size(); + + for (int i = 0; i < szNew; i++) { + int reg = sources.get(i).getReg(); + useList[reg].add(insn); + } + } + + /** + * Removes a given {@code insn} from the use lists for the given + * {@code oldSources} (rather than the sources currently + * returned by insn.getSources()). + * + * @param insn {@code non-null;} insn in question + * @param oldSources {@code null-ok;} registers whose use lists + * {@code insn} should be removed form + */ + private void removeFromUseList(SsaInsn insn, RegisterSpecList oldSources) { + if (oldSources == null) { + return; + } + + int szNew = oldSources.size(); + for (int i = 0; i < szNew; i++) { + if (!useList[oldSources.get(i).getReg()].remove(insn)) { + throw new RuntimeException("use not found"); + } + } + } + + /** + * Adds an insn to both the use and def lists. For use when adding + * a new insn to the method. + * + * @param insn {@code non-null;} insn to add + */ + /*package*/ void onInsnAdded(SsaInsn insn) { + onSourcesChanged(insn, null); + updateOneDefinition(insn, null); + } + + /** + * Removes an instruction from use and def lists. For use during + * instruction removal. + * + * @param insn {@code non-null;} insn to remove + */ + /*package*/ void onInsnRemoved(SsaInsn insn) { + if (useList != null) { + removeFromUseList(insn, insn.getSources()); + } + + RegisterSpec resultReg = insn.getResult(); + if (definitionList != null && resultReg != null) { + definitionList[resultReg.getReg()] = null; + } + } + + /** + * Indicates that the instruction list has changed or the SSA register + * count has increased, so that internal datastructures that rely on + * it should be rebuild. In general, the various other on* methods + * should be called in preference when changes occur if they are + * applicable. + */ + public void onInsnsChanged() { + // Definition list will need to be recomputed + definitionList = null; + + // Use list will need to be recomputed + useList = null; + unmodifiableUseList = null; + } + + /** + * Updates a single definition. + * + * @param insn {@code non-null;} insn who's result should be recorded as + * a definition + * @param oldResult {@code null-ok;} a previous result that should + * be no longer considered a definition by this insn + */ + /*package*/ void updateOneDefinition(SsaInsn insn, + RegisterSpec oldResult) { + if (definitionList == null) return; + + if (oldResult != null) { + int reg = oldResult.getReg(); + definitionList[reg] = null; + } + + RegisterSpec resultReg = insn.getResult(); + + if (resultReg != null) { + int reg = resultReg.getReg(); + + if (definitionList[reg] != null) { + throw new RuntimeException("Duplicate add of insn"); + } else { + definitionList[resultReg.getReg()] = insn; + } + } + } + + /** + * Returns the list of all source uses (not results) for a register. + * + * @param reg register in question + * @return unmodifiable instruction list + */ + public List<SsaInsn> getUseListForRegister(int reg) { + + if (unmodifiableUseList == null) { + buildUseList(); + } + + return unmodifiableUseList[reg]; + } + + /** + * Returns a modifiable copy of the register use list. + * + * @return modifiable copy of the use-list, indexed by register + */ + public ArrayList<SsaInsn>[] getUseListCopy() { + if (useList == null) { + buildUseList(); + } + + ArrayList<SsaInsn>[] useListCopy + = (ArrayList<SsaInsn>[])(new ArrayList[registerCount]); + + for (int i = 0; i < registerCount; i++) { + useListCopy[i] = (ArrayList<SsaInsn>)(new ArrayList(useList[i])); + } + + return useListCopy; + } + + /** + * Checks to see if the given SSA reg is ever associated with a local + * local variable. Each SSA reg may be associated with at most one + * local var. + * + * @param spec {@code non-null;} ssa reg + * @return true if reg is ever associated with a local + */ + public boolean isRegALocal(RegisterSpec spec) { + SsaInsn defn = getDefinitionForRegister(spec.getReg()); + + if (defn == null) { + // version 0 registers are never used as locals + return false; + } + + // Does the definition have a local associated with it? + if (defn.getLocalAssignment() != null) return true; + + // If not, is there a mark-local insn? + for (SsaInsn use : getUseListForRegister(spec.getReg())) { + Insn insn = use.getOriginalRopInsn(); + + if (insn != null + && insn.getOpcode().getOpcode() == RegOps.MARK_LOCAL) { + return true; + } + } + + return false; + } + + /** + * Sets the new register count after renaming. + * + * @param newRegCount new register count + */ + /*package*/ void setNewRegCount(int newRegCount) { + registerCount = newRegCount; + spareRegisterBase = registerCount; + onInsnsChanged(); + } + + /** + * Makes a new SSA register. For use after renaming has completed. + * + * @return {@code >=0;} new SSA register. + */ + public int makeNewSsaReg() { + int reg = registerCount++; + spareRegisterBase = registerCount; + onInsnsChanged(); + return reg; + } + + /** + * Visits all insns in this method. + * + * @param visitor {@code non-null;} callback interface + */ + public void forEachInsn(SsaInsn.Visitor visitor) { + for (SsaBasicBlock block : blocks) { + block.forEachInsn(visitor); + } + } + + /** + * Visits each phi insn in this method + * @param v {@code non-null;} callback. + * + */ + public void forEachPhiInsn(PhiInsn.Visitor v) { + for (SsaBasicBlock block : blocks) { + block.forEachPhiInsn(v); + } + } + + + /** + * Walks the basic block tree in depth-first order, calling the visitor + * method once for every block. This depth-first walk may be run forward + * from the method entry point or backwards from the method exit points. + * + * @param reverse true if this should walk backwards from the exit points + * @param v {@code non-null;} callback interface. {@code parent} is set + * unless this is the root node + */ + public void forEachBlockDepthFirst(boolean reverse, + SsaBasicBlock.Visitor v) { + BitSet visited = new BitSet(blocks.size()); + + // We push the parent first, then the child on the stack. + Stack<SsaBasicBlock> stack = new Stack<SsaBasicBlock>(); + + SsaBasicBlock rootBlock = reverse ? getExitBlock() : getEntryBlock(); + + if (rootBlock == null) { + // in the case there's no exit block + return; + } + + stack.add(null); // Start with null parent. + stack.add(rootBlock); + + while (stack.size() > 0) { + SsaBasicBlock cur = stack.pop(); + SsaBasicBlock parent = stack.pop(); + + if (!visited.get(cur.getIndex())) { + BitSet children + = reverse ? cur.getPredecessors() : cur.getSuccessors(); + for (int i = children.nextSetBit(0); i >= 0 + ; i = children.nextSetBit(i + 1)) { + stack.add(cur); + stack.add(blocks.get(i)); + } + visited.set(cur.getIndex()); + v.visitBlock(cur, parent); + } + } + } + + /** + * Visits blocks in dom-tree order, starting at the current node. + * The {@code parent} parameter of the Visitor.visitBlock callback + * is currently always set to null. + * + * @param v {@code non-null;} callback interface + */ + public void forEachBlockDepthFirstDom(SsaBasicBlock.Visitor v) { + BitSet visited = new BitSet(getBlocks().size()); + Stack<SsaBasicBlock> stack = new Stack<SsaBasicBlock>(); + + stack.add(getEntryBlock()); + + while (stack.size() > 0) { + SsaBasicBlock cur = stack.pop(); + ArrayList<SsaBasicBlock> curDomChildren = cur.getDomChildren(); + + if (!visited.get(cur.getIndex())) { + // We walk the tree this way for historical reasons... + for (int i = curDomChildren.size() - 1; i >= 0; i--) { + SsaBasicBlock child = curDomChildren.get(i); + stack.add(child); + } + visited.set(cur.getIndex()); + v.visitBlock(cur, null); + } + } + } + + /** + * Deletes all insns in the set from this method. + * + * @param deletedInsns {@code non-null;} insns to delete + */ + public void deleteInsns(Set<SsaInsn> deletedInsns) { + for (SsaBasicBlock block : getBlocks()) { + ArrayList<SsaInsn> insns = block.getInsns(); + + for (int i = insns.size() - 1; i >= 0; i--) { + SsaInsn insn = insns.get(i); + + if (deletedInsns.contains(insn)) { + onInsnRemoved(insn); + insns.remove(i); + } + } + + // Check to see if we need to add a GOTO + + int insnsSz = insns.size(); + SsaInsn lastInsn = (insnsSz == 0) ? null : insns.get(insnsSz - 1); + + if (block != getExitBlock() && (insnsSz == 0 + || lastInsn.getOriginalRopInsn() == null + || lastInsn.getOriginalRopInsn().getOpcode() + .getBranchingness() == Rop.BRANCH_NONE)) { + // We managed to eat a throwable insn + + Insn gotoInsn = new PlainInsn(Rops.GOTO, + SourcePosition.NO_INFO, null, RegisterSpecList.EMPTY); + insns.add(SsaInsn.makeFromRop(gotoInsn, block)); + + // Remove secondary successors from this block + BitSet succs = block.getSuccessors(); + for (int i = succs.nextSetBit(0); i >= 0; + i = succs.nextSetBit(i + 1)) { + if (i != block.getPrimarySuccessorIndex()) { + block.removeSuccessor(i); + } + } + } + } + } + + /** + * Sets "back-convert mode". Set during back-conversion when registers + * are about to be mapped into a non-SSA namespace. When true, + * use and def lists are unavailable. + */ + public void setBackMode() { + backMode = true; + useList = null; + definitionList = null; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/SsaRenamer.java b/dx/src/com/android/jack/dx/ssa/SsaRenamer.java new file mode 100644 index 00000000..be9c51bc --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/SsaRenamer.java @@ -0,0 +1,662 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +import com.android.jack.dx.rop.code.LocalItem; +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.rop.type.Type; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Complete transformation to SSA form by renaming all registers accessed.<p> + * + * See Appel algorithm 19.7<p> + * + * Unlike the original algorithm presented in Appel, this renamer converts + * to a new flat (versionless) register space. The "version 0" registers, + * which represent the initial state of the Rop registers and should never + * actually be meaningfully accessed in a legal program, are represented + * as the first N registers in the SSA namespace. Subsequent assignments + * are assigned new unique names. Note that the incoming Rop representation + * has a concept of register widths, where 64-bit values are stored into + * two adjoining Rop registers. This adjoining register representation is + * ignored in SSA form conversion and while in SSA form, each register can be e + * either 32 or 64 bits wide depending on use. The adjoining-register + * represention is re-created later when converting back to Rop form. <p> + * + * But, please note, the SSA Renamer's ignoring of the adjoining-register ROP + * representation means that unaligned accesses to 64-bit registers are not + * supported. For example, you cannot do a 32-bit operation on a portion of + * a 64-bit register. This will never be observed to happen when coming + * from Java code, of course.<p> + * + * The implementation here, rather than keeping a single register version + * stack for the entire method as the dom tree is walked, instead keeps + * a mapping table for the current block being processed. Once the + * current block has been processed, this mapping table is then copied + * and used as the initial state for child blocks.<p> + */ +public class SsaRenamer implements Runnable { + /** debug flag */ + private static final boolean DEBUG = false; + + /** method we're processing */ + private final SsaMethod ssaMeth; + + /** next available SSA register */ + private int nextSsaReg; + + /** the number of original rop registers */ + private final int ropRegCount; + + /** work only on registers above this value */ + private int threshold; + + /** + * indexed by block index; register version state for each block start. + * This list is updated by each dom parent for its children. The only + * sub-arrays that exist at any one time are the start states for blocks + * yet to be processed by a {@code BlockRenamer} instance. + */ + private final RegisterSpec[][] startsForBlocks; + + /** map of SSA register number to debug (local var names) or null of n/a */ + private final ArrayList<LocalItem> ssaRegToLocalItems; + + /** + * maps SSA registers back to the original rop number. Used for + * debug only. + */ + private IntList ssaRegToRopReg; + + /** + * Constructs an instance of the renamer + * + * @param ssaMeth {@code non-null;} un-renamed SSA method that will + * be renamed. + */ + public SsaRenamer(SsaMethod ssaMeth) { + ropRegCount = ssaMeth.getRegCount(); + + this.ssaMeth = ssaMeth; + + /* + * Reserve the first N registers in the SSA register space for + * "version 0" registers. + */ + nextSsaReg = ropRegCount; + threshold = 0; + startsForBlocks = new RegisterSpec[ssaMeth.getBlocks().size()][]; + + ssaRegToLocalItems = new ArrayList<LocalItem>(); + + if (DEBUG) { + ssaRegToRopReg = new IntList(ropRegCount); + } + + /* + * Appel 19.7 + * + * Initialization: + * for each variable a // register i + * Count[a] <- 0 // nextSsaReg, flattened + * Stack[a] <- 0 // versionStack + * push 0 onto Stack[a] + * + */ + + // top entry for the version stack is version 0 + RegisterSpec[] initialRegMapping = new RegisterSpec[ropRegCount]; + for (int i = 0; i < ropRegCount; i++) { + // everyone starts with a version 0 register + initialRegMapping[i] = RegisterSpec.make(i, Type.VOID); + + if (DEBUG) { + ssaRegToRopReg.add(i); + } + } + + // Initial state for entry block + startsForBlocks[ssaMeth.getEntryBlockIndex()] = initialRegMapping; + } + + /** + * Constructs an instance of the renamer with threshold set + * + * @param ssaMeth {@code non-null;} un-renamed SSA method that will + * be renamed. + * @param thresh registers below this number are unchanged + */ + public SsaRenamer(SsaMethod ssaMeth, int thresh) { + this(ssaMeth); + threshold = thresh; + } + + /** + * Performs renaming transformation, modifying the method's instructions + * in-place. + */ + public void run() { + // Rename each block in dom-tree DFS order. + ssaMeth.forEachBlockDepthFirstDom(new SsaBasicBlock.Visitor() { + public void visitBlock (SsaBasicBlock block, + SsaBasicBlock unused) { + new BlockRenamer(block).process(); + } + }); + + ssaMeth.setNewRegCount(nextSsaReg); + ssaMeth.onInsnsChanged(); + + if (DEBUG) { + System.out.println("SSA\tRop"); + /* + * We're going to compute the version of the rop register + * by keeping a running total of how many times the rop + * register has been mapped. + */ + int[] versions = new int[ropRegCount]; + + int sz = ssaRegToRopReg.size(); + for (int i = 0; i < sz; i++) { + int ropReg = ssaRegToRopReg.get(i); + System.out.println(i + "\t" + ropReg + "[" + + versions[ropReg] + "]"); + versions[ropReg]++; + } + } + } + + /** + * Duplicates a RegisterSpec array. + * + * @param orig {@code non-null;} array to duplicate + * @return {@code non-null;} new instance + */ + private static RegisterSpec[] dupArray(RegisterSpec[] orig) { + RegisterSpec[] copy = new RegisterSpec[orig.length]; + + System.arraycopy(orig, 0, copy, 0, orig.length); + + return copy; + } + + /** + * Gets a local variable item for a specified register. + * + * @param ssaReg register in SSA name space + * @return {@code null-ok;} Local variable name or null if none + */ + private LocalItem getLocalForNewReg(int ssaReg) { + if (ssaReg < ssaRegToLocalItems.size()) { + return ssaRegToLocalItems.get(ssaReg); + } else { + return null; + } + } + + /** + * Records a debug (local variable) name for a specified register. + * + * @param ssaReg non-null named register spec in SSA name space + */ + private void setNameForSsaReg(RegisterSpec ssaReg) { + int reg = ssaReg.getReg(); + LocalItem local = ssaReg.getLocalItem(); + + ssaRegToLocalItems.ensureCapacity(reg + 1); + while (ssaRegToLocalItems.size() <= reg) { + ssaRegToLocalItems.add(null); + } + + ssaRegToLocalItems.set(reg, local); + } + + /** + * Returns true if this SSA register is below the specified threshold. + * Used when most code is already in SSA form, and renaming is needed only + * for registers above a certain threshold. + * + * @param ssaReg the SSA register in question + * @return {@code true} if its register number is below the threshold + */ + private boolean isBelowThresholdRegister(int ssaReg) { + return ssaReg < threshold; + } + + /** + * Returns true if this SSA register is a "version 0" + * register. All version 0 registers are assigned the first N register + * numbers, where N is the count of original rop registers. + * + * @param ssaReg the SSA register in question + * @return true if it is a version 0 register. + */ + private boolean isVersionZeroRegister(int ssaReg) { + return ssaReg < ropRegCount; + } + + /** + * Returns true if a and b are equal or are both null. + * + * @param a null-ok + * @param b null-ok + * @return Returns true if a and b are equal or are both null + */ + private static boolean equalsHandlesNulls(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Processes all insns in a block and renames their registers + * as appropriate. + */ + private class BlockRenamer implements SsaInsn.Visitor{ + /** {@code non-null;} block we're processing. */ + private final SsaBasicBlock block; + + /** + * {@code non-null;} indexed by old register name. The current + * top of the version stack as seen by this block. It's + * initialized from the ending state of its dom parent, + * updated as the block's instructions are processed, and then + * copied to each one of its dom children. + */ + private final RegisterSpec[] currentMapping; + + /** + * contains the set of moves we need to keep to preserve local + * var info. All other moves will be deleted. + */ + private final HashSet<SsaInsn> movesToKeep; + + /** + * maps the set of insns to replace after renaming is finished + * on the block. + */ + private final HashMap<SsaInsn, SsaInsn> insnsToReplace; + + private final RenamingMapper mapper; + + /** + * Constructs a block renamer instance. Call {@code process} + * to process. + * + * @param block {@code non-null;} block to process + */ + BlockRenamer(final SsaBasicBlock block) { + this.block = block; + currentMapping = startsForBlocks[block.getIndex()]; + movesToKeep = new HashSet<SsaInsn>(); + insnsToReplace = new HashMap<SsaInsn, SsaInsn>(); + mapper = new RenamingMapper(); + + // We don't need our own start state anymore + startsForBlocks[block.getIndex()] = null; + } + + /** + * Provides a register mapping between the old register space + * and the current renaming mapping. The mapping is updated + * as the current block's instructions are processed. + */ + private class RenamingMapper extends RegisterMapper { + public RenamingMapper() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public int getNewRegisterCount() { + return nextSsaReg; + } + + /** {@inheritDoc} */ + @Override + public RegisterSpec map(RegisterSpec registerSpec) { + if (registerSpec == null) return null; + + int reg = registerSpec.getReg(); + + // For debugging: assert that the mapped types are compatible. + if (DEBUG) { + RegisterSpec newVersion = currentMapping[reg]; + if (newVersion.getBasicType() != Type.BT_VOID + && registerSpec.getBasicFrameType() + != newVersion.getBasicFrameType()) { + + throw new RuntimeException( + "mapping registers of incompatible types! " + + registerSpec + + " " + currentMapping[reg]); + } + } + + return registerSpec.withReg(currentMapping[reg].getReg()); + } + } + + /** + * Renames all the variables in this block and inserts appriopriate + * phis in successor blocks. + */ + public void process() { + /* + * From Appel: + * + * Rename(n) = + * for each statement S in block n // 'statement' in 'block' + */ + + block.forEachInsn(this); + + updateSuccessorPhis(); + + // Delete all move insns in this block. + ArrayList<SsaInsn> insns = block.getInsns(); + int szInsns = insns.size(); + + for (int i = szInsns - 1; i >= 0 ; i--) { + SsaInsn insn = insns.get(i); + SsaInsn replaceInsn; + + replaceInsn = insnsToReplace.get(insn); + + if (replaceInsn != null) { + insns.set(i, replaceInsn); + } else if (insn.isNormalMoveInsn() + && !movesToKeep.contains(insn)) { + insns.remove(i); + } + } + + // Store the start states for our dom children. + boolean first = true; + for (SsaBasicBlock child : block.getDomChildren()) { + if (child != block) { + // Don't bother duplicating the array for the first child. + RegisterSpec[] childStart = first ? currentMapping + : dupArray(currentMapping); + + startsForBlocks[child.getIndex()] = childStart; + first = false; + } + } + + // currentMapping is owned by a child now. + } + + /** + * Enforces a few contraints when a register mapping is added. + * + * <ol> + * <li> Ensures that all new SSA registers specs in the mapping + * table with the same register number are identical. In effect, once + * an SSA register spec has received or lost a local variable name, + * then every old-namespace register that maps to it should gain or + * lose its local variable name as well. + * <li> Records the local name associated with the + * register so that a register is never associated with more than one + * local. + * <li> ensures that only one SSA register + * at a time is considered to be associated with a local variable. When + * {@code currentMapping} is updated and the newly added element + * is named, strip that name from any other SSA registers. + * </ol> + * + * @param ropReg {@code >= 0;} rop register number + * @param ssaReg {@code non-null;} an SSA register that has just + * been added to {@code currentMapping} + */ + private void addMapping(int ropReg, RegisterSpec ssaReg) { + int ssaRegNum = ssaReg.getReg(); + LocalItem ssaRegLocal = ssaReg.getLocalItem(); + + currentMapping[ropReg] = ssaReg; + + /* + * Ensure all SSA register specs with the same reg are identical. + */ + for (int i = currentMapping.length - 1; i >= 0; i--) { + RegisterSpec cur = currentMapping[i]; + + if (ssaRegNum == cur.getReg()) { + currentMapping[i] = ssaReg; + } + } + + // All further steps are for registers with local information. + if (ssaRegLocal == null) { + return; + } + + // Record that this SSA reg has been associated with a local. + setNameForSsaReg(ssaReg); + + // Ensure that no other SSA regs are associated with this local. + for (int i = currentMapping.length - 1; i >= 0; i--) { + RegisterSpec cur = currentMapping[i]; + + if (ssaRegNum != cur.getReg() + && ssaRegLocal.equals(cur.getLocalItem())) { + currentMapping[i] = cur.withLocalItem(null); + } + } + } + + /** + * {@inheritDoc} + * + * Phi insns have their result registers renamed. + */ + public void visitPhiInsn(PhiInsn phi) { + /* don't process sources for phi's */ + processResultReg(phi); + } + + /** + * {@inheritDoc} + * + * Move insns are treated as a simple mapping operation, and + * will later be removed unless they represent a local variable + * assignment. If they represent a local variable assignement, they + * are preserved. + */ + public void visitMoveInsn(NormalSsaInsn insn) { + /* + * For moves: copy propogate the move if we can, but don't + * if we need to preserve local variable info and the + * result has a different name than the source. + */ + + RegisterSpec ropResult = insn.getResult(); + int ropResultReg = ropResult.getReg(); + int ropSourceReg = insn.getSources().get(0).getReg(); + + insn.mapSourceRegisters(mapper); + int ssaSourceReg = insn.getSources().get(0).getReg(); + + LocalItem sourceLocal + = currentMapping[ropSourceReg].getLocalItem(); + LocalItem resultLocal = ropResult.getLocalItem(); + + /* + * A move from a register that's currently associated with a local + * to one that will not be associated with a local does not need + * to be preserved, but the local association should remain. + * Hence, we inherit the sourceLocal where the resultLocal is null. + */ + + LocalItem newLocal + = (resultLocal == null) ? sourceLocal : resultLocal; + LocalItem associatedLocal = getLocalForNewReg(ssaSourceReg); + + /* + * If we take the new local, will only one local have ever + * been associated with this SSA reg? + */ + boolean onlyOneAssociatedLocal + = associatedLocal == null || newLocal == null + || newLocal.equals(associatedLocal); + + /* + * If we're going to copy-propogate, then the ssa register + * spec that's going to go into the mapping is made up of + * the source register number mapped from above, the type + * of the result, and the name either from the result (if + * specified) or inherited from the existing mapping. + * + * The move source has incomplete type information in null + * object cases, so the result type is used. + */ + RegisterSpec ssaReg + = RegisterSpec.makeLocalOptional( + ssaSourceReg, ropResult.getType(), newLocal); + + if (!Optimizer.getPreserveLocals() || (onlyOneAssociatedLocal + && equalsHandlesNulls(newLocal, sourceLocal)) && + threshold == 0) { + /* + * We don't have to keep this move to preserve local + * information. Either the name is the same, or the result + * register spec is unnamed. + */ + + addMapping(ropResultReg, ssaReg); + } else if (onlyOneAssociatedLocal && sourceLocal == null && + threshold == 0) { + /* + * The register was previously unnamed. This means that a + * local starts after it's first assignment in SSA form + */ + + RegisterSpecList ssaSources = RegisterSpecList.make( + RegisterSpec.make(ssaReg.getReg(), + ssaReg.getType(), newLocal)); + + SsaInsn newInsn + = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMarkLocal(ssaReg), + SourcePosition.NO_INFO, null, ssaSources),block); + + insnsToReplace.put(insn, newInsn); + + // Just map as above. + addMapping(ropResultReg, ssaReg); + } else { + /* + * Do not copy-propogate, since the two registers have + * two different local-variable names. + */ + processResultReg(insn); + + movesToKeep.add(insn); + } + } + + /** + * {@inheritDoc} + * + * All insns that are not move or phi insns have their source registers + * mapped ot the current mapping. Their result registers are then + * renamed to a new SSA register which is then added to the current + * register mapping. + */ + public void visitNonMoveInsn(NormalSsaInsn insn) { + /* for each use of some variable X in S */ + insn.mapSourceRegisters(mapper); + + processResultReg(insn); + } + + /** + * Renames the result register of this insn and updates the + * current register mapping. Does nothing if this insn has no result. + * Applied to all non-move insns. + * + * @param insn insn to process. + */ + void processResultReg(SsaInsn insn) { + RegisterSpec ropResult = insn.getResult(); + + if (ropResult == null) { + return; + } + + int ropReg = ropResult.getReg(); + if (isBelowThresholdRegister(ropReg)) { + return; + } + + insn.changeResultReg(nextSsaReg); + addMapping(ropReg, insn.getResult()); + + if (DEBUG) { + ssaRegToRopReg.add(ropReg); + } + + nextSsaReg++; + } + + /** + * Updates the phi insns in successor blocks with operands based + * on the current mapping of the rop register the phis represent. + */ + private void updateSuccessorPhis() { + PhiInsn.Visitor visitor = new PhiInsn.Visitor() { + public void visitPhiInsn (PhiInsn insn) { + int ropReg; + + ropReg = insn.getRopResultReg(); + if (isBelowThresholdRegister(ropReg)) { + return; + } + + /* + * Never add a version 0 register as a phi + * operand. Version 0 registers represent the + * initial register state, and thus are never + * significant. Furthermore, the register liveness + * algorithm doesn't properly count them as "live + * in" at the beginning of the method. + */ + + RegisterSpec stackTop = currentMapping[ropReg]; + if (!isVersionZeroRegister(stackTop.getReg())) { + insn.addPhiOperand(stackTop, block); + } + } + }; + + BitSet successors = block.getSuccessors(); + for (int i = successors.nextSetBit(0); i >= 0; + i = successors.nextSetBit(i + 1)) { + SsaBasicBlock successor = ssaMeth.getBlocks().get(i); + successor.forEachPhiInsn(visitor); + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java b/dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java new file mode 100644 index 00000000..4fed8f2d --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/FirstFitAllocator.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.ssa.BasicRegisterMapper; +import com.android.jack.dx.ssa.NormalSsaInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * Allocates registers via a naive n^2 register allocator. + * This allocator does not try to co-locate local variables or deal + * intelligently with different size register uses. + */ +public class FirstFitAllocator extends RegisterAllocator { + /** + * If true, allocator places parameters at the top of the frame + * in calling-convention order. + */ + private static final boolean PRESLOT_PARAMS = true; + + /** indexed by old reg; the set of old regs we've mapped */ + private final BitSet mapped; + + /** {@inheritDoc} */ + public FirstFitAllocator( + final SsaMethod ssaMeth, final InterferenceGraph interference) { + super(ssaMeth, interference); + + mapped = new BitSet(ssaMeth.getRegCount()); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + return PRESLOT_PARAMS; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + int oldRegCount = ssaMeth.getRegCount(); + + BasicRegisterMapper mapper + = new BasicRegisterMapper(oldRegCount); + + int nextNewRegister = 0; + + if (PRESLOT_PARAMS) { + /* + * Reserve space for the params at the bottom of the register + * space. Later, we'll flip the params to the end of the register + * space. + */ + + nextNewRegister = ssaMeth.getParamWidth(); + } + + for (int i = 0; i < oldRegCount; i++) { + if (mapped.get(i)) { + // we already got this one + continue; + } + + int maxCategory = getCategoryForSsaReg(i); + IntSet current = new BitIntSet(oldRegCount); + + interference.mergeInterferenceSet(i, current); + + boolean isPreslotted = false; + int newReg = 0; + + if (PRESLOT_PARAMS && isDefinitionMoveParam(i)) { + // Any move-param definition must be a NormalSsaInsn + NormalSsaInsn defInsn = (NormalSsaInsn) + ssaMeth.getDefinitionForRegister(i); + + newReg = paramNumberFromMoveParam(defInsn); + + mapper.addMapping(i, newReg, maxCategory); + isPreslotted = true; + } else { + mapper.addMapping(i, nextNewRegister, maxCategory); + newReg = nextNewRegister; + } + + for (int j = i + 1; j < oldRegCount; j++) { + if (mapped.get(j) || isDefinitionMoveParam(j)) { + continue; + } + + /* + * If reg j doesn't interfere with the current mapping. + * Also, if this is a pre-slotted method parameter, we + * can't use more than the original param width. + */ + if (!current.has(j) + && !(isPreslotted + && (maxCategory < getCategoryForSsaReg(j)))) { + + interference.mergeInterferenceSet(j, current); + + maxCategory = Math.max(maxCategory, + getCategoryForSsaReg(j)); + + mapper.addMapping(j, newReg, maxCategory); + mapped.set(j); + } + } + + mapped.set(i); + if (!isPreslotted) { + nextNewRegister += maxCategory; + } + } + + return mapper; + } + + /** + * Returns the parameter number that this move-param insn refers to + * @param ndefInsn a move-param insn (otherwise, exceptions will be thrown) + * @return parameter number (offset in the total parameter width) + */ + private int paramNumberFromMoveParam(NormalSsaInsn ndefInsn) { + CstInsn origInsn = (CstInsn) ndefInsn.getOriginalRopInsn(); + + return ((CstInteger) origInsn.getConstant()).getValue(); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java b/dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java new file mode 100644 index 00000000..65a5d1d1 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/FirstFitLocalCombiningAllocator.java @@ -0,0 +1,1139 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.*; +import com.android.jack.dx.rop.cst.CstInteger; +import com.android.jack.dx.ssa.InterferenceRegisterMapper; +import com.android.jack.dx.ssa.NormalSsaInsn; +import com.android.jack.dx.ssa.Optimizer; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.IntIterator; +import com.android.jack.dx.util.IntSet; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Map; +import java.util.TreeMap; + +/** + * Allocates registers in a first-fit fashion, with the bottom reserved for + * method parameters and all SSAregisters representing the same local variable + * kept together if possible. + */ +public class FirstFitLocalCombiningAllocator extends RegisterAllocator { + /** local debug flag */ + private static final boolean DEBUG = false; + + /** maps local variable to a list of associated SSA registers */ + private final Map<LocalItem, ArrayList<RegisterSpec>> localVariables; + + /** list of move-result-pesudo instructions seen in this method */ + private final ArrayList<NormalSsaInsn> moveResultPseudoInsns; + + /** list of invoke-range instructions seen in this method */ + private final ArrayList<NormalSsaInsn> invokeRangeInsns; + + /** list of phi instructions seen in this method */ + private final ArrayList<PhiInsn> phiInsns; + + /** indexed by SSA reg; the set of SSA regs we've mapped */ + private final BitSet ssaRegsMapped; + + /** Register mapper which will be our result */ + private final InterferenceRegisterMapper mapper; + + /** end of rop registers range (starting at 0) reserved for parameters */ + private final int paramRangeEnd; + + /** set of rop registers reserved for parameters or local variables */ + private final BitSet reservedRopRegs; + + /** set of rop registers that have been used by anything */ + private final BitSet usedRopRegs; + + /** true if converter should take steps to minimize rop-form registers */ + private final boolean minimizeRegisters; + + /** + * Constructs instance. + * + * @param ssaMeth {@code non-null;} method to process + * @param interference non-null interference graph for SSA registers + * @param minimizeRegisters true if converter should take steps to + * minimize rop-form registers + */ + public FirstFitLocalCombiningAllocator( + SsaMethod ssaMeth, InterferenceGraph interference, + boolean minimizeRegisters) { + super(ssaMeth, interference); + + ssaRegsMapped = new BitSet(ssaMeth.getRegCount()); + + mapper = new InterferenceRegisterMapper( + interference, ssaMeth.getRegCount()); + + this.minimizeRegisters = minimizeRegisters; + + /* + * Reserve space for the params at the bottom of the register + * space. Later, we'll flip the params to the end of the register + * space. + */ + + paramRangeEnd = ssaMeth.getParamWidth(); + + reservedRopRegs = new BitSet(paramRangeEnd * 2); + reservedRopRegs.set(0, paramRangeEnd); + usedRopRegs = new BitSet(paramRangeEnd * 2); + localVariables = new TreeMap<LocalItem, ArrayList<RegisterSpec>>(); + moveResultPseudoInsns = new ArrayList<NormalSsaInsn>(); + invokeRangeInsns = new ArrayList<NormalSsaInsn>(); + phiInsns = new ArrayList<PhiInsn>(); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + return true; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + + analyzeInstructions(); + + if (DEBUG) { + printLocalVars(); + } + + if (DEBUG) System.out.println("--->Mapping local-associated params"); + handleLocalAssociatedParams(); + + if (DEBUG) System.out.println("--->Mapping other params"); + handleUnassociatedParameters(); + + if (DEBUG) System.out.println("--->Mapping invoke-range"); + handleInvokeRangeInsns(); + + if (DEBUG) { + System.out.println("--->Mapping local-associated non-params"); + } + handleLocalAssociatedOther(); + + if (DEBUG) System.out.println("--->Mapping check-cast results"); + handleCheckCastResults(); + + if (DEBUG) System.out.println("--->Mapping phis"); + handlePhiInsns(); + + if (DEBUG) System.out.println("--->Mapping others"); + handleNormalUnassociated(); + + return mapper; + } + + /** + * Dumps local variable table to stdout for debugging. + */ + private void printLocalVars() { + System.out.println("Printing local vars"); + for (Map.Entry<LocalItem, ArrayList<RegisterSpec>> e : + localVariables.entrySet()) { + StringBuilder regs = new StringBuilder(); + + regs.append('{'); + regs.append(' '); + for (RegisterSpec reg : e.getValue()) { + regs.append('v'); + regs.append(reg.getReg()); + regs.append(' '); + } + regs.append('}'); + System.out.printf("Local: %s Registers: %s\n", e.getKey(), regs); + } + } + + /** + * Maps all local-associated parameters to rop registers. + */ + private void handleLocalAssociatedParams() { + for (ArrayList<RegisterSpec> ssaRegs : localVariables.values()) { + int sz = ssaRegs.size(); + int paramIndex = -1; + int paramCategory = 0; + + // First, find out if this local variable is a parameter. + for (int i = 0; i < sz; i++) { + RegisterSpec ssaSpec = ssaRegs.get(i); + int ssaReg = ssaSpec.getReg(); + + paramIndex = getParameterIndexForReg(ssaReg); + + if (paramIndex >= 0) { + paramCategory = ssaSpec.getCategory(); + addMapping(ssaSpec, paramIndex); + break; + } + } + + if (paramIndex < 0) { + // This local wasn't a parameter. + continue; + } + + // Any remaining local-associated registers will be mapped later. + tryMapRegs(ssaRegs, paramIndex, paramCategory, true); + } + } + + /** + * Gets the parameter index for SSA registers that are method parameters. + * {@code -1} is returned for non-parameter registers. + * + * @param ssaReg {@code >=0;} SSA register to look up + * @return parameter index or {@code -1} if not a parameter + */ + private int getParameterIndexForReg(int ssaReg) { + SsaInsn defInsn = ssaMeth.getDefinitionForRegister(ssaReg); + if (defInsn == null) { + return -1; + } + + Rop opcode = defInsn.getOpcode(); + + // opcode == null for phi insns. + if (opcode != null && opcode.getOpcode() == RegOps.MOVE_PARAM) { + CstInsn origInsn = (CstInsn) defInsn.getOriginalRopInsn(); + return ((CstInteger) origInsn.getConstant()).getValue(); + } + + return -1; + } + + /** + * Maps all local-associated registers that are not parameters. + * Tries to find an unreserved range that's wide enough for all of + * the SSA registers, and then tries to map them all to that + * range. If not all fit, a new range is tried until all registers + * have been fit. + */ + private void handleLocalAssociatedOther() { + for (ArrayList<RegisterSpec> specs : localVariables.values()) { + int ropReg = paramRangeEnd; + + boolean done = false; + do { + int maxCategory = 1; + + // Compute max category for remaining unmapped registers. + int sz = specs.size(); + for (int i = 0; i < sz; i++) { + RegisterSpec ssaSpec = specs.get(i); + int category = ssaSpec.getCategory(); + if (!ssaRegsMapped.get(ssaSpec.getReg()) + && category > maxCategory) { + maxCategory = category; + } + } + + ropReg = findRopRegForLocal(ropReg, maxCategory); + if (canMapRegs(specs, ropReg)) { + done = tryMapRegs(specs, ropReg, maxCategory, true); + } + + // Increment for next call to findRopRegForLocal. + ropReg++; + } while (!done); + } + } + + /** + * Tries to map a list of SSA registers into the a rop reg, marking + * used rop space as reserved. SSA registers that don't fit are left + * unmapped. + * + * @param specs {@code non-null;} SSA registers to attempt to map + * @param ropReg {@code >=0;} rop register to map to + * @param maxAllowedCategory {@code 1..2;} maximum category + * allowed in mapping. + * @param markReserved do so if {@code true} + * @return {@code true} if all registers were mapped, {@code false} + * if some remain unmapped + */ + private boolean tryMapRegs( + ArrayList<RegisterSpec> specs, int ropReg, + int maxAllowedCategory, boolean markReserved) { + boolean remaining = false; + for (RegisterSpec spec : specs) { + if (ssaRegsMapped.get(spec.getReg())) { + continue; + } + + boolean succeeded; + succeeded = tryMapReg(spec, ropReg, maxAllowedCategory); + remaining = !succeeded || remaining; + if (succeeded && markReserved) { + // This only needs to be called once really with + // the widest category used, but <shrug> + markReserved(ropReg, spec.getCategory()); + } + } + return !remaining; + } + + /** + * Tries to map an SSA register to a rop register. + * + * @param ssaSpec {@code non-null;} SSA register + * @param ropReg {@code >=0;} rop register + * @param maxAllowedCategory {@code 1..2;} the maximum category + * that the SSA register is allowed to be + * @return {@code true} if map succeeded, {@code false} if not + */ + private boolean tryMapReg(RegisterSpec ssaSpec, int ropReg, + int maxAllowedCategory) { + if (ssaSpec.getCategory() <= maxAllowedCategory + && !ssaRegsMapped.get(ssaSpec.getReg()) + && canMapReg(ssaSpec, ropReg)) { + addMapping(ssaSpec, ropReg); + return true; + } + + return false; + } + + /** + * Marks a range of rop registers as "reserved for a local variable." + * + * @param ropReg {@code >= 0;} rop register to reserve + * @param category {@code > 0;} width to reserve + */ + private void markReserved(int ropReg, int category) { + reservedRopRegs.set(ropReg, ropReg + category, true); + } + + /** + * Checks to see if any rop registers in the specified range are reserved + * for local variables or parameters. + * + * @param ropRangeStart {@code >= 0;} lowest rop register + * @param width {@code > 0;} number of rop registers in range. + * @return {@code true} if any register in range is marked reserved + */ + private boolean rangeContainsReserved(int ropRangeStart, int width) { + for (int i = ropRangeStart; i < (ropRangeStart + width); i++) { + if (reservedRopRegs.get(i)) { + return true; + } + } + return false; + } + + /** + * Returns true if given rop register represents the {@code this} pointer + * for a non-static method. + * + * @param startReg rop register + * @return true if the "this" pointer is located here. + */ + private boolean isThisPointerReg(int startReg) { + // "this" is always the first parameter. + return startReg == 0 && !ssaMeth.isStatic(); + } + + /** + * Finds a range of unreserved rop registers. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param width {@code > 0;} the width, in registers, required. + * @return {@code >= 0;} start of available register range. + */ + private int findNextUnreservedRopReg(int startReg, int width) { + int reg; + + reg = reservedRopRegs.nextClearBit(startReg); + + while (true) { + int i = 1; + + while (i < width && !reservedRopRegs.get(reg + i)) { + i++; + } + + if (i == width) { + return reg; + } + + reg = reservedRopRegs.nextClearBit(reg + i); + } + } + + /** + * Finds a range of rop regs that can be used for local variables. + * If {@code MIX_LOCALS_AND_OTHER} is {@code false}, this means any + * rop register that has not yet been used. + * + * @param startReg {@code >= 0;} a rop register to start the search at + * @param width {@code > 0;} the width, in registers, required. + * @return {@code >= 0;} start of available register range. + */ + private int findRopRegForLocal(int startReg, int width) { + int reg; + + reg = usedRopRegs.nextClearBit(startReg); + + while (true) { + int i = 1; + + while (i < width && !usedRopRegs.get(reg + i)) { + i++; + } + + if (i == width) { + return reg; + } + + reg = usedRopRegs.nextClearBit(reg + i); + } + } + + /** + * Maps any parameter that isn't local-associated, which can happen + * in the case where there is no java debug info. + */ + private void handleUnassociatedParameters() { + int szSsaRegs = ssaMeth.getRegCount(); + + for (int ssaReg = 0; ssaReg < szSsaRegs; ssaReg++) { + if (ssaRegsMapped.get(ssaReg)) { + // We already did this one above + continue; + } + + int paramIndex = getParameterIndexForReg(ssaReg); + + RegisterSpec ssaSpec = getDefinitionSpecForSsaReg(ssaReg); + if (paramIndex >= 0) { + addMapping(ssaSpec, paramIndex); + } + } + } + + /** + * Handles all insns that want a register range for their sources. + */ + private void handleInvokeRangeInsns() { + for (NormalSsaInsn insn : invokeRangeInsns) { + adjustAndMapSourceRangeRange(insn); + } + } + + /** + * Handles check cast results to reuse the same source register. + * Inserts a move if it can't map the same register to both and the + * check cast is not caught. + */ + private void handleCheckCastResults() { + for (NormalSsaInsn insn : moveResultPseudoInsns) { + RegisterSpec moveRegSpec = insn.getResult(); + int moveReg = moveRegSpec.getReg(); + BitSet predBlocks = insn.getBlock().getPredecessors(); + + // Expect one predecessor block only + if (predBlocks.cardinality() != 1) { + continue; + } + + SsaBasicBlock predBlock = + ssaMeth.getBlocks().get(predBlocks.nextSetBit(0)); + ArrayList<SsaInsn> insnList = predBlock.getInsns(); + + /** + * If the predecessor block has a check-cast, it will be the last + * instruction + */ + SsaInsn checkCastInsn = insnList.get(insnList.size() - 1); + if (checkCastInsn.getOpcode().getOpcode() != RegOps.CHECK_CAST) { + continue; + } + + RegisterSpec checkRegSpec = checkCastInsn.getSources().get(0); + int checkReg = checkRegSpec.getReg(); + + /** + * See if either register is already mapped. Most likely the move + * result will be mapped already since the cast result is stored + * in a local variable. + */ + int category = checkRegSpec.getCategory(); + boolean moveMapped = ssaRegsMapped.get(moveReg); + boolean checkMapped = ssaRegsMapped.get(checkReg); + if (moveMapped & !checkMapped) { + int moveRopReg = mapper.oldToNew(moveReg); + checkMapped = tryMapReg(checkRegSpec, moveRopReg, category); + } + if (checkMapped & !moveMapped) { + int checkRopReg = mapper.oldToNew(checkReg); + moveMapped = tryMapReg(moveRegSpec, checkRopReg, category); + } + + // Map any unmapped registers to anything available + if (!moveMapped || !checkMapped) { + int ropReg = findNextUnreservedRopReg(paramRangeEnd, category); + ArrayList<RegisterSpec> ssaRegs = + new ArrayList<RegisterSpec>(2); + ssaRegs.add(moveRegSpec); + ssaRegs.add(checkRegSpec); + + while (!tryMapRegs(ssaRegs, ropReg, category, false)) { + ropReg = findNextUnreservedRopReg(ropReg + 1, category); + } + } + + /* + * If source and result have a different mapping, insert a move so + * they can have the same mapping. Don't do this if the check cast + * is caught, since it will overwrite a potentially live value. + */ + boolean hasExceptionHandlers = + checkCastInsn.getOriginalRopInsn().getCatches().size() != 0; + int moveRopReg = mapper.oldToNew(moveReg); + int checkRopReg = mapper.oldToNew(checkReg); + if (moveRopReg != checkRopReg && !hasExceptionHandlers) { + ((NormalSsaInsn) checkCastInsn).changeOneSource(0, + insertMoveBefore(checkCastInsn, checkRegSpec)); + addMapping(checkCastInsn.getSources().get(0), moveRopReg); + } + } + } + + /** + * Handles all phi instructions, trying to map them to a common register. + */ + private void handlePhiInsns() { + for (PhiInsn insn : phiInsns) { + processPhiInsn(insn); + } + } + + /** + * Maps all non-parameter, non-local variable registers. + */ + private void handleNormalUnassociated() { + int szSsaRegs = ssaMeth.getRegCount(); + + for (int ssaReg = 0; ssaReg < szSsaRegs; ssaReg++) { + if (ssaRegsMapped.get(ssaReg)) { + // We already did this one + continue; + } + + RegisterSpec ssaSpec = getDefinitionSpecForSsaReg(ssaReg); + + if (ssaSpec == null) continue; + + int category = ssaSpec.getCategory(); + // Find a rop reg that does not interfere + int ropReg = findNextUnreservedRopReg(paramRangeEnd, category); + while (!canMapReg(ssaSpec, ropReg)) { + ropReg = findNextUnreservedRopReg(ropReg + 1, category); + } + + addMapping(ssaSpec, ropReg); + } + } + + /** + * Checks to see if a list of SSA registers can all be mapped into + * the same rop reg. Ignores registers that have already been mapped, + * and checks the interference graph and ensures the range does not + * cross the parameter range. + * + * @param specs {@code non-null;} SSA registers to check + * @param ropReg {@code >=0;} rop register to check mapping to + * @return {@code true} if all unmapped registers can be mapped + */ + private boolean canMapRegs(ArrayList<RegisterSpec> specs, int ropReg) { + for (RegisterSpec spec : specs) { + if (ssaRegsMapped.get(spec.getReg())) continue; + if (!canMapReg(spec, ropReg)) return false; + } + return true; + } + + /** + * Checks to see if {@code ssaSpec} can be mapped to + * {@code ropReg}. Checks interference graph and ensures + * the range does not cross the parameter range. + * + * @param ssaSpec {@code non-null;} SSA spec + * @param ropReg prosepctive new-namespace reg + * @return {@code true} if mapping is possible + */ + private boolean canMapReg(RegisterSpec ssaSpec, int ropReg) { + int category = ssaSpec.getCategory(); + return !(spansParamRange(ropReg, category) + || mapper.interferes(ssaSpec, ropReg)); + } + + /** + * Returns true if the specified rop register + category + * will cross the boundry between the lower {@code paramWidth} + * registers reserved for method params and the upper registers. We cannot + * allocate a register that spans the param block and the normal block, + * because we will be moving the param block to high registers later. + * + * @param ssaReg register in new namespace + * @param category width that the register will have + * @return {@code true} in the case noted above + */ + private boolean spansParamRange(int ssaReg, int category) { + return ((ssaReg < paramRangeEnd) + && ((ssaReg + category) > paramRangeEnd)); + } + + /** + * Analyze each instruction and find out all the local variable assignments + * and move-result-pseudo/invoke-range instrucitons. + */ + private void analyzeInstructions() { + ssaMeth.forEachInsn(new SsaInsn.Visitor() { + /** {@inheritDoc} */ + public void visitMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + + /** {@inheritDoc} */ + public void visitPhiInsn(PhiInsn insn) { + processInsn(insn); + } + + /** {@inheritDoc} */ + public void visitNonMoveInsn(NormalSsaInsn insn) { + processInsn(insn); + } + + /** + * This method collects three types of instructions: + * + * 1) Adds a local variable assignment to the + * {@code localVariables} map. + * 2) Add move-result-pseudo to the + * {@code moveResultPseudoInsns} list. + * 3) Add invoke-range to the + * {@code invokeRangeInsns} list. + * + * @param insn {@code non-null;} insn that may represent a + * local variable assignment + */ + private void processInsn(SsaInsn insn) { + RegisterSpec assignment; + assignment = insn.getLocalAssignment(); + + if (assignment != null) { + LocalItem local = assignment.getLocalItem(); + + ArrayList<RegisterSpec> regList + = localVariables.get(local); + + if (regList == null) { + regList = new ArrayList<RegisterSpec>(); + localVariables.put(local, regList); + } + + regList.add(assignment); + } + + if (insn instanceof NormalSsaInsn) { + if (insn.getOpcode().getOpcode() == + RegOps.MOVE_RESULT_PSEUDO) { + moveResultPseudoInsns.add((NormalSsaInsn) insn); + } else if (Optimizer.getAdvice().requiresSourcesInOrder( + insn.getOriginalRopInsn().getOpcode(), + insn.getSources())) { + invokeRangeInsns.add((NormalSsaInsn) insn); + } + } else if (insn instanceof PhiInsn) { + phiInsns.add((PhiInsn) insn); + } + + } + }); + } + + /** + * Adds a mapping from an SSA register to a rop register. + * {@link #canMapReg} should have already been called. + * + * @param ssaSpec {@code non-null;} SSA register to map from + * @param ropReg {@code >=0;} rop register to map to + */ + private void addMapping(RegisterSpec ssaSpec, int ropReg) { + int ssaReg = ssaSpec.getReg(); + + // An assertion. + if (ssaRegsMapped.get(ssaReg) || !canMapReg(ssaSpec, ropReg)) { + throw new RuntimeException( + "attempt to add invalid register mapping"); + } + + if (DEBUG) { + System.out.printf("Add mapping s%d -> v%d c:%d\n", + ssaSpec.getReg(), ropReg, ssaSpec.getCategory()); + } + + int category = ssaSpec.getCategory(); + mapper.addMapping(ssaSpec.getReg(), ropReg, category); + ssaRegsMapped.set(ssaReg); + usedRopRegs.set(ropReg, ropReg + category); + } + + + /** + * Maps the source registers of the specified instruction such that they + * will fall in a contiguous range in rop form. Moves are inserted as + * necessary to allow the range to be allocated. + * + * @param insn {@code non-null;} insn whos sources to process + */ + private void adjustAndMapSourceRangeRange(NormalSsaInsn insn) { + int newRegStart = findRangeAndAdjust(insn); + + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + int nextRopReg = newRegStart; + + for (int i = 0; i < szSources; i++) { + RegisterSpec source = sources.get(i); + int sourceReg = source.getReg(); + int category = source.getCategory(); + int curRopReg = nextRopReg; + nextRopReg += category; + + if (ssaRegsMapped.get(sourceReg)) { + continue; + } + + LocalItem localItem = getLocalItemForReg(sourceReg); + addMapping(source, curRopReg); + + if (localItem != null) { + markReserved(curRopReg, category); + ArrayList<RegisterSpec> similarRegisters + = localVariables.get(localItem); + + int szSimilar = similarRegisters.size(); + + /* + * Try to map all SSA registers also associated with + * this local. + */ + for (int j = 0; j < szSimilar; j++) { + RegisterSpec similarSpec = similarRegisters.get(j); + int similarReg = similarSpec.getReg(); + + // Don't map anything that's also a source. + if (-1 != sources.indexOfRegister(similarReg)) { + continue; + } + + // Registers left unmapped will get handled later. + tryMapReg(similarSpec, curRopReg, category); + } + } + } + } + + /** + * Find a contiguous rop register range that fits the specified + * instruction's sources. First, try to center the range around + * sources that have already been mapped to rop registers. If that fails, + * just find a new contiguous range that doesn't interfere. + * + * @param insn {@code non-null;} the insn whose sources need to + * fit. Must be last insn in basic block. + * @return {@code >= 0;} rop register of start of range + */ + private int findRangeAndAdjust(NormalSsaInsn insn) { + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + // the category for each source index + int categoriesForIndex[] = new int[szSources]; + int rangeLength = 0; + + // Compute rangeLength and categoriesForIndex + for (int i = 0; i < szSources; i++) { + int category = sources.get(i).getCategory(); + categoriesForIndex[i] = category; + rangeLength += categoriesForIndex[i]; + } + + // the highest score of fits tried so far + int maxScore = Integer.MIN_VALUE; + // the high scoring range's start + int resultRangeStart = -1; + // by source index: set of sources needing moves in high scoring plan + BitSet resultMovesRequired = null; + + /* + * First, go through each source that's already been mapped. Try + * to center the range around the rop register this source is mapped + * to. + */ + int rangeStartOffset = 0; + for (int i = 0; i < szSources; i++) { + int ssaCenterReg = sources.get(i).getReg(); + + if (i != 0) { + rangeStartOffset -= categoriesForIndex[i - 1]; + } + if (!ssaRegsMapped.get(ssaCenterReg)) { + continue; + } + + int rangeStart = mapper.oldToNew(ssaCenterReg) + rangeStartOffset; + + if (rangeStart < 0 || spansParamRange(rangeStart, rangeLength)) { + continue; + } + + BitSet curMovesRequired = new BitSet(szSources); + + int fitWidth + = fitPlanForRange(rangeStart, insn, categoriesForIndex, + curMovesRequired); + + if (fitWidth < 0) { + continue; + } + + int score = fitWidth - curMovesRequired.cardinality(); + + if (score > maxScore) { + maxScore = score; + resultRangeStart = rangeStart; + resultMovesRequired = curMovesRequired; + } + + if (fitWidth == rangeLength) { + // We can't do any better than this, so stop here + break; + } + } + + /* + * If we were unable to find a plan for a fit centered around + * an already-mapped source, just try to find a range of + * registers we can move the range into. + */ + + if (resultRangeStart == -1) { + resultMovesRequired = new BitSet(szSources); + + resultRangeStart = findAnyFittingRange(insn, rangeLength, + categoriesForIndex, resultMovesRequired); + } + + /* + * Now, insert any moves required. + */ + + for (int i = resultMovesRequired.nextSetBit(0); i >= 0; + i = resultMovesRequired.nextSetBit(i+1)) { + insn.changeOneSource(i, insertMoveBefore(insn, sources.get(i))); + } + + return resultRangeStart; + } + + /** + * Finds an unreserved range that will fit the sources of the + * specified instruction. Does not bother trying to center the range + * around an already-mapped source register; + * + * @param insn {@code non-null;} insn to build range for + * @param rangeLength {@code >=0;} length required in register units + * @param categoriesForIndex {@code non-null;} indexed by source index; + * the category for each source + * @param outMovesRequired {@code non-null;} an output parameter indexed by + * source index that will contain the set of sources which need + * moves inserted + * @return the rop register that starts the fitting range + */ + private int findAnyFittingRange(NormalSsaInsn insn, int rangeLength, + int[] categoriesForIndex, BitSet outMovesRequired) { + int rangeStart = paramRangeEnd; + while (true) { + rangeStart = findNextUnreservedRopReg(rangeStart, rangeLength); + int fitWidth + = fitPlanForRange(rangeStart, insn, + categoriesForIndex, outMovesRequired); + + if (fitWidth >= 0) { + break; + } + rangeStart++; + outMovesRequired.clear(); + } + return rangeStart; + } + + /** + * Attempts to build a plan for fitting a range of sources into rop + * registers. + * + * @param ropReg {@code >= 0;} rop reg that begins range + * @param insn {@code non-null;} insn to plan range for + * @param categoriesForIndex {@code non-null;} indexed by source index; + * the category for each source + * @param outMovesRequired {@code non-null;} an output parameter indexed by + * source index that will contain the set of sources which need + * moves inserted + * @return the width of the fit that that does not involve added moves or + * {@code -1} if "no fit possible" + */ + private int fitPlanForRange(int ropReg, NormalSsaInsn insn, + int[] categoriesForIndex, BitSet outMovesRequired) { + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + int fitWidth = 0; + IntSet liveOut = insn.getBlock().getLiveOutRegs(); + RegisterSpecList liveOutSpecs = ssaSetToSpecs(liveOut); + + // An SSA reg may only be mapped into a range once. + BitSet seen = new BitSet(ssaMeth.getRegCount()); + + for (int i = 0; i < szSources ; i++) { + RegisterSpec ssaSpec = sources.get(i); + int ssaReg = ssaSpec.getReg(); + int category = categoriesForIndex[i]; + + if (i != 0) { + ropReg += categoriesForIndex[i-1]; + } + + if (ssaRegsMapped.get(ssaReg) + && mapper.oldToNew(ssaReg) == ropReg) { + // This is a register that is already mapped appropriately. + fitWidth += category; + } else if (rangeContainsReserved(ropReg, category)) { + fitWidth = -1; + break; + } else if (!ssaRegsMapped.get(ssaReg) + && canMapReg(ssaSpec, ropReg) + && !seen.get(ssaReg)) { + // This is a register that can be mapped appropriately. + fitWidth += category; + } else if (!mapper.areAnyPinned(liveOutSpecs, ropReg, category) + && !mapper.areAnyPinned(sources, ropReg, category)) { + /* + * This is a source that can be moved. We can insert a + * move as long as: + * + * * no SSA register pinned to the desired rop reg + * is live out on the block + * + * * no SSA register pinned to desired rop reg is + * a source of this insn (since this may require + * overlapping moves, which we can't presently handle) + */ + + outMovesRequired.set(i); + } else { + fitWidth = -1; + break; + } + + seen.set(ssaReg); + } + return fitWidth; + } + + /** + * Converts a bit set of SSA registers into a RegisterSpecList containing + * the definition specs of all the registers. + * + * @param ssaSet {@code non-null;} set of SSA registers + * @return list of RegisterSpecs as noted above + */ + RegisterSpecList ssaSetToSpecs(IntSet ssaSet) { + RegisterSpecList result = new RegisterSpecList(ssaSet.elements()); + + IntIterator iter = ssaSet.iterator(); + + int i = 0; + while (iter.hasNext()) { + result.set(i++, getDefinitionSpecForSsaReg(iter.next())); + } + + return result; + } + + /** + * Gets a local item associated with an ssa register, if one exists. + * + * @param ssaReg {@code >= 0;} SSA register + * @return {@code null-ok;} associated local item or null + */ + private LocalItem getLocalItemForReg(int ssaReg) { + for (Map.Entry<LocalItem, ArrayList<RegisterSpec>> entry : + localVariables.entrySet()) { + for (RegisterSpec spec : entry.getValue()) { + if (spec.getReg() == ssaReg) { + return entry.getKey(); + } + } + } + + return null; + } + + /** + * Attempts to map the sources and result of a phi to a common register. + * Will try existing mappings first, from most to least common. If none + * of the registers have mappings yet, a new mapping is created. + */ + private void processPhiInsn(PhiInsn insn) { + RegisterSpec result = insn.getResult(); + int resultReg = result.getReg(); + int category = result.getCategory(); + + RegisterSpecList sources = insn.getSources(); + int sourcesSize = sources.size(); + + // List of phi sources / result that need mapping + ArrayList<RegisterSpec> ssaRegs = new ArrayList<RegisterSpec>(); + + // Track how many times a particular mapping is found + Multiset mapSet = new Multiset(sourcesSize + 1); + + /* + * If the result of the phi has an existing mapping, get it. + * Otherwise, add it to the list of regs that need mapping. + */ + if (ssaRegsMapped.get(resultReg)) { + mapSet.add(mapper.oldToNew(resultReg)); + } else { + ssaRegs.add(result); + } + + for (int i = 0; i < sourcesSize; i++) { + RegisterSpec source = sources.get(i); + SsaInsn def = ssaMeth.getDefinitionForRegister(source.getReg()); + RegisterSpec sourceDef = def.getResult(); + int sourceReg = sourceDef.getReg(); + + /* + * If a source of the phi has an existing mapping, get it. + * Otherwise, add it to the list of regs that need mapping. + */ + if (ssaRegsMapped.get(sourceReg)) { + mapSet.add(mapper.oldToNew(sourceReg)); + } else { + ssaRegs.add(sourceDef); + } + } + + // Try all existing mappings, with the most common ones first + for (int i = 0; i < mapSet.getSize(); i++) { + int maxReg = mapSet.getAndRemoveHighestCount(); + tryMapRegs(ssaRegs, maxReg, category, false); + } + + // Map any remaining unmapped regs with whatever fits + int mapReg = findNextUnreservedRopReg(paramRangeEnd, category); + while (!tryMapRegs(ssaRegs, mapReg, category, false)) { + mapReg = findNextUnreservedRopReg(mapReg + 1, category); + } + } + + // A set that tracks how often elements are added to it. + private static class Multiset { + private final int[] reg; + private final int[] count; + private int size; + + /** + * Constructs an instance. + * + * @param maxSize the maximum distinct elements the set may have + */ + public Multiset(int maxSize) { + reg = new int[maxSize]; + count = new int[maxSize]; + size = 0; + } + + /** + * Adds an element to the set. + * + * @param element element to add + */ + public void add(int element) { + for (int i = 0; i < size; i++) { + if (reg[i] == element) { + count[i]++; + return; + } + } + + reg[size] = element; + count[size] = 1; + size++; + } + + /** + * Searches the set for the element that has been added the most. + * In the case of a tie, the element that was added first is returned. + * Then, it clears the count on that element. The size of the set + * remains unchanged. + * + * @return element with the highest count + */ + public int getAndRemoveHighestCount() { + int maxIndex = -1; + int maxReg = -1; + int maxCount = 0; + + for (int i = 0; i < size; i++) { + if (maxCount < count[i]) { + maxIndex = i; + maxReg = reg[i]; + maxCount = count[i]; + } + } + + count[maxIndex] = 0; + return maxReg; + } + + /** + * Gets the number of distinct elements in the set. + * + * @return size of the set + */ + public int getSize() { + return size; + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java b/dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java new file mode 100644 index 00000000..c679d61e --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/IdenticalBlockCombiner.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.CstInsn; +import com.android.jack.dx.rop.code.Insn; +import com.android.jack.dx.rop.code.InsnList; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.SwitchInsn; +import com.android.jack.dx.util.IntList; + +import java.util.BitSet; + +/** + * Searches for basic blocks that all have the same successor and insns + * but different predecessors. These blocks are then combined into a single + * block and the now-unused blocks are deleted. These identical blocks + * frequently are created when catch blocks are edge-split. + */ +public class IdenticalBlockCombiner { + private final RopMethod ropMethod; + private final BasicBlockList blocks; + private final BasicBlockList newBlocks; + + /** + * Constructs instance. Call {@code process()} to run. + * + * @param rm {@code non-null;} instance to process + */ + public IdenticalBlockCombiner(RopMethod rm) { + ropMethod = rm; + blocks = ropMethod.getBlocks(); + newBlocks = blocks.getMutableCopy(); + } + + /** + * Runs algorithm. TODO: This is n^2, and could be made linear-ish with + * a hash. In particular, hash the contents of each block and only + * compare blocks with the same hash. + * + * @return {@code non-null;} new method that has been processed + */ + public RopMethod process() { + int szBlocks = blocks.size(); + // indexed by label + BitSet toDelete = new BitSet(blocks.getMaxLabel()); + + // For each non-deleted block... + for (int bindex = 0; bindex < szBlocks; bindex++) { + BasicBlock b = blocks.get(bindex); + + if (toDelete.get(b.getLabel())) { + // doomed block + continue; + } + + IntList preds = ropMethod.labelToPredecessors(b.getLabel()); + + // ...look at all of it's predecessors that have only one succ... + int szPreds = preds.size(); + for (int i = 0; i < szPreds; i++) { + int iLabel = preds.get(i); + + BasicBlock iBlock = blocks.labelToBlock(iLabel); + + if (toDelete.get(iLabel) + || iBlock.getSuccessors().size() > 1 + || iBlock.getFirstInsn().getOpcode().getOpcode() == + RegOps.MOVE_RESULT) { + continue; + } + + IntList toCombine = new IntList(); + + // ...and see if they can be combined with any other preds... + for (int j = i + 1; j < szPreds; j++) { + int jLabel = preds.get(j); + BasicBlock jBlock = blocks.labelToBlock(jLabel); + + if (jBlock.getSuccessors().size() == 1 + && compareInsns(iBlock, jBlock)) { + + toCombine.add(jLabel); + toDelete.set(jLabel); + } + } + + combineBlocks(iLabel, toCombine); + } + } + + for (int i = szBlocks - 1; i >= 0; i--) { + if (toDelete.get(newBlocks.get(i).getLabel())) { + newBlocks.set(i, null); + } + } + + newBlocks.shrinkToFit(); + newBlocks.setImmutable(); + + return new RopMethod(newBlocks, ropMethod.getFirstLabel()); + } + + /** + * Helper method to compare the contents of two blocks. + * + * @param a {@code non-null;} a block to compare + * @param b {@code non-null;} another block to compare + * @return {@code true} iff the two blocks' instructions are the same + */ + private static boolean compareInsns(BasicBlock a, BasicBlock b) { + return a.getInsns().contentEquals(b.getInsns()); + } + + /** + * Combines blocks proven identical into one alpha block, re-writing + * all of the successor links that point to the beta blocks to point + * to the alpha block instead. + * + * @param alphaLabel block that will replace all the beta block + * @param betaLabels label list of blocks to combine + */ + private void combineBlocks(int alphaLabel, IntList betaLabels) { + int szBetas = betaLabels.size(); + + for (int i = 0; i < szBetas; i++) { + int betaLabel = betaLabels.get(i); + BasicBlock bb = blocks.labelToBlock(betaLabel); + IntList preds = ropMethod.labelToPredecessors(bb.getLabel()); + int szPreds = preds.size(); + + for (int j = 0; j < szPreds; j++) { + BasicBlock predBlock = newBlocks.labelToBlock(preds.get(j)); + replaceSucc(predBlock, betaLabel, alphaLabel); + } + } + } + + /** + * Replaces one of a block's successors with a different label. Constructs + * an updated BasicBlock instance and places it in {@code newBlocks}. + * + * @param block block to replace + * @param oldLabel label of successor to replace + * @param newLabel label of new successor + */ + private void replaceSucc(BasicBlock block, int oldLabel, int newLabel) { + IntList newSuccessors = block.getSuccessors().mutableCopy(); + int newPrimarySuccessor; + + newSuccessors.set(newSuccessors.indexOf(oldLabel), newLabel); + newPrimarySuccessor = block.getPrimarySuccessor(); + + if (newPrimarySuccessor == oldLabel) { + newPrimarySuccessor = newLabel; + } + + newSuccessors.setImmutable(); + + BasicBlock newBB = new BasicBlock(block.getLabel(), + block.getInsns(), newSuccessors, newPrimarySuccessor); + + newBlocks.set(newBlocks.indexOfLabel(block.getLabel()), newBB); + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java b/dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java new file mode 100644 index 00000000..00a2c699 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/InterferenceGraph.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.SetFactory; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.BitIntSet; +import com.android.jack.dx.util.IntSet; +import com.android.jack.dx.util.ListIntSet; + +import java.util.BitSet; +import java.util.List; +import java.util.ArrayList; + +/** + * A register interference graph + */ +public class InterferenceGraph { + /** + * {@code non-null;} interference graph, indexed by register in + * both dimensions + */ + private final ArrayList<IntSet> interference; + + /** + * Creates a new graph. + * + * @param countRegs {@code >= 0;} the start count of registers in + * the namespace. New registers can be added subsequently. + */ + public InterferenceGraph(int countRegs) { + interference = new ArrayList<IntSet>(countRegs); + + for (int i = 0; i < countRegs; i++) { + interference.add(SetFactory.makeInterferenceSet(countRegs)); + } + } + + /** + * Adds a register pair to the interference/liveness graph. Parameter + * order is insignificant. + * + * @param regV one register index + * @param regW another register index + */ + public void add(int regV, int regW) { + ensureCapacity(Math.max(regV, regW) + 1); + + interference.get(regV).add(regW); + interference.get(regW).add(regV); + } + + /** + * Dumps interference graph to stdout for debugging. + */ + public void dumpToStdout() { + int oldRegCount = interference.size(); + + for (int i = 0; i < oldRegCount; i++) { + StringBuilder sb = new StringBuilder(); + + sb.append("Reg " + i + ":" + interference.get(i).toString()); + + System.out.println(sb.toString()); + } + } + + /** + * Merges the interference set for a register into a given bit set + * + * @param reg {@code >= 0;} register + * @param set {@code non-null;} interference set; will be merged + * with set for given register + */ + public void mergeInterferenceSet(int reg, IntSet set) { + if (reg < interference.size()) { + set.merge(interference.get(reg)); + } + } + + /** + * Ensures that the interference graph is appropriately sized. + * + * @param size requested minumum size + */ + private void ensureCapacity(int size) { + int countRegs = interference.size(); + + interference.ensureCapacity(size); + + for (int i = countRegs; i < size; i++) { + interference.add(SetFactory.makeInterferenceSet(size)); + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java b/dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java new file mode 100644 index 00000000..44c9964c --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/LivenessAnalyzer.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; + +import java.util.BitSet; +import java.util.List; +import java.util.ArrayList; + +/** + * From Appel "Modern Compiler Implementation in Java" algorithm 19.17 + * Calculate the live ranges for register {@code reg}.<p> + * + * v = regV <p> + * s = insn <p> + * M = visitedBlocks <p> + */ +public class LivenessAnalyzer { + /** + * {@code non-null;} index by basic block indexed set of basic blocks + * that have already been visited. "M" as written in the original Appel + * algorithm. + */ + private final BitSet visitedBlocks; + + /** + * {@code non-null;} set of blocks remaing to visit as "live out as block" + */ + private final BitSet liveOutBlocks; + + /** + * {@code >=0;} SSA register currently being analyzed. + * "v" in the original Appel algorithm. + */ + private final int regV; + + /** method to process */ + private final SsaMethod ssaMeth; + + /** interference graph being updated */ + private final InterferenceGraph interference; + + /** block "n" in Appel 19.17 */ + private SsaBasicBlock blockN; + + /** index of statement {@code s} in {@code blockN} */ + private int statementIndex; + + /** the next function to call */ + private NextFunction nextFunction; + + /** constants for {@link #nextFunction} */ + private static enum NextFunction { + LIVE_IN_AT_STATEMENT, + LIVE_OUT_AT_STATEMENT, + LIVE_OUT_AT_BLOCK, + DONE; + } + + /** + * Runs register liveness algorithm for a method, updating the + * live in/out information in {@code SsaBasicBlock} instances and + * returning an interference graph. + * + * @param ssaMeth {@code non-null;} method to process + * @return {@code non-null;} interference graph indexed by SSA + * registers in both directions + */ + public static InterferenceGraph constructInterferenceGraph( + SsaMethod ssaMeth) { + int szRegs = ssaMeth.getRegCount(); + InterferenceGraph interference = new InterferenceGraph(szRegs); + + for (int i = 0; i < szRegs; i++) { + new LivenessAnalyzer(ssaMeth, i, interference).run(); + } + + coInterferePhis(ssaMeth, interference); + + return interference; + } + + /** + * Makes liveness analyzer instance for specific register. + * + * @param ssaMeth {@code non-null;} method to process + * @param reg register whose liveness to analyze + * @param interference {@code non-null;} indexed by SSA reg in + * both dimensions; graph to update + * + */ + private LivenessAnalyzer(SsaMethod ssaMeth, int reg, + InterferenceGraph interference) { + int blocksSz = ssaMeth.getBlocks().size(); + + this.ssaMeth = ssaMeth; + this.regV = reg; + visitedBlocks = new BitSet(blocksSz); + liveOutBlocks = new BitSet(blocksSz); + this.interference = interference; + } + + /** + * The algorithm in Appel is presented in partial tail-recursion + * form. Obviously, that's not efficient in java, so this function + * serves as the dispatcher instead. + */ + private void handleTailRecursion() { + while (nextFunction != NextFunction.DONE) { + switch (nextFunction) { + case LIVE_IN_AT_STATEMENT: + nextFunction = NextFunction.DONE; + liveInAtStatement(); + break; + + case LIVE_OUT_AT_STATEMENT: + nextFunction = NextFunction.DONE; + liveOutAtStatement(); + break; + + case LIVE_OUT_AT_BLOCK: + nextFunction = NextFunction.DONE; + liveOutAtBlock(); + break; + + default: + } + } + } + + /** + * From Appel algorithm 19.17. + */ + public void run() { + List<SsaInsn> useList = ssaMeth.getUseListForRegister(regV); + + for (SsaInsn insn : useList) { + nextFunction = NextFunction.DONE; + + if (insn instanceof PhiInsn) { + // If s is a phi-function with V as it's ith argument. + PhiInsn phi = (PhiInsn) insn; + + for (SsaBasicBlock pred : + phi.predBlocksForReg(regV, ssaMeth)) { + blockN = pred; + + nextFunction = NextFunction.LIVE_OUT_AT_BLOCK; + handleTailRecursion(); + } + } else { + // Special management for registers of category 2, indeed they use + // two 32-bits registers thus there is an implicit interference between + // the result register and operands contrary to category 1 where there + // is no writing of registers before all operands are read. + RegisterSpec resultSpec = insn.getResult(); + if (resultSpec != null && resultSpec.getCategory() == 2 + && insn.getSources().specForRegister(regV).getCategory() == 2) { + interference.add(regV, resultSpec.getReg()); + } + + blockN = insn.getBlock(); + statementIndex = blockN.getInsns().indexOf(insn); + + if (statementIndex < 0) { + throw new RuntimeException( + "insn not found in it's own block"); + } + + nextFunction = NextFunction.LIVE_IN_AT_STATEMENT; + handleTailRecursion(); + } + } + + int nextLiveOutBlock; + while ((nextLiveOutBlock = liveOutBlocks.nextSetBit(0)) >= 0) { + blockN = ssaMeth.getBlocks().get(nextLiveOutBlock); + liveOutBlocks.clear(nextLiveOutBlock); + nextFunction = NextFunction.LIVE_OUT_AT_BLOCK; + handleTailRecursion(); + } + } + + /** + * "v is live-out at n." + */ + private void liveOutAtBlock() { + if (! visitedBlocks.get(blockN.getIndex())) { + visitedBlocks.set(blockN.getIndex()); + + blockN.addLiveOut(regV); + + ArrayList<SsaInsn> insns; + + insns = blockN.getInsns(); + + // Live out at last statement in blockN + statementIndex = insns.size() - 1; + nextFunction = NextFunction.LIVE_OUT_AT_STATEMENT; + } + } + + /** + * "v is live-in at s." + */ + private void liveInAtStatement() { + // if s is the first statement in block N + if (statementIndex == 0) { + // v is live-in at n + blockN.addLiveIn(regV); + + BitSet preds = blockN.getPredecessors(); + + liveOutBlocks.or(preds); + } else { + // Let s' be the statement preceeding s + statementIndex -= 1; + nextFunction = NextFunction.LIVE_OUT_AT_STATEMENT; + } + } + + /** + * "v is live-out at s." + */ + private void liveOutAtStatement() { + SsaInsn statement = blockN.getInsns().get(statementIndex); + RegisterSpec rs = statement.getResult(); + + if (!statement.isResultReg(regV)) { + if (rs != null) { + interference.add(regV, rs.getReg()); + } + nextFunction = NextFunction.LIVE_IN_AT_STATEMENT; + } + } + + /** + * Ensures that all the phi result registers for all the phis in the + * same basic block interfere with each other. This is needed since + * the dead code remover has allowed through "dead-end phis" whose + * results are not used except as local assignments. Without this step, + * a the result of a dead-end phi might be assigned the same register + * as the result of another phi, and the phi removal move scheduler may + * generate moves that over-write the live result. + * + * @param ssaMeth {@code non-null;} method to pricess + * @param interference {@code non-null;} interference graph + */ + private static void coInterferePhis(SsaMethod ssaMeth, + InterferenceGraph interference) { + for (SsaBasicBlock b : ssaMeth.getBlocks()) { + List<SsaInsn> phis = b.getPhiInsns(); + + int szPhis = phis.size(); + + for (int i = 0; i < szPhis; i++) { + for (int j = 0; j < szPhis; j++) { + if (i == j) { + continue; + } + + interference.add(phis.get(i).getResult().getReg(), + phis.get(j).getResult().getReg()); + } + } + } + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java b/dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java new file mode 100644 index 00000000..28b20009 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/NullRegisterAllocator.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.ssa.BasicRegisterMapper; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaMethod; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * A register allocator that maps SSA register n to Rop register 2*n, + * essentially preserving the original mapping and remaining agnostic + * about normal or wide categories. Used for debugging. + */ +public class NullRegisterAllocator extends RegisterAllocator { + /** {@inheritDoc} */ + public NullRegisterAllocator(SsaMethod ssaMeth, + InterferenceGraph interference) { + super(ssaMeth, interference); + } + + /** {@inheritDoc} */ + @Override + public boolean wantsParamsMovedHigh() { + // We're not smart enough for this. + return false; + } + + /** {@inheritDoc} */ + @Override + public RegisterMapper allocateRegisters() { + int oldRegCount = ssaMeth.getRegCount(); + + BasicRegisterMapper mapper = new BasicRegisterMapper(oldRegCount); + + for (int i = 0; i < oldRegCount; i++) { + mapper.addMapping(i, i*2, 2); + } + + return mapper; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java b/dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java new file mode 100644 index 00000000..e81a9d68 --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/RegisterAllocator.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.PlainInsn; +import com.android.jack.dx.rop.code.RegOps; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.rop.code.SourcePosition; +import com.android.jack.dx.ssa.NormalSsaInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.IntIterator; +import com.android.jack.dx.util.IntSet; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * Base class of all register allocators. + */ +public abstract class RegisterAllocator { + /** method being processed */ + protected final SsaMethod ssaMeth; + + /** interference graph, indexed by register in both dimensions */ + protected final InterferenceGraph interference; + + /** + * Creates an instance. Call {@code allocateRegisters} to run. + * @param ssaMeth method to process. + * @param interference Interference graph, indexed by register in both + * dimensions. + */ + public RegisterAllocator(SsaMethod ssaMeth, + InterferenceGraph interference) { + this.ssaMeth = ssaMeth; + this.interference = interference; + } + + /** + * Indicates whether the method params were allocated at the bottom + * of the namespace, and thus should be moved up to the top of the + * namespace after phi removal. + * + * @return {@code true} if params should be moved from low to high + */ + public abstract boolean wantsParamsMovedHigh(); + + /** + * Runs the algorithm. + * + * @return a register mapper to apply to the {@code SsaMethod} + */ + public abstract RegisterMapper allocateRegisters(); + + /** + * Returns the category (width) of the definition site of the register. + * Returns {@code 1} for undefined registers. + * + * @param reg register + * @return {@code 1..2} + */ + protected final int getCategoryForSsaReg(int reg) { + SsaInsn definition = ssaMeth.getDefinitionForRegister(reg); + + if (definition == null) { + // an undefined reg + return 1; + } else { + return definition.getResult().getCategory(); + } + } + + /** + * Returns the RegisterSpec of the definition of the register. + * + * @param reg {@code >= 0;} SSA register + * @return definition spec of the register or null if it is never defined + * (for the case of "version 0" SSA registers) + */ + protected final RegisterSpec getDefinitionSpecForSsaReg(int reg) { + SsaInsn definition = ssaMeth.getDefinitionForRegister(reg); + + return definition == null ? null : definition.getResult(); + } + + /** + * Returns true if the definition site of this register is a + * move-param (ie, this is a method parameter). + * + * @param reg register in question + * @return {@code true} if this is a method parameter + */ + protected boolean isDefinitionMoveParam(int reg) { + SsaInsn defInsn = ssaMeth.getDefinitionForRegister(reg); + + if (defInsn instanceof NormalSsaInsn) { + NormalSsaInsn ndefInsn = (NormalSsaInsn) defInsn; + + return ndefInsn.getOpcode().getOpcode() == RegOps.MOVE_PARAM; + } + + return false; + } + + /** + * Inserts a move instruction for a specified SSA register before a + * specified instruction, creating a new SSA register and adjusting the + * interference graph in the process. The insn currently must be the + * last insn in a block. + * + * @param insn {@code non-null;} insn to insert move before, must + * be last insn in block + * @param reg {@code non-null;} SSA register to duplicate + * @return {@code non-null;} spec of new SSA register created by move + */ + protected final RegisterSpec insertMoveBefore(SsaInsn insn, + RegisterSpec reg) { + SsaBasicBlock block = insn.getBlock(); + ArrayList<SsaInsn> insns = block.getInsns(); + int insnIndex = insns.indexOf(insn); + + if (insnIndex < 0) { + throw new IllegalArgumentException ( + "specified insn is not in this block"); + } + + if (insnIndex != insns.size() - 1) { + /* + * Presently, the interference updater only works when + * adding before the last insn, and the last insn must have no + * result + */ + throw new IllegalArgumentException( + "Adding move here not supported:" + insn.toHuman()); + } + + /* + * Get new register and make new move instruction. + */ + + // The new result must not have an associated local variable. + RegisterSpec newRegSpec = RegisterSpec.make(ssaMeth.makeNewSsaReg(), + reg.getTypeBearer()); + + SsaInsn toAdd = SsaInsn.makeFromRop( + new PlainInsn(Rops.opMove(newRegSpec.getType()), + SourcePosition.NO_INFO, newRegSpec, + RegisterSpecList.make(reg)), block); + + insns.add(insnIndex, toAdd); + + int newReg = newRegSpec.getReg(); + + /* + * Adjust interference graph based on what's live out of the current + * block and what's used by the final instruction. + */ + + IntSet liveOut = block.getLiveOutRegs(); + IntIterator liveOutIter = liveOut.iterator(); + + while (liveOutIter.hasNext()) { + interference.add(newReg, liveOutIter.next()); + } + + // Everything that's a source in the last insn interferes. + RegisterSpecList sources = insn.getSources(); + int szSources = sources.size(); + + for (int i = 0; i < szSources; i++) { + interference.add(newReg, sources.get(i).getReg()); + } + + ssaMeth.onInsnsChanged(); + + return newRegSpec; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/back/SsaToRop.java b/dx/src/com/android/jack/dx/ssa/back/SsaToRop.java new file mode 100644 index 00000000..2c63a55f --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/back/SsaToRop.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa.back; + +import com.android.jack.dx.rop.code.BasicBlock; +import com.android.jack.dx.rop.code.BasicBlockList; +import com.android.jack.dx.rop.code.InsnList; +import com.android.jack.dx.rop.code.RegisterSpec; +import com.android.jack.dx.rop.code.RegisterSpecList; +import com.android.jack.dx.rop.code.Rop; +import com.android.jack.dx.rop.code.RopMethod; +import com.android.jack.dx.rop.code.Rops; +import com.android.jack.dx.ssa.BasicRegisterMapper; +import com.android.jack.dx.ssa.PhiInsn; +import com.android.jack.dx.ssa.RegisterMapper; +import com.android.jack.dx.ssa.SsaBasicBlock; +import com.android.jack.dx.ssa.SsaInsn; +import com.android.jack.dx.ssa.SsaMethod; +import com.android.jack.dx.util.Hex; +import com.android.jack.dx.util.IntList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Comparator; + +/** + * Converts a method in SSA form to ROP form. + */ +public class SsaToRop { + /** local debug flag */ + private static final boolean DEBUG = false; + + /** {@code non-null;} method to process */ + private final SsaMethod ssaMeth; + + /** + * {@code true} if the converter should attempt to minimize + * the rop-form register count + */ + private final boolean minimizeRegisters; + + /** {@code non-null;} interference graph */ + private final InterferenceGraph interference; + + /** + * Converts a method in SSA form to ROP form. + * + * @param ssaMeth {@code non-null;} method to process + * @param minimizeRegisters {@code true} if the converter should + * attempt to minimize the rop-form register count + * @return {@code non-null;} rop-form output + */ + public static RopMethod convertToRopMethod(SsaMethod ssaMeth, + boolean minimizeRegisters) { + return new SsaToRop(ssaMeth, minimizeRegisters).convert(); + } + + /** + * Constructs an instance. + * + * @param ssaMeth {@code non-null;} method to process + * @param minimizeRegisters {@code true} if the converter should + * attempt to minimize the rop-form register count + */ + private SsaToRop(SsaMethod ssaMethod, boolean minimizeRegisters) { + this.minimizeRegisters = minimizeRegisters; + this.ssaMeth = ssaMethod; + this.interference = + LivenessAnalyzer.constructInterferenceGraph(ssaMethod); + } + + /** + * Performs the conversion. + * + * @return {@code non-null;} rop-form output + */ + private RopMethod convert() { + if (DEBUG) { + interference.dumpToStdout(); + } + + // These are other allocators for debugging or historical comparison: + // allocator = new NullRegisterAllocator(ssaMeth, interference); + // allocator = new FirstFitAllocator(ssaMeth, interference); + + RegisterAllocator allocator = + new FirstFitLocalCombiningAllocator(ssaMeth, interference, + minimizeRegisters); + + RegisterMapper mapper = allocator.allocateRegisters(); + + if (DEBUG) { + System.out.println("Printing reg map"); + System.out.println(((BasicRegisterMapper)mapper).toHuman()); + } + + ssaMeth.setBackMode(); + + ssaMeth.mapRegisters(mapper); + + removePhiFunctions(); + + if (allocator.wantsParamsMovedHigh()) { + moveParametersToHighRegisters(); + } + + removeEmptyGotos(); + + RopMethod ropMethod = new RopMethod(convertBasicBlocks(), + ssaMeth.blockIndexToRopLabel(ssaMeth.getEntryBlockIndex())); + ropMethod = new IdenticalBlockCombiner(ropMethod).process(); + + return ropMethod; + } + + /** + * Removes all blocks containing only GOTOs from the control flow. + * Although much of this work will be done later when converting + * from rop to dex, not all simplification cases can be handled + * there. Furthermore, any no-op block between the exit block and + * blocks containing the real return or throw statements must be + * removed. + */ + private void removeEmptyGotos() { + final ArrayList<SsaBasicBlock> blocks = ssaMeth.getBlocks(); + + ssaMeth.forEachBlockDepthFirst(false, new SsaBasicBlock.Visitor() { + public void visitBlock(SsaBasicBlock b, SsaBasicBlock parent) { + ArrayList<SsaInsn> insns = b.getInsns(); + + if ((insns.size() == 1) + && (insns.get(0).getOpcode() == Rops.GOTO)) { + BitSet preds = (BitSet) b.getPredecessors().clone(); + + for (int i = preds.nextSetBit(0); i >= 0; + i = preds.nextSetBit(i + 1)) { + SsaBasicBlock pb = blocks.get(i); + pb.replaceSuccessor(b.getIndex(), + b.getPrimarySuccessorIndex()); + } + } + } + }); + } + + /** + * See Appel 19.6. To remove the phi instructions in an edge-split + * SSA representation we know we can always insert a move in a + * predecessor block. + */ + private void removePhiFunctions() { + ArrayList<SsaBasicBlock> blocks = ssaMeth.getBlocks(); + + for (SsaBasicBlock block : blocks) { + // Add moves in all the pred blocks for each phi insn. + block.forEachPhiInsn(new PhiVisitor(blocks)); + + // Delete the phi insns. + block.removeAllPhiInsns(); + } + + /* + * After all move insns have been added, sort them so they don't + * destructively interfere. + */ + for (SsaBasicBlock block : blocks) { + block.scheduleMovesFromPhis(); + } + } + + /** + * Helper for {@link #removePhiFunctions}: PhiSuccessorUpdater for + * adding move instructions to predecessors based on phi insns. + */ + private static class PhiVisitor implements PhiInsn.Visitor { + private final ArrayList<SsaBasicBlock> blocks; + + public PhiVisitor(ArrayList<SsaBasicBlock> blocks) { + this.blocks = blocks; + } + + public void visitPhiInsn(PhiInsn insn) { + RegisterSpecList sources = insn.getSources(); + RegisterSpec result = insn.getResult(); + int sz = sources.size(); + + for (int i = 0; i < sz; i++) { + RegisterSpec source = sources.get(i); + SsaBasicBlock predBlock = blocks.get( + insn.predBlockIndexForSourcesIndex(i)); + + predBlock.addMoveToEnd(result, source); + } + } + } + + /** + * Moves the parameter registers, which allocateRegisters() places + * at the bottom of the frame, up to the top of the frame to match + * Dalvik calling convention. + */ + private void moveParametersToHighRegisters() { + int paramWidth = ssaMeth.getParamWidth(); + BasicRegisterMapper mapper + = new BasicRegisterMapper(ssaMeth.getRegCount()); + int regCount = ssaMeth.getRegCount(); + + for (int i = 0; i < regCount; i++) { + if (i < paramWidth) { + mapper.addMapping(i, regCount - paramWidth + i, 1); + } else { + mapper.addMapping(i, i - paramWidth, 1); + } + } + + if (DEBUG) { + System.out.printf("Moving %d registers from 0 to %d\n", + paramWidth, regCount - paramWidth); + } + + ssaMeth.mapRegisters(mapper); + } + + /** + * @return rop-form basic block list + */ + private BasicBlockList convertBasicBlocks() { + ArrayList<SsaBasicBlock> blocks = ssaMeth.getBlocks(); + + // Exit block may be null. + SsaBasicBlock exitBlock = ssaMeth.getExitBlock(); + + ssaMeth.computeReachability(); + int ropBlockCount = ssaMeth.getCountReachableBlocks(); + + // Don't count the exit block, if it exists and is reachable. + ropBlockCount -= (exitBlock != null && exitBlock.isReachable()) ? 1 : 0; + + BasicBlockList result = new BasicBlockList(ropBlockCount); + + // Convert all the reachable blocks except the exit block. + int ropBlockIndex = 0; + for (SsaBasicBlock b : blocks) { + if (b.isReachable() && b != exitBlock) { + result.set(ropBlockIndex++, convertBasicBlock(b)); + } + } + + // The exit block, which is discarded, must do nothing. + if (exitBlock != null && exitBlock.getInsns().size() != 0) { + throw new RuntimeException( + "Exit block must have no insns when leaving SSA form"); + } + + return result; + } + + /** + * Validates that a basic block is a valid end predecessor. It must + * end in a RETURN or a THROW. Throws a runtime exception on error. + * + * @param b {@code non-null;} block to validate + * @throws RuntimeException on error + */ + private void verifyValidExitPredecessor(SsaBasicBlock b) { + ArrayList<SsaInsn> insns = b.getInsns(); + SsaInsn lastInsn = insns.get(insns.size() - 1); + Rop opcode = lastInsn.getOpcode(); + + if (opcode.getBranchingness() != Rop.BRANCH_RETURN + && opcode != Rops.THROW) { + throw new RuntimeException("Exit predecessor must end" + + " in valid exit statement."); + } + } + + /** + * Converts a single basic block to rop form. + * + * @param block SSA block to process + * @return {@code non-null;} ROP block + */ + private BasicBlock convertBasicBlock(SsaBasicBlock block) { + IntList successorList = block.getRopLabelSuccessorList(); + int primarySuccessorLabel = block.getPrimarySuccessorRopLabel(); + + // Filter out any reference to the SSA form's exit block. + + // Exit block may be null. + SsaBasicBlock exitBlock = ssaMeth.getExitBlock(); + int exitRopLabel = (exitBlock == null) ? -1 : exitBlock.getRopLabel(); + + if (successorList.contains(exitRopLabel)) { + if (successorList.size() > 1) { + throw new RuntimeException( + "Exit predecessor must have no other successors" + + Hex.u2(block.getRopLabel())); + } else { + successorList = IntList.EMPTY; + primarySuccessorLabel = -1; + + verifyValidExitPredecessor(block); + } + } + + successorList.setImmutable(); + + BasicBlock result = new BasicBlock( + block.getRopLabel(), convertInsns(block.getInsns()), + successorList, + primarySuccessorLabel); + + return result; + } + + /** + * Converts an insn list to rop form. + * + * @param ssaInsns {@code non-null;} old instructions + * @return {@code non-null;} immutable instruction list + */ + private InsnList convertInsns(ArrayList<SsaInsn> ssaInsns) { + int insnCount = ssaInsns.size(); + InsnList result = new InsnList(insnCount); + + for (int i = 0; i < insnCount; i++) { + result.set(i, ssaInsns.get(i).toRopInsn()); + } + + result.setImmutable(); + + return result; + } + + /** + * <b>Note:</b> This method is not presently used. + * + * @return a list of registers ordered by most-frequently-used to + * least-frequently-used. Each register is listed once and only + * once. + */ + public int[] getRegistersByFrequency() { + int regCount = ssaMeth.getRegCount(); + Integer[] ret = new Integer[regCount]; + + for (int i = 0; i < regCount; i++) { + ret[i] = i; + } + + Arrays.sort(ret, new Comparator<Integer>() { + public int compare(Integer o1, Integer o2) { + return ssaMeth.getUseListForRegister(o2).size() + - ssaMeth.getUseListForRegister(o1).size(); + } + }); + + int result[] = new int[regCount]; + + for (int i = 0; i < regCount; i++) { + result[i] = ret[i]; + } + + return result; + } +} diff --git a/dx/src/com/android/jack/dx/ssa/package-info.java b/dx/src/com/android/jack/dx/ssa/package-info.java new file mode 100644 index 00000000..eb704fde --- /dev/null +++ b/dx/src/com/android/jack/dx/ssa/package-info.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.jack.dx.ssa; + +/** + * <h1>An introduction to SSA Form</h1> + * + * This package contains classes associated with dx's {@code SSA} + * intermediate form. This form is a static-single-assignment representation of + * Rop-form a method with Rop-form-like instructions (with the addition of a + * {@link PhiInsn phi instriction}. This form is intended to make it easy to + * implement basic optimization steps and register allocation so that a + * reasonably efficient register machine representation can be produced from a + * stack machine source bytecode.<p> + * + * <h2>Key Classes</h2> + * + * <h3>Classes related to conversion and lifetime</h3> + * <ul> + * <li> {@link Optimizer} is a singleton class containing methods for + * converting, optimizing, and then back-converting Rop-form methods. It's the + * typical gateway into the rest of the package. + * <li> {@link SsaConverter} converts a Rop-form method to SSA form. + * <li> {@link SsaToRop} converts an SSA-form method back to Rop form. + * </ul> + * + * <h3>Classes related to method representation</h3> + * <ul> + * <li> A {@link SsaMethod} instance represents a method. + * <li> A {@link SsaBasicBlock} instance represents a basic block, whose + * semantics are quite similar to basic blocks in + * {@link com.android.jack.dx.rop Rop form}. + * <li> {@link PhiInsn} instances represent "phi" operators defined in SSA + * literature. They must be the first N instructions in a basic block. + * <li> {@link NormalSsaInsn} instances represent instructions that directly + * correspond to {@code Rop} form. + * </ul> + * + * <h3>Classes related to optimization steps</h3> + * <ul> + * <li> {@link MoveParamCombiner} is a simple step that ensures each method + * parameter is represented by at most one SSA register. + * <li> {@link SCCP} is a (partially implemented) sparse-conditional + * constant propogator. + * <li> {@link LiteralOpUpgrader} is a step that attempts to use constant + * information to convert math and comparison instructions into + * constant-bearing "literal ops" in cases where they can be represented in the + * output form (see {@link TranslationAdvice#hasConstantOperation}). + * <li> {@link ConstCollector} is a step that attempts to trade (modest) + * increased register space for decreased instruction count in cases where + * the same constant value is used repeatedly in a single method. + * <li> {@link DeadCodeRemover} is a dead code remover. This phase must + * always be run to remove unused phi instructions. + * </ul> + * + * <h2>SSA Lifetime</h2> + * The representation of a method in SSA form obeys slightly different + * constraints depending upon whether it is in the process of being converted + * into or out of SSA form. + * + * <h3>Conversion into SSA Form</h3> + * + * {@link SsaConverter#convertToSsaMethod} takes a {@code RopMethod} and + * returns a fully-converted {@code SsaMethod}. The conversion process + * is roughly as follows: + * + * <ol> + * <li> The Rop-form method, its blocks and their instructions are directly + * wrapped in {@code SsaMethod}, {@code SsaBasicBlock} and + * {@code SsaInsn} instances. Nothing else changes. + * <li> Critical control-flow graph edges are {@link SsaConverter#edgeSplit + * split} and new basic blocks inserted as required to meet the constraints + * necessary for the ultimate SSA representation. + * <li> A {@link LocalVariableExtractor} is run to produce a table of + * Rop registers to local variables necessary during phi placement. This + * step could also be done in Rop form and then updated through the preceding + * steps. + * <li> {@code Phi} instructions are {link SsaConverter#placePhiFunctions} + * placed in a semi-pruned fashion, which requires computation of {@link + * Dominators dominance graph} and each node's {@link DomFront + * dominance-frontier set}. + * <li> Finally, source and result registers for all instructions are {@link + * SsaRenamer renamed} such that each assignment is given a unique register + * number (register categories or widths, significant in Rop form, do not + * exist in SSA). Move instructions are eliminated except where necessary + * to preserve local variable assignments. + * </ol> + * + */ |