--- /dev/null
+/*
+ * 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.dexgen.dex.code;
+
+import com.android.dexgen.rop.code.LocalItem;
+import com.android.dexgen.rop.code.RegisterSpec;
+import com.android.dexgen.rop.code.RegisterSpecList;
+import com.android.dexgen.rop.code.RegisterSpecSet;
+import com.android.dexgen.rop.code.SourcePosition;
+import com.android.dexgen.rop.cst.Constant;
+import com.android.dexgen.rop.cst.CstMemberRef;
+import com.android.dexgen.rop.cst.CstType;
+import com.android.dexgen.rop.cst.CstUtf8;
+import com.android.dexgen.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;
+ }
+}