blob: 5b1d53313007305aaa6864dd18663e402beee589 [file] [log] [blame]
/*
* 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.LocalItem;
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.SourcePosition;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstMemberRef;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.cst.CstUtf8;
import com.android.dx.rop.type.Type;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Processor for instruction lists, which takes a "first cut" of
* instruction selection as a basis and produces a "final cut" in the
* form of a {@link DalvInsnList} instance.
*/
public final class OutputFinisher {
/**
* {@code >= 0;} register count for the method, not including any extra
* "reserved" registers needed to translate "difficult" instructions
*/
private final int unreservedRegCount;
/** {@code non-null;} the list of instructions, per se */
private ArrayList<DalvInsn> insns;
/** whether any instruction has position info */
private boolean hasAnyPositionInfo;
/** whether any instruction has local variable info */
private boolean hasAnyLocalInfo;
/**
* {@code >= 0;} the count of reserved registers (low-numbered
* registers used when expanding instructions that can't be
* represented simply); becomes valid after a call to {@link
* #massageInstructions}
*/
private int reservedCount;
/**
* Constructs an instance. It initially contains no instructions.
*
* @param regCount {@code >= 0;} register count for the method
* @param initialCapacity {@code >= 0;} initial capacity of the instructions
* list
*/
public OutputFinisher(int initialCapacity, int regCount) {
this.unreservedRegCount = regCount;
this.insns = new ArrayList<DalvInsn>(initialCapacity);
this.reservedCount = -1;
this.hasAnyPositionInfo = false;
this.hasAnyLocalInfo = false;
}
/**
* Returns whether any of the instructions added to this instance
* come with position info.
*
* @return whether any of the instructions added to this instance
* come with position info
*/
public boolean hasAnyPositionInfo() {
return hasAnyPositionInfo;
}
/**
* Returns whether this instance has any local variable information.
*
* @return whether this instance has any local variable information
*/
public boolean hasAnyLocalInfo() {
return hasAnyLocalInfo;
}
/**
* Helper for {@link #add} which scrutinizes a single
* instruction for local variable information.
*
* @param insn {@code non-null;} instruction to scrutinize
* @return {@code true} iff the instruction refers to any
* named locals
*/
private static boolean hasLocalInfo(DalvInsn insn) {
if (insn instanceof LocalSnapshot) {
RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals();
int size = specs.size();
for (int i = 0; i < size; i++) {
if (hasLocalInfo(specs.get(i))) {
return true;
}
}
} else if (insn instanceof LocalStart) {
RegisterSpec spec = ((LocalStart) insn).getLocal();
if (hasLocalInfo(spec)) {
return true;
}
}
return false;
}
/**
* Helper for {@link #hasAnyLocalInfo} which scrutinizes a single
* register spec.
*
* @param spec {@code non-null;} spec to scrutinize
* @return {@code true} iff the spec refers to any
* named locals
*/
private static boolean hasLocalInfo(RegisterSpec spec) {
return (spec != null)
&& (spec.getLocalItem().getName() != null);
}
/**
* Returns the set of all constants referred to by instructions added
* to this instance.
*
* @return {@code non-null;} the set of constants
*/
public HashSet<Constant> getAllConstants() {
HashSet<Constant> result = new HashSet<Constant>(20);
for (DalvInsn insn : insns) {
addConstants(result, insn);
}
return result;
}
/**
* Helper for {@link #getAllConstants} which adds all the info for
* a single instruction.
*
* @param result {@code non-null;} result set to add to
* @param insn {@code non-null;} instruction to scrutinize
*/
private static void addConstants(HashSet<Constant> result,
DalvInsn insn) {
if (insn instanceof CstInsn) {
Constant cst = ((CstInsn) insn).getConstant();
result.add(cst);
} else if (insn instanceof LocalSnapshot) {
RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals();
int size = specs.size();
for (int i = 0; i < size; i++) {
addConstants(result, specs.get(i));
}
} else if (insn instanceof LocalStart) {
RegisterSpec spec = ((LocalStart) insn).getLocal();
addConstants(result, spec);
}
}
/**
* Helper for {@link #getAllConstants} which adds all the info for
* a single {@code RegisterSpec}.
*
* @param result {@code non-null;} result set to add to
* @param spec {@code null-ok;} register spec to add
*/
private static void addConstants(HashSet<Constant> result,
RegisterSpec spec) {
if (spec == null) {
return;
}
LocalItem local = spec.getLocalItem();
CstUtf8 name = local.getName();
CstUtf8 signature = local.getSignature();
Type type = spec.getType();
if (type != Type.KNOWN_NULL) {
result.add(CstType.intern(type));
}
if (name != null) {
result.add(name);
}
if (signature != null) {
result.add(signature);
}
}
/**
* Adds an instruction to the output.
*
* @param insn {@code non-null;} the instruction to add
*/
public void add(DalvInsn insn) {
insns.add(insn);
updateInfo(insn);
}
/**
* Inserts an instruction in the output at the given offset.
*
* @param at {@code >= 0;} what index to insert at
* @param insn {@code non-null;} the instruction to insert
*/
public void insert(int at, DalvInsn insn) {
insns.add(at, insn);
updateInfo(insn);
}
/**
* Helper for {@link #add} and {@link #insert},
* which updates the position and local info flags.
*
* @param insn {@code non-null;} an instruction that was just introduced
*/
private void updateInfo(DalvInsn insn) {
if (! hasAnyPositionInfo) {
SourcePosition pos = insn.getPosition();
if (pos.getLine() >= 0) {
hasAnyPositionInfo = true;
}
}
if (! hasAnyLocalInfo) {
if (hasLocalInfo(insn)) {
hasAnyLocalInfo = true;
}
}
}
/**
* Reverses a branch which is buried a given number of instructions
* backward in the output. It is illegal to call this unless the
* indicated instruction really is a reversible branch.
*
* @param which how many instructions back to find the branch;
* {@code 0} is the most recently added instruction,
* {@code 1} is the instruction before that, etc.
* @param newTarget {@code non-null;} the new target for the reversed branch
*/
public void reverseBranch(int which, CodeAddress newTarget) {
int size = insns.size();
int index = size - which - 1;
TargetInsn targetInsn;
try {
targetInsn = (TargetInsn) insns.get(index);
} catch (IndexOutOfBoundsException ex) {
// Translate the exception.
throw new IllegalArgumentException("too few instructions");
} catch (ClassCastException ex) {
// Translate the exception.
throw new IllegalArgumentException("non-reversible instruction");
}
/*
* No need to call this.set(), since the format and other info
* are the same.
*/
insns.set(index, targetInsn.withNewTargetAndReversed(newTarget));
}
/**
* Assigns indices in all instructions that need them, using the
* given callback to perform lookups. This should be called before
* calling {@link #finishProcessingAndGetList}.
*
* @param callback {@code non-null;} callback object
*/
public void assignIndices(DalvCode.AssignIndicesCallback callback) {
for (DalvInsn insn : insns) {
if (insn instanceof CstInsn) {
assignIndices((CstInsn) insn, callback);
}
}
}
/**
* Helper for {@link #assignIndices} which does assignment for one
* instruction.
*
* @param insn {@code non-null;} the instruction
* @param callback {@code non-null;} the callback
*/
private static void assignIndices(CstInsn insn,
DalvCode.AssignIndicesCallback callback) {
Constant cst = insn.getConstant();
int index = callback.getIndex(cst);
if (index >= 0) {
insn.setIndex(index);
}
if (cst instanceof CstMemberRef) {
CstMemberRef member = (CstMemberRef) cst;
CstType definer = member.getDefiningClass();
index = callback.getIndex(definer);
if (index >= 0) {
insn.setClassIndex(index);
}
}
}
/**
* Does final processing on this instance and gets the output as
* a {@link DalvInsnList}. Final processing consists of:
*
* <ul>
* <li>optionally renumbering registers (to make room as needed for
* expanded instructions)</li>
* <li>picking a final opcode for each instruction</li>
* <li>rewriting instructions, because of register number,
* constant pool index, or branch target size issues</li>
* <li>assigning final addresses</li>
* </ul>
*
* <p><b>Note:</b> This method may only be called once per instance
* of this class.</p>
*
* @return {@code non-null;} the output list
* @throws UnsupportedOperationException if this method has
* already been called
*/
public DalvInsnList finishProcessingAndGetList() {
if (reservedCount >= 0) {
throw new UnsupportedOperationException("already processed");
}
InsnFormat[] formats = makeFormatsArray();
reserveRegisters(formats);
massageInstructions(formats);
assignAddressesAndFixBranches();
return DalvInsnList.makeImmutable(insns,
reservedCount + unreservedRegCount);
}
/**
* Helper for {@link #finishProcessingAndGetList}, which extracts
* the format out of each instruction into a separate array, to be
* further manipulated as things progress.
*
* @return {@code non-null;} the array of formats
*/
private InsnFormat[] makeFormatsArray() {
int size = insns.size();
InsnFormat[] result = new InsnFormat[size];
for (int i = 0; i < size; i++) {
result[i] = insns.get(i).getOpcode().getFormat();
}
return result;
}
/**
* Helper for {@link #finishProcessingAndGetList}, which figures
* out how many reserved registers are required and then reserving
* them. It also updates the given {@code formats} array so
* as to avoid extra work when constructing the massaged
* instruction list.
*
* @param formats {@code non-null;} array of per-instruction format selections
*/
private void reserveRegisters(InsnFormat[] formats) {
int oldReservedCount = (reservedCount < 0) ? 0 : reservedCount;
/*
* Call calculateReservedCount() and then perform register
* reservation, repeatedly until no new reservations happen.
*/
for (;;) {
int newReservedCount = calculateReservedCount(formats);
if (oldReservedCount >= newReservedCount) {
break;
}
int reservedDifference = newReservedCount - oldReservedCount;
int size = insns.size();
for (int i = 0; i < size; i++) {
/*
* CodeAddress instance identity is used to link
* TargetInsns to their targets, so it is
* inappropriate to make replacements, and they don't
* have registers in any case. Hence, the instanceof
* test below.
*/
DalvInsn insn = insns.get(i);
if (!(insn instanceof CodeAddress)) {
/*
* No need to call this.set() since the format and
* other info are the same.
*/
insns.set(i, insn.withRegisterOffset(reservedDifference));
}
}
oldReservedCount = newReservedCount;
}
reservedCount = oldReservedCount;
}
/**
* Helper for {@link #reserveRegisters}, which does one
* pass over the instructions, calculating the number of
* registers that need to be reserved. It also updates the
* {@code formats} list to help avoid extra work in future
* register reservation passes.
*
* @param formats {@code non-null;} array of per-instruction format selections
* @return {@code >= 0;} the count of reserved registers
*/
private int calculateReservedCount(InsnFormat[] formats) {
int size = insns.size();
/*
* Potential new value of reservedCount, which gets updated in the
* following loop. It starts out with the existing reservedCount
* and gets increased if it turns out that additional registers
* need to be reserved.
*/
int newReservedCount = reservedCount;
for (int i = 0; i < size; i++) {
DalvInsn insn = insns.get(i);
InsnFormat originalFormat = formats[i];
InsnFormat newFormat = findFormatForInsn(insn, originalFormat);
if (originalFormat == newFormat) {
continue;
}
if (newFormat == null) {
/*
* The instruction will need to be expanded, so reserve
* registers for it.
*/
int reserve = insn.getMinimumRegisterRequirement();
if (reserve > newReservedCount) {
newReservedCount = reserve;
}
}
formats[i] = newFormat;
}
return newReservedCount;
}
/**
* Attempts to fit the given instruction into a format, returning
* either a format that the instruction fits into or {@code null}
* to indicate that the instruction will need to be expanded. This
* fitting process starts with the given format as a first "best
* guess" and then pessimizes from there if necessary.
*
* @param insn {@code non-null;} the instruction in question
* @param format {@code null-ok;} the current guess as to the best instruction
* format to use; {@code null} means that no simple format fits
* @return {@code null-ok;} a possibly-different format, which is either a
* good fit or {@code null} to indicate that no simple format
* fits
*/
private InsnFormat findFormatForInsn(DalvInsn insn, InsnFormat format) {
if (format == null) {
// The instruction is already known not to fit any simple format.
return format;
}
if (format.isCompatible(insn)) {
// The instruction already fits in the current best-known format.
return format;
}
Dop dop = insn.getOpcode();
int family = dop.getFamily();
for (;;) {
format = format.nextUp();
if ((format == null) ||
(format.isCompatible(insn) &&
(Dops.getOrNull(family, format) != null))) {
break;
}
}
return format;
}
/**
* Helper for {@link #finishProcessingAndGetList}, which goes
* through each instruction in the output, making sure its opcode
* can accomodate its arguments. In cases where the opcode is
* unable to do so, this replaces the instruction with a larger
* instruction with identical semantics that <i>will</i> work.
*
* <p>This method may also reserve a number of low-numbered
* registers, renumbering the instructions' original registers, in
* order to have register space available in which to move
* very-high registers when expanding instructions into
* multi-instruction sequences. This expansion is done when no
* simple instruction format can be found for a given instruction that
* is able to accomodate that instruction's registers.</p>
*
* <p>This method ignores issues of branch target size, since
* final addresses aren't known at the point that this method is
* called.</p>
*
* @param formats {@code non-null;} array of per-instruction format selections
*/
private void massageInstructions(InsnFormat[] formats) {
if (reservedCount == 0) {
/*
* The easy common case: No registers were reserved, so we
* merely need to replace any instructions whose format changed
* during the reservation pass, but all instructions will stay
* at their original indices, and the instruction list doesn't
* grow.
*/
int size = insns.size();
for (int i = 0; i < size; i++) {
DalvInsn insn = insns.get(i);
Dop dop = insn.getOpcode();
InsnFormat format = formats[i];
if (format != dop.getFormat()) {
dop = Dops.getOrNull(dop.getFamily(), format);
insns.set(i, insn.withOpcode(dop));
}
}
} else {
/*
* The difficult uncommon case: Some instructions have to be
* expanded to deal with high registers.
*/
insns = performExpansion(formats);
}
}
/**
* Helper for {@link #massageInstructions}, which constructs a
* replacement list, where each {link DalvInsn} instance that
* couldn't be represented simply (due to register representation
* problems) is expanded into a series of instances that together
* perform the proper function.
*
* @param formats {@code non-null;} array of per-instruction format selections
* @return {@code non-null;} the replacement list
*/
private ArrayList<DalvInsn> performExpansion(InsnFormat[] formats) {
int size = insns.size();
ArrayList<DalvInsn> result = new ArrayList<DalvInsn>(size * 2);
for (int i = 0; i < size; i++) {
DalvInsn insn = insns.get(i);
Dop dop = insn.getOpcode();
InsnFormat originalFormat = dop.getFormat();
InsnFormat currentFormat = formats[i];
DalvInsn prefix;
DalvInsn suffix;
if (currentFormat != null) {
// No expansion is necessary.
prefix = null;
suffix = null;
} else {
// Expansion is required.
prefix = insn.hrPrefix();
suffix = insn.hrSuffix();
/*
* Get the initial guess as to the hr version, but then
* let findFormatForInsn() pick a better format, if any.
*/
insn = insn.hrVersion();
originalFormat = insn.getOpcode().getFormat();
currentFormat = findFormatForInsn(insn, originalFormat);
}
if (prefix != null) {
result.add(prefix);
}
if (currentFormat != originalFormat) {
dop = Dops.getOrNull(dop.getFamily(), currentFormat);
insn = insn.withOpcode(dop);
}
result.add(insn);
if (suffix != null) {
result.add(suffix);
}
}
return result;
}
/**
* Helper for {@link #finishProcessingAndGetList}, which assigns
* addresses to each instruction, possibly rewriting branches to
* fix ones that wouldn't otherwise be able to reach their
* targets.
*/
private void assignAddressesAndFixBranches() {
for (;;) {
assignAddresses();
if (!fixBranches()) {
break;
}
}
}
/**
* Helper for {@link #assignAddressesAndFixBranches}, which
* assigns an address to each instruction, in order.
*/
private void assignAddresses() {
int address = 0;
int size = insns.size();
for (int i = 0; i < size; i++) {
DalvInsn insn = insns.get(i);
insn.setAddress(address);
address += insn.codeSize();
}
}
/**
* Helper for {@link #assignAddressesAndFixBranches}, which checks
* the branch target size requirement of each branch instruction
* to make sure it fits. For instructions that don't fit, this
* rewrites them to use a {@code goto} of some sort. In the
* case of a conditional branch that doesn't fit, the sense of the
* test is reversed in order to branch around a {@code goto}
* to the original target.
*
* @return whether any branches had to be fixed
*/
private boolean fixBranches() {
int size = insns.size();
boolean anyFixed = false;
for (int i = 0; i < size; i++) {
DalvInsn insn = insns.get(i);
if (!(insn instanceof TargetInsn)) {
// This loop only needs to inspect TargetInsns.
continue;
}
Dop dop = insn.getOpcode();
InsnFormat format = dop.getFormat();
TargetInsn target = (TargetInsn) insn;
if (format.branchFits(target)) {
continue;
}
if (dop.getFamily() == DalvOps.GOTO) {
// It is a goto; widen it if possible.
InsnFormat newFormat = findFormatForInsn(insn, format);
if (newFormat == null) {
/*
* The branch is already maximally large. This should
* only be possible if a method somehow manages to have
* more than 2^31 code units.
*/
throw new UnsupportedOperationException("method too long");
}
dop = Dops.getOrNull(dop.getFamily(), newFormat);
insn = insn.withOpcode(dop);
insns.set(i, insn);
} else {
/*
* It is a conditional: Reverse its sense, and arrange for
* it to branch around an absolute goto to the original
* branch target.
*
* Note: An invariant of the list being processed is
* that every TargetInsn is followed by a CodeAddress.
* Hence, it is always safe to get the next element
* after a TargetInsn and cast it to CodeAddress, as
* is happening a few lines down.
*
* Also note: Size gets incremented by one here, as we
* have -- in the net -- added one additional element
* to the list, so we increment i to match. The added
* and changed elements will be inspected by a repeat
* call to this method after this invocation returns.
*/
CodeAddress newTarget;
try {
newTarget = (CodeAddress) insns.get(i + 1);
} catch (IndexOutOfBoundsException ex) {
// The TargetInsn / CodeAddress invariant was violated.
throw new IllegalStateException(
"unpaired TargetInsn (dangling)");
} catch (ClassCastException ex) {
// The TargetInsn / CodeAddress invariant was violated.
throw new IllegalStateException("unpaired TargetInsn");
}
TargetInsn gotoInsn =
new TargetInsn(Dops.GOTO, target.getPosition(),
RegisterSpecList.EMPTY, target.getTarget());
insns.set(i, gotoInsn);
insns.add(i, target.withNewTargetAndReversed(newTarget));
size++;
i++;
}
anyFixed = true;
}
return anyFixed;
}
}