EscapeAnalysispublic class EscapeAnalysis extends Object Simple intraprocedural escape analysis. Finds new arrays that don't escape
the method they are created in and replaces the array values with registers. |
Fields Summary |
---|
private SsaMethod | ssaMethmethod we're processing | private int | regCountssaMeth.getRegCount() | private ArrayList | latticeValuesLattice values for each object register group |
Constructors Summary |
---|
private EscapeAnalysis(SsaMethod ssaMeth)Constructs an instance.
this.ssaMeth = ssaMeth;
this.regCount = ssaMeth.getRegCount();
this.latticeValues = new ArrayList<EscapeSet>();
|
Methods Summary |
---|
private void | addEdge(com.android.dx.ssa.EscapeAnalysis$EscapeSet parentSet, com.android.dx.ssa.EscapeAnalysis$EscapeSet childSet)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.
if (!childSet.parentSets.contains(parentSet)) {
childSet.parentSets.add(parentSet);
}
if (!parentSet.childSets.contains(childSet)) {
parentSet.childSets.add(childSet);
}
| private int | findSetIndex(com.android.dx.rop.code.RegisterSpec reg)Finds the index in the lattice for a particular register.
Returns the size of the lattice if the register wasn't found.
int i;
for (i = 0; i < latticeValues.size(); i++) {
EscapeSet e = latticeValues.get(i);
if (e.regSet.get(reg.getReg())) {
return i;
}
}
return i;
| private SsaInsn | getInsnForMove(SsaInsn moveInsn)Finds the corresponding instruction for a given move result
int pred = moveInsn.getBlock().getPredecessors().nextSetBit(0);
ArrayList<SsaInsn> predInsns = ssaMeth.getBlocks().get(pred).getInsns();
return predInsns.get(predInsns.size()-1);
| private SsaInsn | getMoveForInsn(SsaInsn insn)Finds the corresponding move result for a given instruction
int succ = insn.getBlock().getSuccessors().nextSetBit(0);
ArrayList<SsaInsn> succInsns = ssaMeth.getBlocks().get(succ).getInsns();
return succInsns.get(0);
| private void | insertExceptionThrow(SsaInsn insn, com.android.dx.rop.code.RegisterSpec index, java.util.HashSet deletedInsns)Replaces instructions that trigger an ArrayIndexOutofBounds exception
with an actual throw of the exception.
// 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);
| private void | insertPlainInsnBefore(SsaInsn insn, com.android.dx.rop.code.RegisterSpecList newSources, com.android.dx.rop.code.RegisterSpec newResult, int newOpcode, com.android.dx.rop.cst.Constant cst)Inserts a new PlainInsn before the given instruction.
TODO: move this somewhere more appropriate
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);
| private void | insertThrowingInsnBefore(SsaInsn insn, com.android.dx.rop.code.RegisterSpecList newSources, com.android.dx.rop.code.RegisterSpec newResult, int newOpcode, com.android.dx.rop.cst.Constant cst)Inserts a new ThrowingInsn before the given instruction.
TODO: move this somewhere more appropriate
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);
| private void | movePropagate()Identifies extra moves added by scalar replacement and propagates the
source of the move to any users of the result.
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);
}
}
| public static void | process(SsaMethod ssaMethod)Performs escape analysis on a method. Finds scalar replaceable arrays and
replaces them with equivalent registers.
new EscapeAnalysis(ssaMethod).run();
| private void | processInsn(SsaInsn insn)Process a single instruction, looking for new objects resulting from
move result or move param.
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);
}
| private com.android.dx.ssa.EscapeAnalysis$EscapeSet | processMoveResultPseudoInsn(SsaInsn insn)Determine the origin of a move result pseudo instruction that generates
an object. Creates a new EscapeSet for the new object accordingly.
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;
| private void | processPhiUse(SsaInsn use, com.android.dx.ssa.EscapeAnalysis$EscapeSet escSet, java.util.ArrayList 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.
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());
}
| private void | processRegister(com.android.dx.rop.code.RegisterSpec result, com.android.dx.ssa.EscapeAnalysis$EscapeSet escSet)Iterate through all the uses of a new object.
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);
}
}
}
| private void | processUse(com.android.dx.rop.code.RegisterSpec def, SsaInsn use, com.android.dx.ssa.EscapeAnalysis$EscapeSet escSet, java.util.ArrayList regWorklist)Handles non-phi uses of new objects. Checks to see how instruction is
used and updates the escape state accordingly.
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;
}
| private void | replaceDef(SsaInsn def, SsaInsn prev, int length, java.util.ArrayList newRegs)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.
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);
}
| private void | replaceNode(com.android.dx.ssa.EscapeAnalysis$EscapeSet newNode, com.android.dx.ssa.EscapeAnalysis$EscapeSet oldNode)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.
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);
}
| private void | replaceUse(SsaInsn use, SsaInsn prev, java.util.ArrayList newRegs, java.util.HashSet deletedInsns)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.
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:
}
| private void | run()Runs escape analysis and scalar replacement of arrays.
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();
| private void | scalarReplacement()Performs scalar replacement on all eligible arrays.
// 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();
}
|
|