blob: bf9f08e52036b2838a2a8bac839aa17fb8264090 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dx.dex.code;
import com.android.dx.rop.code.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));
}
}