/* * 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.dx.ssa; import com.android.dx.rop.code.Exceptions; import com.android.dx.rop.code.FillArrayDataInsn; import com.android.dx.rop.code.Insn; 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.ThrowingCstInsn; import com.android.dx.rop.code.ThrowingInsn; import com.android.dx.rop.cst.Constant; import com.android.dx.rop.cst.CstLiteralBits; import com.android.dx.rop.cst.CstMethodRef; import com.android.dx.rop.cst.CstNat; import com.android.dx.rop.cst.CstString; import com.android.dx.rop.cst.CstType; import com.android.dx.rop.cst.TypedConstant; import com.android.dx.rop.cst.Zeroes; import com.android.dx.rop.type.StdTypeList; import com.android.dx.rop.type.Type; import com.android.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 childSets; /** list of objects that this object is put into */ ArrayList 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(); parentSets = new ArrayList(); 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 latticeValues; /** * Constructs an instance. * * @param ssaMeth method to process */ private EscapeAnalysis(SsaMethod ssaMeth) { this.ssaMeth = ssaMeth; this.regCount = ssaMeth.getRegCount(); this.latticeValues = new ArrayList(); } /** * 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 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 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 regWorklist = new ArrayList(); regWorklist.add(result); // Go through the worklist while (!regWorklist.isEmpty()) { int listSize = regWorklist.size() - 1; RegisterSpec def = regWorklist.remove(listSize); List 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 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 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 newRegs = new ArrayList(length); HashSet deletedInsns = new HashSet(); // 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 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 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 newRegs, HashSet 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 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[] 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 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(""), 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 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 insns = insn.getBlock().getInsns(); insns.add(insns.lastIndexOf(insn), newInsn); ssaMeth.onInsnAdded(newInsn); } }