diff options
Diffstat (limited to 'dx/src/com/android/dx/ssa/SsaRenamer.java')
-rw-r--r-- | dx/src/com/android/dx/ssa/SsaRenamer.java | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/ssa/SsaRenamer.java b/dx/src/com/android/dx/ssa/SsaRenamer.java new file mode 100644 index 0000000..58e4142 --- /dev/null +++ b/dx/src/com/android/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.dx.ssa; + +import com.android.dx.rop.code.LocalItem; +import com.android.dx.rop.code.PlainInsn; +import com.android.dx.rop.code.RegisterSpec; +import com.android.dx.rop.code.RegisterSpecList; +import com.android.dx.rop.code.Rops; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.type.Type; +import com.android.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); + } + } + } +} |