diff options
Diffstat (limited to 'dx/src/com/android/dx/ssa/ConstCollector.java')
-rw-r--r-- | dx/src/com/android/dx/ssa/ConstCollector.java | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/ssa/ConstCollector.java b/dx/src/com/android/dx/ssa/ConstCollector.java new file mode 100644 index 0000000..62d629f --- /dev/null +++ b/dx/src/com/android/dx/ssa/ConstCollector.java @@ -0,0 +1,399 @@ +/* + * 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.dx.ssa; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.PlainCstInsn; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegOps; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstString; +import com.android.dx.rop.cst.TypedConstant; +import com.android.dx.rop.type.StdTypeList; +import com.android.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); + } + } + } +} |