FileDocCategorySizeDatePackage
OutputFinisher.javaAPI DocAndroid 5.1 API32062Thu Mar 12 22:18:30 GMT 2015com.android.dx.dex.code

OutputFinisher

public final class OutputFinisher extends Object
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.

Fields Summary
private final com.android.dx.dex.DexOptions
dexOptions
{@code non-null;} options for dex output
private final int
unreservedRegCount
{@code >= 0;} register count for the method, not including any extra "reserved" registers needed to translate "difficult" instructions
private ArrayList
insns
{@code non-null;} the list of instructions, per se
private boolean
hasAnyPositionInfo
whether any instruction has position info
private boolean
hasAnyLocalInfo
whether any instruction has local variable info
private int
reservedCount
{@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
reservedParameterCount
{@code >= 0;} the count of reserved registers just before parameters in order to align them.
private final int
paramSize
Size, in register units, of all the parameters to this method
Constructors Summary
public OutputFinisher(com.android.dx.dex.DexOptions dexOptions, int initialCapacity, int regCount, int paramSize)
Constructs an instance. It initially contains no instructions.

param
dexOptions {@code non-null;} options for dex output
param
initialCapacity {@code >= 0;} initial capacity of the instructions list
param
regCount {@code >= 0;} register count for the method
param
paramSize size, in register units, of all the parameters for this method

        this.dexOptions = dexOptions;
        this.unreservedRegCount = regCount;
        this.insns = new ArrayList<DalvInsn>(initialCapacity);
        this.reservedCount = -1;
        this.hasAnyPositionInfo = false;
        this.hasAnyLocalInfo = false;
        this.paramSize = paramSize;
    
Methods Summary
public voidadd(DalvInsn insn)
Adds an instruction to the output.

param
insn {@code non-null;} the instruction to add

        insns.add(insn);
        updateInfo(insn);
    
private static voidaddConstants(java.util.HashSet result, DalvInsn insn)
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

        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);
        }
    
private static voidaddConstants(java.util.HashSet result, com.android.dx.rop.code.RegisterSpec 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

        if (spec == null) {
            return;
        }

        LocalItem local = spec.getLocalItem();
        CstString name = local.getName();
        CstString 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);
        }
    
private voidaddReservedParameters(int delta)

      shiftParameters(delta);
      reservedParameterCount += delta;
    
private voidaddReservedRegisters(int delta)

      shiftAllRegisters(delta);
      reservedCount += delta;
    
private voidalign64bits(Dop[] opcodes)

      while (true) {
        int notAligned64bitRegAccess = 0;
        int aligned64bitRegAccess = 0;
        int notAligned64bitParamAccess = 0;
        int aligned64bitParamAccess = 0;
        int lastParameter = unreservedRegCount + reservedCount + reservedParameterCount;
        int firstParameter = lastParameter - paramSize;

        // Collects the number of time that 64-bit registers are accessed aligned or not.
        for (DalvInsn insn : insns) {
          RegisterSpecList regs = insn.getRegisters();
          for (int usedRegIdx = 0; usedRegIdx < regs.size(); usedRegIdx++) {
            RegisterSpec reg = regs.get(usedRegIdx);
            if (reg.isCategory2()) {
              boolean isParameter = reg.getReg() >= firstParameter;
              if (reg.isEvenRegister()) {
                if (isParameter) {
                  aligned64bitParamAccess++;
                } else {
                  aligned64bitRegAccess++;
                }
              } else {
                if (isParameter) {
                  notAligned64bitParamAccess++;
                } else {
                  notAligned64bitRegAccess++;
                }
              }
            }
          }
        }

        if (notAligned64bitParamAccess > aligned64bitParamAccess
            && notAligned64bitRegAccess > aligned64bitRegAccess) {
          addReservedRegisters(1);
        } else if (notAligned64bitParamAccess > aligned64bitParamAccess) {
          addReservedParameters(1);
        } else if (notAligned64bitRegAccess > aligned64bitRegAccess) {
          addReservedRegisters(1);

          // Need to shift parameters if they exist and if number of unaligned is greater than
          // aligned. We test the opposite because we previously shift all registers by one,
          // so the number of aligned become the number of unaligned.
          if (paramSize != 0 && aligned64bitParamAccess > notAligned64bitParamAccess) {
            addReservedParameters(1);
          }
        } else {
          break;
        }

        if (!reserveRegisters(opcodes)) {
          break;
        }
      }
    
private voidassignAddresses()
Helper for {@link #assignAddressesAndFixBranches}, which assigns an address to each instruction, in order.

        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();
        }
    
private voidassignAddressesAndFixBranches()
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.

        for (;;) {
            assignAddresses();
            if (!fixBranches()) {
                break;
            }
        }
    
public voidassignIndices(DalvCode.AssignIndicesCallback callback)
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

        for (DalvInsn insn : insns) {
            if (insn instanceof CstInsn) {
                assignIndices((CstInsn) insn, callback);
            }
        }
    
private static voidassignIndices(CstInsn insn, DalvCode.AssignIndicesCallback 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

        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);
            }
        }
    
private intcalculateReservedCount(Dop[] opcodes)
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 opcodes} list to help avoid extra work in future register reservation passes.

param
opcodes {@code non-null;} array of per-instruction opcode selections
return
{@code >= 0;} the count of reserved registers

        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);
            Dop originalOpcode = opcodes[i];
            Dop newOpcode = findOpcodeForInsn(insn, originalOpcode);

            if (newOpcode == null) {
                /*
                 * The instruction will need to be expanded, so find the
                 * expanded opcode and reserve registers for it.
                 */
                Dop expandedOp = findExpandedOpcodeForInsn(insn);
                BitSet compatRegs = expandedOp.getFormat().compatibleRegs(insn);
                int reserve = insn.getMinimumRegisterRequirement(compatRegs);
                if (reserve > newReservedCount) {
                    newReservedCount = reserve;
                }
            } else if (originalOpcode == newOpcode) {
                continue;
            }

            opcodes[i] = newOpcode;
        }

        return newReservedCount;
    
private DopfindExpandedOpcodeForInsn(DalvInsn insn)
Finds the proper opcode for the given instruction, ignoring register constraints.

param
insn {@code non-null;} the instruction in question
return
{@code non-null;} the opcode that fits

        Dop result = findOpcodeForInsn(insn.getLowRegVersion(), insn.getOpcode());
        if (result == null) {
            throw new DexException("No expanded opcode for " + insn);
        }
        return result;
    
private DopfindOpcodeForInsn(DalvInsn insn, Dop guess)
Attempts to fit the given instruction into a specific opcode, returning the opcode whose 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 opcode as a first "best guess" and then pessimizes from there if necessary.

param
insn {@code non-null;} the instruction in question
param
guess {@code null-ok;} the current guess as to the best opcode; {@code null} means that no simple opcode fits
return
{@code null-ok;} a possibly-different opcode; either a {@code non-null} good fit or {@code null} to indicate that no simple opcode fits

        /*
         * Note: The initial guess might be null, meaning that an
         * earlier call to this method already determined that there
         * was no possible simple opcode fit.
         */

        while (guess != null) {
            if (guess.getFormat().isCompatible(insn)) {
                /*
                 * Don't break out for const_string to generate jumbo version
                 * when option is enabled.
                 */
                if (!dexOptions.forceJumbo ||
                    guess.getOpcode() != Opcodes.CONST_STRING) {
                    break;
                }
            }

            guess = Dops.getNextOrNull(guess, dexOptions);
        }

        return guess;
    
public DalvInsnListfinishProcessingAndGetList()
Does final processing on this instance and gets the output as a {@link DalvInsnList}. Final processing consists of:
  • optionally renumbering registers (to make room as needed for expanded instructions)
  • picking a final opcode for each instruction
  • rewriting instructions, because of register number, constant pool index, or branch target size issues
  • assigning final addresses

Note: This method may only be called once per instance of this class.

return
{@code non-null;} the output list
throws
UnsupportedOperationException if this method has already been called

        if (reservedCount >= 0) {
            throw new UnsupportedOperationException("already processed");
        }

        Dop[] opcodes = makeOpcodesArray();
        reserveRegisters(opcodes);
        if (dexOptions.ALIGN_64BIT_REGS_IN_OUTPUT_FINISHER) {
          align64bits(opcodes);
        }
        massageInstructions(opcodes);
        assignAddressesAndFixBranches();

        return DalvInsnList.makeImmutable(insns, reservedCount + unreservedRegCount
            + reservedParameterCount);
    
private booleanfixBranches()
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

        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 opcode = insn.getOpcode();
            TargetInsn target = (TargetInsn) insn;

            if (opcode.getFormat().branchFits(target)) {
                continue;
            }

            if (opcode.getFamily() == Opcodes.GOTO) {
                // It is a goto; widen it if possible.
                opcode = findOpcodeForInsn(insn, opcode);
                if (opcode == 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");
                }
                insns.set(i, insn.withOpcode(opcode));
            } 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;
    
public java.util.HashSetgetAllConstants()
Returns the set of all constants referred to by instructions added to this instance.

return
{@code non-null;} the set of constants

        HashSet<Constant> result = new HashSet<Constant>(20);

        for (DalvInsn insn : insns) {
            addConstants(result, insn);
        }

        return result;
    
public booleanhasAnyLocalInfo()
Returns whether this instance has any local variable information.

return
whether this instance has any local variable information

        return hasAnyLocalInfo;
    
public booleanhasAnyPositionInfo()
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

        return hasAnyPositionInfo;
    
private static booleanhasLocalInfo(DalvInsn insn)
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

        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;
    
private static booleanhasLocalInfo(com.android.dx.rop.code.RegisterSpec spec)
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

        return (spec != null)
            && (spec.getLocalItem().getName() != null);
    
public voidinsert(int at, DalvInsn 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

        insns.add(at, insn);
        updateInfo(insn);
    
private Dop[]makeOpcodesArray()
Helper for {@link #finishProcessingAndGetList}, which extracts the opcode out of each instruction into a separate array, to be further manipulated as things progress.

return
{@code non-null;} the array of opcodes

        int size = insns.size();
        Dop[] result = new Dop[size];

        for (int i = 0; i < size; i++) {
            result[i] = insns.get(i).getOpcode();
        }

        return result;
    
private voidmassageInstructions(Dop[] opcodes)
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 will work.

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.

This method ignores issues of branch target size, since final addresses aren't known at the point that this method is called.

param
opcodes {@code non-null;} array of per-instruction opcode selections

        if (reservedCount == 0) {
            /*
             * The easy common case: No registers were reserved, so we
             * merely need to replace any instructions whose format
             * (and hence whose opcode) 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 originalOpcode = insn.getOpcode();
                Dop currentOpcode = opcodes[i];

                if (originalOpcode != currentOpcode) {
                    insns.set(i, insn.withOpcode(currentOpcode));
                }
            }
        } else {
            /*
             * The difficult uncommon case: Some instructions have to be
             * expanded to deal with high registers.
             */
            insns = performExpansion(opcodes);
        }
    
private java.util.ArrayListperformExpansion(Dop[] opcodes)
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
opcodes {@code non-null;} array of per-instruction opcode selections
return
{@code non-null;} the replacement list

        int size = insns.size();
        ArrayList<DalvInsn> result = new ArrayList<DalvInsn>(size * 2);

        ArrayList<CodeAddress> closelyBoundAddresses = new ArrayList<CodeAddress>();

        for (int i = 0; i < size; i++) {
            DalvInsn insn = insns.get(i);
            Dop originalOpcode = insn.getOpcode();
            Dop currentOpcode = opcodes[i];
            DalvInsn prefix;
            DalvInsn suffix;

            if (currentOpcode != null) {
                // No expansion is necessary.
                prefix = null;
                suffix = null;
            } else {
                // Expansion is required.
                currentOpcode = findExpandedOpcodeForInsn(insn);
                BitSet compatRegs =
                    currentOpcode.getFormat().compatibleRegs(insn);
                prefix = insn.expandedPrefix(compatRegs);
                suffix = insn.expandedSuffix(compatRegs);

                // Expand necessary registers to fit the new format
                insn = insn.expandedVersion(compatRegs);
            }

            if (insn instanceof CodeAddress) {
                // If we have a closely bound address, don't add it yet,
                // because we need to add it after the prefix for the
                // instruction it is bound to.
                if (((CodeAddress) insn).getBindsClosely()) {
                    closelyBoundAddresses.add((CodeAddress)insn);
                    continue;
                }
            }

            if (prefix != null) {
                result.add(prefix);
            }

            // Add any pending closely bound addresses
            if (!(insn instanceof ZeroSizeInsn) && closelyBoundAddresses.size() > 0) {
                for (CodeAddress codeAddress: closelyBoundAddresses) {
                    result.add(codeAddress);
                }
                closelyBoundAddresses.clear();
            }

            if (currentOpcode != originalOpcode) {
                insn = insn.withOpcode(currentOpcode);
            }
            result.add(insn);

            if (suffix != null) {
                result.add(suffix);
            }
        }

        return result;
    
private booleanreserveRegisters(Dop[] opcodes)
Helper for {@link #finishProcessingAndGetList}, which figures out how many reserved registers are required and then reserving them. It also updates the given {@code opcodes} array so as to avoid extra work when constructing the massaged instruction list.

param
opcodes {@code non-null;} array of per-instruction opcode selections
return
true if reservedCount is expanded, false otherwise

        boolean reservedCountExpanded = false;
        int oldReservedCount = (reservedCount < 0) ? 0 : reservedCount;

        /*
         * Call calculateReservedCount() and then perform register
         * reservation, repeatedly until no new reservations happen.
         */
        for (;;) {
            int newReservedCount = calculateReservedCount(opcodes);
            if (oldReservedCount >= newReservedCount) {
                break;
            }

            reservedCountExpanded = true;

            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;

        return reservedCountExpanded;
    
public voidreverseBranch(int which, CodeAddress newTarget)
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

        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));
    
private voidshiftAllRegisters(int delta)

      int insnSize = insns.size();

      for (int i = 0; i < insnSize; i++) {
        DalvInsn insn = insns.get(i);
        // Since there is no need to replace CodeAddress since it does not use registers, skips it to
        // avoid to update all TargetInsn that contain a reference to CodeAddress
        if (!(insn instanceof CodeAddress)) {
          insns.set(i, insn.withRegisterOffset(delta));
        }
      }
    
private voidshiftParameters(int delta)

      int insnSize = insns.size();
      int lastParameter = unreservedRegCount + reservedCount + reservedParameterCount;
      int firstParameter = lastParameter - paramSize;

      BasicRegisterMapper mapper = new BasicRegisterMapper(lastParameter);
      for (int i = 0; i < lastParameter; i++) {
        if (i >= firstParameter) {
          mapper.addMapping(i, i + delta, 1);
        } else {
          mapper.addMapping(i, i, 1);
        }
      }

      for (int i = 0; i < insnSize; i++) {
        DalvInsn insn = insns.get(i);
        // Since there is no need to replace CodeAddress since it does not use registers, skips it to
        // avoid to update all TargetInsn that contain a reference to CodeAddress
        if (!(insn instanceof CodeAddress)) {
          insns.set(i, insn.withMapper(mapper));
        }
      }
    
private voidupdateInfo(DalvInsn 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

        if (! hasAnyPositionInfo) {
            SourcePosition pos = insn.getPosition();
            if (pos.getLine() >= 0) {
                hasAnyPositionInfo = true;
            }
        }

        if (! hasAnyLocalInfo) {
            if (hasLocalInfo(insn)) {
                hasAnyLocalInfo = true;
            }
        }