com.android.dx.dex.code.RopTranslator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications.
/*
* 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.dex.DexOptions;
import com.android.dx.io.Opcodes;
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.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;} options for dex output */
private final DexOptions dexOptions;
/** {@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
* @param dexOptions {@code non-null;} options for dex output
* @return {@code non-null;} the translated version
*/
public static DalvCode translate(RopMethod method, int positionInfo,
LocalVariableInfo locals, int paramSize, DexOptions dexOptions) {
RopTranslator translator =
new RopTranslator(method, positionInfo, locals, paramSize, dexOptions);
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
* @param dexOptions {@code non-null;} options for dex output
*/
private RopTranslator(RopMethod method, int positionInfo, LocalVariableInfo locals,
int paramSize, DexOptions dexOptions) {
this.dexOptions = dexOptions;
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(dexOptions, maxInsns, bsz * 3, regCount, paramSize);
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() {
@Override
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);
// make a new address that binds closely to the switch instruction
CodeAddress switchAddress =
new CodeAddress(lastAddress.getPosition(), true);
SwitchData dataInsn =
new SwitchData(pos, switchAddress, cases, switchTargets);
Dop opcode = dataInsn.isPacked() ?
Dops.PACKED_SWITCH : Dops.SPARSE_SWITCH;
TargetInsn switchInsn =
new TargetInsn(opcode, pos, getRegs(insn), dataAddress);
addOutput(switchAddress);
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() != Opcodes.NEW_ARRAY)) {
/*
* It's a type-specific new-array-, 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 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));
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy