diff options
Diffstat (limited to 'dx/src/com/android/dx/dex/code/RopTranslator.java')
-rw-r--r-- | dx/src/com/android/dx/dex/code/RopTranslator.java | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/dx/src/com/android/dx/dex/code/RopTranslator.java b/dx/src/com/android/dx/dex/code/RopTranslator.java new file mode 100644 index 0000000..a38ea11 --- /dev/null +++ b/dx/src/com/android/dx/dex/code/RopTranslator.java @@ -0,0 +1,872 @@ +/* + * 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.dex.code; + +import com.android.dx.rop.code.BasicBlock; +import com.android.dx.rop.code.BasicBlockList; +import com.android.dx.rop.code.FillArrayDataInsn; +import com.android.dx.rop.code.Insn; +import com.android.dx.rop.code.LocalVariableInfo; +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.RegisterSpecSet; +import com.android.dx.rop.code.Rop; +import com.android.dx.rop.code.RopMethod; +import com.android.dx.rop.code.SourcePosition; +import com.android.dx.rop.code.SwitchInsn; +import com.android.dx.rop.code.ThrowingCstInsn; +import com.android.dx.rop.code.ThrowingInsn; +import com.android.dx.rop.cst.Constant; +import com.android.dx.rop.cst.CstInteger; +import com.android.dx.rop.type.Type; +import com.android.dx.util.Bits; +import com.android.dx.util.IntList; + +import java.util.ArrayList; + +/** + * Translator from {@link RopMethod} to {@link DalvCode}. The {@link + * #translate} method is the thing to call on this class. + */ +public final class RopTranslator { + /** {@code non-null;} method to translate */ + private final RopMethod method; + + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** {@code null-ok;} local variable info to use */ + private final LocalVariableInfo locals; + + /** {@code non-null;} container for all the address objects for the method */ + private final BlockAddresses addresses; + + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} visitor to use during translation */ + private final TranslationVisitor translationVisitor; + + /** {@code >= 0;} register count for the method */ + private final int regCount; + + /** {@code null-ok;} block output order; becomes non-null in {@link #pickOrder} */ + private int[] order; + + /** size, in register units, of all the parameters to this method */ + private final int paramSize; + + /** + * true if the parameters to this method happen to be in proper order + * at the end of the frame (as the optimizer emits them) + */ + private boolean paramsAreInOrder; + + /** + * Translates a {@link RopMethod}. This may modify the given + * input. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + * @return {@code non-null;} the translated version + */ + public static DalvCode translate(RopMethod method, int positionInfo, + LocalVariableInfo locals, int paramSize) { + RopTranslator translator = + new RopTranslator(method, positionInfo, locals, + paramSize); + return translator.translateAndGetResult(); + } + + /** + * Constructs an instance. This method is private. Use {@link #translate}. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + */ + private RopTranslator(RopMethod method, int positionInfo, + LocalVariableInfo locals, int paramSize) { + this.method = method; + this.positionInfo = positionInfo; + this.locals = locals; + this.addresses = new BlockAddresses(method); + this.paramSize = paramSize; + this.order = null; + this.paramsAreInOrder = calculateParamsAreInOrder(method, paramSize); + + BasicBlockList blocks = method.getBlocks(); + int bsz = blocks.size(); + + /* + * Max possible instructions includes three code address + * objects per basic block (to the first and last instruction, + * and just past the end of the block), and the possibility of + * an extra goto at the end of each basic block. + */ + int maxInsns = (bsz * 3) + blocks.getInstructionCount(); + + if (locals != null) { + /* + * If we're tracking locals, then there's could be another + * extra instruction per block (for the locals state at the + * start of the block) as well as one for each interblock + * local introduction. + */ + maxInsns += bsz + locals.getAssignmentCount(); + } + + /* + * If params are not in order, we will need register space + * for them before this is all over... + */ + this.regCount = blocks.getRegCount() + + (paramsAreInOrder ? 0 : this.paramSize); + + this.output = new OutputCollector(maxInsns, bsz * 3, regCount); + + if (locals != null) { + this.translationVisitor = + new LocalVariableAwareTranslationVisitor(output, locals); + } else { + this.translationVisitor = new TranslationVisitor(output); + } + } + + /** + * Checks to see if the move-param instructions that occur in this + * method happen to slot the params in an order at the top of the + * stack frame that matches dalvik's calling conventions. This will + * alway result in "true" for methods that have run through the + * SSA optimizer. + * + * @param paramSize size, in register units, of all the parameters + * to this method + */ + private static boolean calculateParamsAreInOrder(RopMethod method, + final int paramSize) { + final boolean[] paramsAreInOrder = { true }; + final int initialRegCount = method.getBlocks().getRegCount(); + + /* + * We almost could just check the first block here, but the + * {@code cf} layer will put in a second move-param in a + * subsequent block in the case of synchronized methods. + */ + method.getBlocks().forEachInsn(new Insn.BaseVisitor() { + public void visitPlainCstInsn(PlainCstInsn insn) { + if (insn.getOpcode().getOpcode()== RegOps.MOVE_PARAM) { + int param = + ((CstInteger) insn.getConstant()).getValue(); + + paramsAreInOrder[0] = paramsAreInOrder[0] + && ((initialRegCount - paramSize + param) + == insn.getResult().getReg()); + } + } + }); + + return paramsAreInOrder[0]; + } + + /** + * Does the translation and returns the result. + * + * @return {@code non-null;} the result + */ + private DalvCode translateAndGetResult() { + pickOrder(); + outputInstructions(); + + StdCatchBuilder catches = + new StdCatchBuilder(method, order, addresses); + + return new DalvCode(positionInfo, output.getFinisher(), catches); + } + + /** + * Performs initial creation of output instructions based on the + * original blocks. + */ + private void outputInstructions() { + BasicBlockList blocks = method.getBlocks(); + int[] order = this.order; + int len = order.length; + + // Process the blocks in output order. + for (int i = 0; i < len; i++) { + int nextI = i + 1; + int nextLabel = (nextI == order.length) ? -1 : order[nextI]; + outputBlock(blocks.labelToBlock(order[i]), nextLabel); + } + } + + /** + * Helper for {@link #outputInstructions}, which does the processing + * and output of one block. + * + * @param block {@code non-null;} the block to process and output + * @param nextLabel {@code >= -1;} the next block that will be processed, or + * {@code -1} if there is no next block + */ + private void outputBlock(BasicBlock block, int nextLabel) { + // Append the code address for this block. + CodeAddress startAddress = addresses.getStart(block); + output.add(startAddress); + + // Append the local variable state for the block. + if (locals != null) { + RegisterSpecSet starts = locals.getStarts(block); + output.add(new LocalSnapshot(startAddress.getPosition(), + starts)); + } + + /* + * Choose and append an output instruction for each original + * instruction. + */ + translationVisitor.setBlock(block, addresses.getLast(block)); + block.getInsns().forEach(translationVisitor); + + // Insert the block end code address. + output.add(addresses.getEnd(block)); + + // Set up for end-of-block activities. + + int succ = block.getPrimarySuccessor(); + Insn lastInsn = block.getLastInsn(); + + /* + * Check for (and possibly correct for) a non-optimal choice of + * which block will get output next. + */ + + if ((succ >= 0) && (succ != nextLabel)) { + /* + * The block has a "primary successor" and that primary + * successor isn't the next block to be output. + */ + Rop lastRop = lastInsn.getOpcode(); + if ((lastRop.getBranchingness() == Rop.BRANCH_IF) && + (block.getSecondarySuccessor() == nextLabel)) { + /* + * The block ends with an "if" of some sort, and its + * secondary successor (the "then") is in fact the + * next block to output. So, reverse the sense of + * the test, so that we can just emit the next block + * without an interstitial goto. + */ + output.reverseBranch(1, addresses.getStart(succ)); + } else { + /* + * Our only recourse is to add a goto here to get the + * flow to be correct. + */ + TargetInsn insn = + new TargetInsn(Dops.GOTO, lastInsn.getPosition(), + RegisterSpecList.EMPTY, + addresses.getStart(succ)); + output.add(insn); + } + } + } + + /** + * Picks an order for the blocks by doing "trace" analysis. + */ + private void pickOrder() { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + int maxLabel = blocks.getMaxLabel(); + int[] workSet = Bits.makeBitSet(maxLabel); + int[] tracebackSet = Bits.makeBitSet(maxLabel); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + Bits.set(workSet, one.getLabel()); + } + + int[] order = new int[sz]; + int at = 0; + + /* + * Starting with the designated "first label" (that is, the + * first block of the method), add that label to the order, + * and then pick its first as-yet unordered successor to + * immediately follow it, giving top priority to the primary + * (aka default) successor (if any). Keep following successors + * until the trace runs out of possibilities. Then, continue + * by finding an unordered chain containing the first as-yet + * unordered block, and adding it to the order, and so on. + */ + for (int label = method.getFirstLabel(); + label != -1; + label = Bits.findFirst(workSet, 0)) { + + /* + * Attempt to trace backward from the chosen block to an + * as-yet unordered predecessor which lists the chosen + * block as its primary successor, and so on, until we + * fail to find such an unordered predecessor. Start the + * trace with that block. Note that the first block in the + * method has no predecessors, so in that case this loop + * will simply terminate with zero iterations and without + * picking a new starter block. + */ + traceBack: + for (;;) { + IntList preds = method.labelToPredecessors(label); + int psz = preds.size(); + + for (int i = 0; i < psz; i++) { + int predLabel = preds.get(i); + + if (Bits.get(tracebackSet, predLabel)) { + /* + * We found a predecessor loop; stop tracing back + * from here. + */ + break; + } + + if (!Bits.get(workSet, predLabel)) { + // This one's already ordered. + continue; + } + + BasicBlock pred = blocks.labelToBlock(predLabel); + if (pred.getPrimarySuccessor() == label) { + // Found one! + label = predLabel; + Bits.set(tracebackSet, label); + continue traceBack; + } + } + + // Failed to find a better block to start the trace. + break; + } + + /* + * Trace a path from the chosen block to one of its + * unordered successors (hopefully the primary), and so + * on, until we run out of unordered successors. + */ + while (label != -1) { + Bits.clear(workSet, label); + Bits.clear(tracebackSet, label); + order[at] = label; + at++; + + BasicBlock one = blocks.labelToBlock(label); + BasicBlock preferredBlock = blocks.preferredSuccessorOf(one); + + if (preferredBlock == null) { + break; + } + + int preferred = preferredBlock.getLabel(); + int primary = one.getPrimarySuccessor(); + + if (Bits.get(workSet, preferred)) { + /* + * Order the current block's preferred successor + * next, as it has yet to be scheduled. + */ + label = preferred; + } else if ((primary != preferred) && (primary >= 0) + && Bits.get(workSet, primary)) { + /* + * The primary is available, so use that. + */ + label = primary; + } else { + /* + * There's no obvious candidate, so pick the first + * one that's available, if any. + */ + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + label = -1; + for (int i = 0; i < ssz; i++) { + int candidate = successors.get(i); + if (Bits.get(workSet, candidate)) { + label = candidate; + break; + } + } + } + } + } + + if (at != sz) { + // There was a duplicate block label. + throw new RuntimeException("shouldn't happen"); + } + + this.order = order; + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn) { + return getRegs(insn, insn.getResult()); + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @param resultReg {@code null-ok;} the real result to use (ignore the insn's) + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn, + RegisterSpec resultReg) { + RegisterSpecList regs = insn.getSources(); + + if (insn.getOpcode().isCommutative() + && (regs.size() == 2) + && (resultReg.getReg() == regs.get(1).getReg())) { + + /* + * For commutative ops which have two register sources, + * if the second source is the same register as the result, + * swap the sources so that an opcode of form 12x can be selected + * instead of one of form 23x + */ + + regs = RegisterSpecList.make(regs.get(1), regs.get(0)); + } + + if (resultReg == null) { + return regs; + } + + return regs.withFirst(resultReg); + } + + /** + * Instruction visitor class for doing the instruction translation per se. + */ + private class TranslationVisitor implements Insn.Visitor { + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} basic block being worked on */ + private BasicBlock block; + + /** + * {@code null-ok;} code address for the salient last instruction of the + * block (used before switches and throwing instructions) + */ + private CodeAddress lastAddress; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + */ + public TranslationVisitor(OutputCollector output) { + this.output = output; + } + + /** + * Sets the block currently being worked on. + * + * @param block {@code non-null;} the block + * @param lastAddress {@code non-null;} code address for the salient + * last instruction of the block + */ + public void setBlock(BasicBlock block, CodeAddress lastAddress) { + this.block = block; + this.lastAddress = lastAddress; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + Rop rop = insn.getOpcode(); + if (rop.getOpcode() == RegOps.MARK_LOCAL) { + /* + * Ignore these. They're dealt with by + * the LocalVariableAwareTranslationVisitor + */ + return; + } + if (rop.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + // These get skipped + return; + } + + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + DalvInsn di; + + switch (rop.getBranchingness()) { + case Rop.BRANCH_NONE: + case Rop.BRANCH_RETURN: + case Rop.BRANCH_THROW: { + di = new SimpleInsn(opcode, pos, getRegs(insn)); + break; + } + case Rop.BRANCH_GOTO: { + /* + * Code in the main translation loop will emit a + * goto if necessary (if the branch isn't to the + * immediately subsequent block). + */ + return; + } + case Rop.BRANCH_IF: { + int target = block.getSuccessors().get(1); + di = new TargetInsn(opcode, pos, getRegs(insn), + addresses.getStart(target)); + break; + } + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + int ropOpcode = rop.getOpcode(); + DalvInsn di; + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + + if (ropOpcode == RegOps.MOVE_PARAM) { + if (!paramsAreInOrder) { + /* + * Parameters are not in order at the top of the reg space. + * We need to add moves. + */ + + RegisterSpec dest = insn.getResult(); + int param = + ((CstInteger) insn.getConstant()).getValue(); + RegisterSpec source = + RegisterSpec.make(regCount - paramSize + param, + dest.getType()); + di = new SimpleInsn(opcode, pos, + RegisterSpecList.make(dest, source)); + addOutput(di); + } + } else { + // No moves required for the parameters + RegisterSpecList regs = getRegs(insn); + di = new CstInsn(opcode, pos, regs, insn.getConstant()); + addOutput(di); + } + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + SourcePosition pos = insn.getPosition(); + IntList cases = insn.getCases(); + IntList successors = block.getSuccessors(); + int casesSz = cases.size(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + /* + * Check the assumptions that the number of cases is one + * less than the number of successors and that the last + * successor in the list is the primary (in this case, the + * default). This test is here to guard against forgetting + * to change this code if the way switch instructions are + * constructed also gets changed. + */ + if ((casesSz != (succSz - 1)) || + (primarySuccessor != successors.get(casesSz))) { + throw new RuntimeException("shouldn't happen"); + } + + CodeAddress[] switchTargets = new CodeAddress[casesSz]; + + for (int i = 0; i < casesSz; i++) { + int label = successors.get(i); + switchTargets[i] = addresses.getStart(label); + } + + CodeAddress dataAddress = new CodeAddress(pos); + SwitchData dataInsn = + new SwitchData(pos, lastAddress, cases, switchTargets); + Dop opcode = dataInsn.isPacked() ? + Dops.PACKED_SWITCH : Dops.SPARSE_SWITCH; + TargetInsn switchInsn = + new TargetInsn(opcode, pos, getRegs(insn), dataAddress); + + addOutput(lastAddress); + addOutput(switchInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Looks forward to the current block's primary successor, returning + * the RegisterSpec of the result of the move-result-pseudo at the + * top of that block or null if none. + * + * @return {@code null-ok;} result of move-result-pseudo at the beginning of + * primary successor + */ + private RegisterSpec getNextMoveResultPseudo() + { + int label = block.getPrimarySuccessor(); + + if (label < 0) { + return null; + } + + Insn insn + = method.getBlocks().labelToBlock(label).getInsns().get(0); + + if (insn.getOpcode().getOpcode() != RegOps.MOVE_RESULT_PSEUDO) { + return null; + } else { + return insn.getResult(); + } + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + Constant cst = insn.getConstant(); + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + addOutput(lastAddress); + + if (rop.isCallLike()) { + RegisterSpecList regs = insn.getSources(); + DalvInsn di = new CstInsn(opcode, pos, regs, cst); + + addOutput(di); + } else { + RegisterSpec realResult = getNextMoveResultPseudo(); + + RegisterSpecList regs = getRegs(insn, realResult); + DalvInsn di; + + boolean hasResult = opcode.hasResult() + || (rop.getOpcode() == RegOps.CHECK_CAST); + + if (hasResult != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch " + + insn); + } + + if ((rop.getOpcode() == RegOps.NEW_ARRAY) && + (opcode.getOpcode() != DalvOps.NEW_ARRAY)) { + /* + * It's a type-specific new-array-<primitive>, and + * so it should be turned into a SimpleInsn (no + * constant ref as it's implicit). + */ + di = new SimpleInsn(opcode, pos, regs); + } else { + /* + * This is the general case for constant-bearing + * instructions. + */ + di = new CstInsn(opcode, pos, regs, cst); + } + + addOutput(di); + } + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + RegisterSpec realResult; + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + realResult = getNextMoveResultPseudo(); + + if (opcode.hasResult() != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch" + insn); + } + + addOutput(lastAddress); + + DalvInsn di = new SimpleInsn(opcode, pos, + getRegs(insn, realResult)); + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + SourcePosition pos = insn.getPosition(); + Constant cst = insn.getConstant(); + ArrayList<Constant> values = insn.getInitValues(); + Rop rop = insn.getOpcode(); + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + CodeAddress dataAddress = new CodeAddress(pos); + ArrayData dataInsn = + new ArrayData(pos, lastAddress, values, cst); + + TargetInsn fillArrayDataInsn = + new TargetInsn(Dops.FILL_ARRAY_DATA, pos, getRegs(insn), + dataAddress); + + addOutput(lastAddress); + addOutput(fillArrayDataInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Adds to the output. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutput(DalvInsn insn) { + output.add(insn); + } + + /** + * Adds to the output suffix. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutputSuffix(DalvInsn insn) { + output.addSuffix(insn); + } + } + + /** + * Instruction visitor class for doing instruction translation with + * local variable tracking + */ + private class LocalVariableAwareTranslationVisitor + extends TranslationVisitor { + /** {@code non-null;} local variable info */ + private LocalVariableInfo locals; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + * @param locals {@code non-null;} the local variable info + */ + public LocalVariableAwareTranslationVisitor(OutputCollector output, + LocalVariableInfo locals) { + super(output); + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public void visitPlainInsn(PlainInsn insn) { + super.visitPlainInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + super.visitPlainCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitSwitchInsn(SwitchInsn insn) { + super.visitSwitchInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + super.visitThrowingCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingInsn(ThrowingInsn insn) { + super.visitThrowingInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** + * Adds a {@link LocalStart} to the output if the given + * instruction in fact introduces a local variable. + * + * @param insn {@code non-null;} instruction in question + */ + public void addIntroductionIfNecessary(Insn insn) { + RegisterSpec spec = locals.getAssignment(insn); + + if (spec != null) { + addOutput(new LocalStart(insn.getPosition(), spec)); + } + } + } +} |