content: /*
   * 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.taobao.android.dx.dex.code;
  
  import com.taobao.android.dx.rop.code.RegisterSpec;
  import com.taobao.android.dx.rop.code.RegisterSpecList;
  import com.taobao.android.dx.rop.cst.Constant;
  import com.taobao.android.dx.rop.cst.CstInteger;
  import com.taobao.android.dx.rop.cst.CstKnownNull;
  import com.taobao.android.dx.rop.cst.CstLiteral64;
  import com.taobao.android.dx.rop.cst.CstLiteralBits;
  import com.taobao.android.dx.rop.cst.CstString;
  import com.taobao.android.dx.util.AnnotatedOutput;
  import com.taobao.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 < 0x100) || (opcode > 0xffff)) {
              throw new IllegalArgumentException("opcode out of range 0..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));
      }
  }
  