| /* |
| * 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.RegisterSpec; |
| import com.android.dx.rop.code.RegisterSpecList; |
| import com.android.dx.rop.cst.Constant; |
| import com.android.dx.rop.cst.CstInteger; |
| import com.android.dx.rop.cst.CstKnownNull; |
| import com.android.dx.rop.cst.CstLiteral64; |
| import com.android.dx.rop.cst.CstLiteralBits; |
| import com.android.dx.rop.cst.CstString; |
| import com.android.dx.util.AnnotatedOutput; |
| import com.android.dx.util.Hex; |
| |
| import java.util.BitSet; |
| |
| /** |
| * Base class for all instruction format handlers. Instruction format |
| * handlers know how to translate {@link DalvInsn} instances into |
| * streams of code units, as well as human-oriented listing strings |
| * representing such translations. |
| */ |
| public abstract class InsnFormat { |
| /** |
| * flag to enable/disable the new extended opcode formats; meant as a |
| * temporary measure until VM support for the salient opcodes is |
| * added. TODO: Remove this declaration when the VM can deal. |
| */ |
| public static boolean ALLOW_EXTENDED_OPCODES = true; |
| |
| /** |
| * Returns the string form, suitable for inclusion in a listing |
| * dump, of the given instruction. The instruction must be of this |
| * instance's format for proper operation. |
| * |
| * @param insn {@code non-null;} the instruction |
| * @param noteIndices whether to include an explicit notation of |
| * constant pool indices |
| * @return {@code non-null;} the string form |
| */ |
| public final String listingString(DalvInsn insn, boolean noteIndices) { |
| String op = insn.getOpcode().getName(); |
| String arg = insnArgString(insn); |
| String comment = insnCommentString(insn, noteIndices); |
| StringBuilder sb = new StringBuilder(100); |
| |
| sb.append(op); |
| |
| if (arg.length() != 0) { |
| sb.append(' '); |
| sb.append(arg); |
| } |
| |
| if (comment.length() != 0) { |
| sb.append(" // "); |
| sb.append(comment); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Returns the string form of the arguments to the given instruction. |
| * The instruction must be of this instance's format. If the instruction |
| * has no arguments, then the result should be {@code ""}, not |
| * {@code null}. |
| * |
| * <p>Subclasses must override this method.</p> |
| * |
| * @param insn {@code non-null;} the instruction |
| * @return {@code non-null;} the string form |
| */ |
| public abstract String insnArgString(DalvInsn insn); |
| |
| /** |
| * Returns the associated comment for the given instruction, if any. |
| * The instruction must be of this instance's format. If the instruction |
| * has no comment, then the result should be {@code ""}, not |
| * {@code null}. |
| * |
| * <p>Subclasses must override this method.</p> |
| * |
| * @param insn {@code non-null;} the instruction |
| * @param noteIndices whether to include an explicit notation of |
| * constant pool indices |
| * @return {@code non-null;} the string form |
| */ |
| public abstract String insnCommentString(DalvInsn insn, |
| boolean noteIndices); |
| |
| /** |
| * Gets the code size of instructions that use this format. The |
| * size is a number of 16-bit code units, not bytes. This should |
| * throw an exception if this format is of variable size. |
| * |
| * @return {@code >= 0;} the instruction length in 16-bit code units |
| */ |
| public abstract int codeSize(); |
| |
| /** |
| * Returns whether or not the given instruction's arguments will |
| * fit in this instance's format. This includes such things as |
| * counting register arguments, checking register ranges, and |
| * making sure that additional arguments are of appropriate types |
| * and are in-range. If this format has a branch target but the |
| * instruction's branch offset is unknown, this method will simply |
| * not check the offset. |
| * |
| * <p>Subclasses must override this method.</p> |
| * |
| * @param insn {@code non-null;} the instruction to check |
| * @return {@code true} iff the instruction's arguments are |
| * appropriate for this instance, or {@code false} if not |
| */ |
| public abstract boolean isCompatible(DalvInsn insn); |
| |
| /** |
| * Returns which of a given instruction's registers will fit in |
| * this instance's format. |
| * |
| * <p>The default implementation of this method always returns |
| * an empty BitSet. Subclasses must override this method if they |
| * have registers.</p> |
| * |
| * @param insn {@code non-null;} the instruction to check |
| * @return {@code non-null;} a BitSet flagging registers in the |
| * register list that are compatible to this format |
| */ |
| public BitSet compatibleRegs(DalvInsn insn) { |
| return new BitSet(); |
| } |
| |
| /** |
| * Returns whether or not the given instruction's branch offset will |
| * fit in this instance's format. This always returns {@code false} |
| * for formats that don't include a branch offset. |
| * |
| * <p>The default implementation of this method always returns |
| * {@code false}. Subclasses must override this method if they |
| * include branch offsets.</p> |
| * |
| * @param insn {@code non-null;} the instruction to check |
| * @return {@code true} iff the instruction's branch offset is |
| * appropriate for this instance, or {@code false} if not |
| */ |
| public boolean branchFits(TargetInsn insn) { |
| return false; |
| } |
| |
| /** |
| * Writes the code units for the given instruction to the given |
| * output destination. The instruction must be of this instance's format. |
| * |
| * <p>Subclasses must override this method.</p> |
| * |
| * @param out {@code non-null;} the output destination to write to |
| * @param insn {@code non-null;} the instruction to write |
| */ |
| public abstract void writeTo(AnnotatedOutput out, DalvInsn insn); |
| |
| /** |
| * Helper method to return a register list string. |
| * |
| * @param list {@code non-null;} the list of registers |
| * @return {@code non-null;} the string form |
| */ |
| protected static String regListString(RegisterSpecList list) { |
| int sz = list.size(); |
| StringBuffer sb = new StringBuffer(sz * 5 + 2); |
| |
| sb.append('{'); |
| |
| for (int i = 0; i < sz; i++) { |
| if (i != 0) { |
| sb.append(", "); |
| } |
| sb.append(list.get(i).regString()); |
| } |
| |
| sb.append('}'); |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Helper method to return a register range string. |
| * |
| * @param list {@code non-null;} the list of registers (which must be |
| * sequential) |
| * @return {@code non-null;} the string form |
| */ |
| protected static String regRangeString(RegisterSpecList list) { |
| int size = list.size(); |
| StringBuilder sb = new StringBuilder(30); |
| |
| sb.append("{"); |
| |
| switch (size) { |
| case 0: { |
| // Nothing to do. |
| break; |
| } |
| case 1: { |
| sb.append(list.get(0).regString()); |
| break; |
| } |
| default: { |
| RegisterSpec lastReg = list.get(size - 1); |
| if (lastReg.getCategory() == 2) { |
| /* |
| * Add one to properly represent a list-final |
| * category-2 register. |
| */ |
| lastReg = lastReg.withOffset(1); |
| } |
| |
| sb.append(list.get(0).regString()); |
| sb.append(".."); |
| sb.append(lastReg.regString()); |
| } |
| } |
| |
| sb.append("}"); |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Helper method to return a literal bits argument string. |
| * |
| * @param value the value |
| * @return {@code non-null;} the string form |
| */ |
| protected static String literalBitsString(CstLiteralBits value) { |
| StringBuffer sb = new StringBuffer(100); |
| |
| sb.append('#'); |
| |
| if (value instanceof CstKnownNull) { |
| sb.append("null"); |
| } else { |
| sb.append(value.typeName()); |
| sb.append(' '); |
| sb.append(value.toHuman()); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Helper method to return a literal bits comment string. |
| * |
| * @param value the value |
| * @param width the width of the constant, in bits (used for displaying |
| * the uninterpreted bits; one of: {@code 4 8 16 32 64} |
| * @return {@code non-null;} the comment |
| */ |
| protected static String literalBitsComment(CstLiteralBits value, |
| int width) { |
| StringBuffer sb = new StringBuffer(20); |
| |
| sb.append("#"); |
| |
| long bits; |
| |
| if (value instanceof CstLiteral64) { |
| bits = ((CstLiteral64) value).getLongBits(); |
| } else { |
| bits = value.getIntBits(); |
| } |
| |
| switch (width) { |
| case 4: sb.append(Hex.uNibble((int) bits)); break; |
| case 8: sb.append(Hex.u1((int) bits)); break; |
| case 16: sb.append(Hex.u2((int) bits)); break; |
| case 32: sb.append(Hex.u4((int) bits)); break; |
| case 64: sb.append(Hex.u8(bits)); break; |
| default: { |
| throw new RuntimeException("shouldn't happen"); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Helper method to return a branch address string. |
| * |
| * @param insn {@code non-null;} the instruction in question |
| * @return {@code non-null;} the string form of the instruction's |
| * branch target |
| */ |
| protected static String branchString(DalvInsn insn) { |
| TargetInsn ti = (TargetInsn) insn; |
| int address = ti.getTargetAddress(); |
| |
| return (address == (char) address) ? Hex.u2(address) : Hex.u4(address); |
| } |
| |
| /** |
| * Helper method to return the comment for a branch. |
| * |
| * @param insn {@code non-null;} the instruction in question |
| * @return {@code non-null;} the comment |
| */ |
| protected static String branchComment(DalvInsn insn) { |
| TargetInsn ti = (TargetInsn) insn; |
| int offset = ti.getTargetOffset(); |
| |
| return (offset == (short) offset) ? Hex.s2(offset) : Hex.s4(offset); |
| } |
| |
| /** |
| * Helper method to return the constant string for a {@link CstInsn} |
| * in human form. |
| * |
| * @param insn {@code non-null;} a constant-bearing instruction |
| * @return {@code non-null;} the human string form of the contained |
| * constant |
| */ |
| protected static String cstString(DalvInsn insn) { |
| CstInsn ci = (CstInsn) insn; |
| Constant cst = ci.getConstant(); |
| |
| return cst instanceof CstString ? ((CstString) cst).toQuoted() : cst.toHuman(); |
| } |
| |
| /** |
| * Helper method to return an instruction comment for a constant. |
| * |
| * @param insn {@code non-null;} a constant-bearing instruction |
| * @return {@code non-null;} comment string representing the constant |
| */ |
| protected static String cstComment(DalvInsn insn) { |
| CstInsn ci = (CstInsn) insn; |
| |
| if (! ci.hasIndex()) { |
| return ""; |
| } |
| |
| StringBuilder sb = new StringBuilder(20); |
| int index = ci.getIndex(); |
| |
| sb.append(ci.getConstant().typeName()); |
| sb.append('@'); |
| |
| if (index < 65536) { |
| sb.append(Hex.u2(index)); |
| } else { |
| sb.append(Hex.u4(index)); |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Helper method to determine if a signed int value fits in a nibble. |
| * |
| * @param value the value in question |
| * @return {@code true} iff it's in the range -8..+7 |
| */ |
| protected static boolean signedFitsInNibble(int value) { |
| return (value >= -8) && (value <= 7); |
| } |
| |
| /** |
| * Helper method to determine if an unsigned int value fits in a nibble. |
| * |
| * @param value the value in question |
| * @return {@code true} iff it's in the range 0..0xf |
| */ |
| protected static boolean unsignedFitsInNibble(int value) { |
| return value == (value & 0xf); |
| } |
| |
| /** |
| * Helper method to determine if a signed int value fits in a byte. |
| * |
| * @param value the value in question |
| * @return {@code true} iff it's in the range -0x80..+0x7f |
| */ |
| protected static boolean signedFitsInByte(int value) { |
| return (byte) value == value; |
| } |
| |
| /** |
| * Helper method to determine if an unsigned int value fits in a byte. |
| * |
| * @param value the value in question |
| * @return {@code true} iff it's in the range 0..0xff |
| */ |
| protected static boolean unsignedFitsInByte(int value) { |
| return value == (value & 0xff); |
| } |
| |
| /** |
| * Helper method to determine if a signed int value fits in a short. |
| * |
| * @param value the value in question |
| * @return {@code true} iff it's in the range -0x8000..+0x7fff |
| */ |
| protected static boolean signedFitsInShort(int value) { |
| return (short) value == value; |
| } |
| |
| /** |
| * Helper method to determine if an unsigned int value fits in a short. |
| * |
| * @param value the value in question |
| * @return {@code true} iff it's in the range 0..0xffff |
| */ |
| protected static boolean unsignedFitsInShort(int value) { |
| return value == (value & 0xffff); |
| } |
| |
| /** |
| * Helper method to determine if a list of registers are sequential, |
| * including degenerate cases for empty or single-element lists. |
| * |
| * @param list {@code non-null;} the list of registers |
| * @return {@code true} iff the list is sequentially ordered |
| */ |
| protected static boolean isRegListSequential(RegisterSpecList list) { |
| int sz = list.size(); |
| |
| if (sz < 2) { |
| return true; |
| } |
| |
| int first = list.get(0).getReg(); |
| int next = first; |
| |
| for (int i = 0; i < sz; i++) { |
| RegisterSpec one = list.get(i); |
| if (one.getReg() != next) { |
| return false; |
| } |
| next += one.getCategory(); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Helper method to extract the callout-argument index from an |
| * appropriate instruction. |
| * |
| * @param insn {@code non-null;} the instruction |
| * @return {@code >= 0;} the callout argument index |
| */ |
| protected static int argIndex(DalvInsn insn) { |
| int arg = ((CstInteger) ((CstInsn) insn).getConstant()).getValue(); |
| |
| if (arg < 0) { |
| throw new IllegalArgumentException("bogus insn"); |
| } |
| |
| return arg; |
| } |
| |
| /** |
| * Helper method to combine an opcode and a second byte of data into |
| * the appropriate form for emitting into a code buffer. |
| * |
| * @param insn {@code non-null;} the instruction containing the opcode |
| * @param arg {@code 0..255;} arbitrary other byte value |
| * @return combined value |
| */ |
| protected static short opcodeUnit(DalvInsn insn, int arg) { |
| if ((arg & 0xff) != arg) { |
| throw new IllegalArgumentException("arg out of range 0..255"); |
| } |
| |
| int opcode = insn.getOpcode().getOpcode(); |
| |
| if ((opcode & 0xff) != opcode) { |
| throw new IllegalArgumentException("opcode out of range 0..255"); |
| } |
| |
| return (short) (opcode | (arg << 8)); |
| } |
| |
| /** |
| * Helper method to get an extended (16-bit) opcode out of an |
| * instruction, returning it as a code unit. The opcode |
| * <i>must</i> be an extended opcode. |
| * |
| * @param insn {@code non-null;} the instruction containing the |
| * extended opcode |
| * @return the opcode as a code unit |
| */ |
| protected static short opcodeUnit(DalvInsn insn) { |
| int opcode = insn.getOpcode().getOpcode(); |
| |
| if ((opcode < 0xff) || (opcode > 0xffff)) { |
| throw new IllegalArgumentException( |
| "extended opcode out of range 255..65535"); |
| } |
| |
| return (short) opcode; |
| } |
| |
| /** |
| * Helper method to combine two bytes into a code unit. |
| * |
| * @param low {@code 0..255;} low byte |
| * @param high {@code 0..255;} high byte |
| * @return combined value |
| */ |
| protected static short codeUnit(int low, int high) { |
| if ((low & 0xff) != low) { |
| throw new IllegalArgumentException("low out of range 0..255"); |
| } |
| |
| if ((high & 0xff) != high) { |
| throw new IllegalArgumentException("high out of range 0..255"); |
| } |
| |
| return (short) (low | (high << 8)); |
| } |
| |
| /** |
| * Helper method to combine four nibbles into a code unit. |
| * |
| * @param n0 {@code 0..15;} low nibble |
| * @param n1 {@code 0..15;} medium-low nibble |
| * @param n2 {@code 0..15;} medium-high nibble |
| * @param n3 {@code 0..15;} high nibble |
| * @return combined value |
| */ |
| protected static short codeUnit(int n0, int n1, int n2, int n3) { |
| if ((n0 & 0xf) != n0) { |
| throw new IllegalArgumentException("n0 out of range 0..15"); |
| } |
| |
| if ((n1 & 0xf) != n1) { |
| throw new IllegalArgumentException("n1 out of range 0..15"); |
| } |
| |
| if ((n2 & 0xf) != n2) { |
| throw new IllegalArgumentException("n2 out of range 0..15"); |
| } |
| |
| if ((n3 & 0xf) != n3) { |
| throw new IllegalArgumentException("n3 out of range 0..15"); |
| } |
| |
| return (short) (n0 | (n1 << 4) | (n2 << 8) | (n3 << 12)); |
| } |
| |
| /** |
| * Helper method to combine two nibbles into a byte. |
| * |
| * @param low {@code 0..15;} low nibble |
| * @param high {@code 0..15;} high nibble |
| * @return {@code 0..255;} combined value |
| */ |
| protected static int makeByte(int low, int high) { |
| if ((low & 0xf) != low) { |
| throw new IllegalArgumentException("low out of range 0..15"); |
| } |
| |
| if ((high & 0xf) != high) { |
| throw new IllegalArgumentException("high out of range 0..15"); |
| } |
| |
| return low | (high << 4); |
| } |
| |
| /** |
| * Writes one code unit to the given output destination. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0) { |
| out.writeShort(c0); |
| } |
| |
| /** |
| * Writes two code units to the given output destination. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, short c1) { |
| out.writeShort(c0); |
| out.writeShort(c1); |
| } |
| |
| /** |
| * Writes three code units to the given output destination. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1 code unit to write |
| * @param c2 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, short c1, |
| short c2) { |
| out.writeShort(c0); |
| out.writeShort(c1); |
| out.writeShort(c2); |
| } |
| |
| /** |
| * Writes four code units to the given output destination. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1 code unit to write |
| * @param c2 code unit to write |
| * @param c3 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, short c1, |
| short c2, short c3) { |
| out.writeShort(c0); |
| out.writeShort(c1); |
| out.writeShort(c2); |
| out.writeShort(c3); |
| } |
| |
| /** |
| * Writes five code units to the given output destination. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1 code unit to write |
| * @param c2 code unit to write |
| * @param c3 code unit to write |
| * @param c4 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, short c1, |
| short c2, short c3, short c4) { |
| out.writeShort(c0); |
| out.writeShort(c1); |
| out.writeShort(c2); |
| out.writeShort(c3); |
| out.writeShort(c4); |
| } |
| |
| /** |
| * Writes three code units to the given output destination, where the |
| * second and third are represented as single <code>int</code> and emitted |
| * in little-endian order. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1c2 code unit pair to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, int c1c2) { |
| write(out, c0, (short) c1c2, (short) (c1c2 >> 16)); |
| } |
| |
| /** |
| * Writes four code units to the given output destination, where the |
| * second and third are represented as single <code>int</code> and emitted |
| * in little-endian order. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1c2 code unit pair to write |
| * @param c3 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, int c1c2, |
| short c3) { |
| write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3); |
| } |
| |
| /** |
| * Writes five code units to the given output destination, where the |
| * second and third are represented as single <code>int</code> and emitted |
| * in little-endian order. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1c2 code unit pair to write |
| * @param c3 code unit to write |
| * @param c4 code unit to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, int c1c2, |
| short c3, short c4) { |
| write(out, c0, (short) c1c2, (short) (c1c2 >> 16), c3, c4); |
| } |
| |
| /** |
| * Writes five code units to the given output destination, where the |
| * second through fifth are represented as single <code>long</code> |
| * and emitted in little-endian order. |
| * |
| * @param out {@code non-null;} where to write to |
| * @param c0 code unit to write |
| * @param c1c2c3c4 code unit quad to write |
| */ |
| protected static void write(AnnotatedOutput out, short c0, long c1c2c3c4) { |
| write(out, c0, (short) c1c2c3c4, (short) (c1c2c3c4 >> 16), |
| (short) (c1c2c3c4 >> 32), (short) (c1c2c3c4 >> 48)); |
| } |
| } |