blob: 93ff293b4fae28bf7a388f29fb864c6d5c304875 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/disassembler.h"
#include "vm/globals.h" // Needed here to get TARGET_ARCH_ARM.
#if defined(TARGET_ARCH_ARM)
#include "platform/assert.h"
#include "vm/cpu.h"
namespace dart {
class ARMDecoder : public ValueObject {
public:
ARMDecoder(char* buffer, size_t buffer_size)
: buffer_(buffer),
buffer_size_(buffer_size),
buffer_pos_(0) {
buffer_[buffer_pos_] = '\0';
}
~ARMDecoder() {}
// Writes one disassembled instruction into 'buffer' (0-terminated).
// Returns true if the instruction was successfully decoded, false otherwise.
void InstructionDecode(uword pc);
private:
// Bottleneck functions to print into the out_buffer.
void Print(const char* str);
// Printing of common values.
void PrintRegister(int reg);
void PrintSRegister(int reg);
void PrintDRegister(int reg);
void PrintDRegisterList(int start, int reg_count);
void PrintQRegister(int reg);
void PrintCondition(Instr* instr);
void PrintShiftRm(Instr* instr);
void PrintShiftImm(Instr* instr);
void PrintPU(Instr* instr);
// Handle formatting of instructions and their options.
int FormatRegister(Instr* instr, const char* option);
int FormatSRegister(Instr* instr, const char* option);
int FormatDRegister(Instr* instr, const char* option);
int FormatQRegister(Instr* instr, const char* option);
int FormatOption(Instr* instr, const char* option);
void Format(Instr* instr, const char* format);
void Unknown(Instr* instr);
// Each of these functions decodes one particular instruction type, a 3-bit
// field in the instruction encoding.
// Types 0 and 1 are combined as they are largely the same except for the way
// they interpret the shifter operand.
void DecodeType01(Instr* instr);
void DecodeType2(Instr* instr);
void DecodeType3(Instr* instr);
void DecodeType4(Instr* instr);
void DecodeType5(Instr* instr);
void DecodeType6(Instr* instr);
void DecodeType7(Instr* instr);
void DecodeSIMDDataProcessing(Instr* instr);
// Convenience functions.
char* get_buffer() const { return buffer_; }
char* current_position_in_buffer() { return buffer_ + buffer_pos_; }
size_t remaining_size_in_buffer() { return buffer_size_ - buffer_pos_; }
char* buffer_; // Decode instructions into this buffer.
size_t buffer_size_; // The size of the character buffer.
size_t buffer_pos_; // Current character position in buffer.
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(ARMDecoder);
};
// Support for assertions in the ARMDecoder formatting functions.
#define STRING_STARTS_WITH(string, compare_string) \
(strncmp(string, compare_string, strlen(compare_string)) == 0)
// Append the str to the output buffer.
void ARMDecoder::Print(const char* str) {
char cur = *str++;
while (cur != '\0' && (buffer_pos_ < (buffer_size_ - 1))) {
buffer_[buffer_pos_++] = cur;
cur = *str++;
}
buffer_[buffer_pos_] = '\0';
}
// These condition names are defined in a way to match the native disassembler
// formatting. See for example the command "objdump -d <binary file>".
static const char* cond_names[kMaxCondition] = {
"eq", "ne", "cs" , "cc" , "mi" , "pl" , "vs" , "vc" ,
"hi", "ls", "ge", "lt", "gt", "le", "", "invalid",
};
// Print the condition guarding the instruction.
void ARMDecoder::PrintCondition(Instr* instr) {
Print(cond_names[instr->ConditionField()]);
}
// These register names are defined in a way to match the native disassembler
// formatting, except for register alias pp (r9).
// See for example the command "objdump -d <binary file>".
static const char* reg_names[kNumberOfCpuRegisters] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
"r8", "pp", "r10", "fp", "ip", "sp", "lr", "pc",
};
// Print the register name according to the active name converter.
void ARMDecoder::PrintRegister(int reg) {
ASSERT(0 <= reg);
ASSERT(reg < kNumberOfCpuRegisters);
Print(reg_names[reg]);
}
void ARMDecoder::PrintSRegister(int reg) {
ASSERT(0 <= reg);
ASSERT(reg < kNumberOfSRegisters);
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"s%d", reg);
}
void ARMDecoder::PrintDRegister(int reg) {
ASSERT(0 <= reg);
ASSERT(reg < kNumberOfDRegisters);
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"d%d", reg);
}
void ARMDecoder::PrintQRegister(int reg) {
ASSERT(0 <= reg);
ASSERT(reg < kNumberOfQRegisters);
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"q%d", reg);
}
// These shift names are defined in a way to match the native disassembler
// formatting. See for example the command "objdump -d <binary file>".
static const char* shift_names[kMaxShift] = {
"lsl", "lsr", "asr", "ror"
};
// Print the register shift operands for the instruction. Generally used for
// data processing instructions.
void ARMDecoder::PrintShiftRm(Instr* instr) {
Shift shift = instr->ShiftField();
int shift_amount = instr->ShiftAmountField();
int rm = instr->RmField();
PrintRegister(rm);
if ((instr->RegShiftField() == 0) && (shift == LSL) && (shift_amount == 0)) {
// Special case for using rm only.
return;
}
if (instr->RegShiftField() == 0) {
// by immediate
if ((shift == ROR) && (shift_amount == 0)) {
Print(", RRX");
return;
} else if (((shift == LSR) || (shift == ASR)) && (shift_amount == 0)) {
shift_amount = 32;
}
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
", %s #%d",
shift_names[shift],
shift_amount);
} else {
// by register
int rs = instr->RsField();
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
", %s ",
shift_names[shift]);
PrintRegister(rs);
}
}
// Print the immediate operand for the instruction. Generally used for data
// processing instructions.
void ARMDecoder::PrintShiftImm(Instr* instr) {
int rotate = instr->RotateField() * 2;
int immed8 = instr->Immed8Field();
int imm = (immed8 >> rotate) | (immed8 << (32 - rotate));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"#%d",
imm);
}
// Print PU formatting to reduce complexity of FormatOption.
void ARMDecoder::PrintPU(Instr* instr) {
switch (instr->PUField()) {
case 0: {
Print("da");
break;
}
case 1: {
Print("ia");
break;
}
case 2: {
Print("db");
break;
}
case 3: {
Print("ib");
break;
}
default: {
UNREACHABLE();
break;
}
}
}
// Handle all register based formatting in these functions to reduce the
// complexity of FormatOption.
int ARMDecoder::FormatRegister(Instr* instr, const char* format) {
ASSERT(format[0] == 'r');
if (format[1] == 'n') { // 'rn: Rn register
int reg = instr->RnField();
PrintRegister(reg);
return 2;
} else if (format[1] == 'd') { // 'rd: Rd register
int reg = instr->RdField();
PrintRegister(reg);
if (format[2] == '2') { // 'rd2: possibly Rd, Rd+1 register pair
if (instr->HasSign() && !instr->HasL()) {
if ((reg % 2) != 0) {
Print(" *** unknown (odd register pair) ***");
} else {
Print(", ");
PrintRegister(reg + 1);
}
}
return 3;
}
return 2;
} else if (format[1] == 's') { // 'rs: Rs register
int reg = instr->RsField();
PrintRegister(reg);
return 2;
} else if (format[1] == 'm') { // 'rm: Rm register
int reg = instr->RmField();
PrintRegister(reg);
return 2;
} else if (format[1] == 'l') {
// 'rlist: register list for load and store multiple instructions
ASSERT(STRING_STARTS_WITH(format, "rlist"));
int rlist = instr->RlistField();
int reg = 0;
Print("{");
// Print register list in ascending order, by scanning the bit mask.
while (rlist != 0) {
if ((rlist & 1) != 0) {
PrintRegister(reg);
if ((rlist >> 1) != 0) {
Print(", ");
}
}
reg++;
rlist >>= 1;
}
Print("}");
return 5;
}
UNREACHABLE();
return -1;
}
int ARMDecoder::FormatSRegister(Instr* instr, const char* format) {
ASSERT(format[0] == 's');
if (format[1] == 'n') { // 'sn: Sn register
int reg = instr->SnField();
PrintSRegister(reg);
return 2;
} else if (format[1] == 'd') { // 'sd: Sd register
int reg = instr->SdField();
PrintSRegister(reg);
return 2;
} else if (format[1] == 'm') {
int reg = instr->SmField();
if (format[2] == '1') { // 'sm1: S[m+1] register
reg++;
ASSERT(reg < kNumberOfSRegisters);
PrintSRegister(reg);
return 3;
} else { // 'sm: Sm register
PrintSRegister(reg);
return 2;
}
} else if (format[1] == 'l') {
ASSERT(STRING_STARTS_WITH(format, "slist"));
int reg_count = instr->Bits(0, 8);
int start = instr->Bit(22) | (instr->Bits(12, 4) << 1);
Print("{");
for (int i = start; i < start + reg_count; i++) {
PrintSRegister(i);
if (i != start + reg_count - 1) {
Print(", ");
}
}
Print("}");
return 5;
}
UNREACHABLE();
return -1;
}
void ARMDecoder::PrintDRegisterList(int start, int reg_count) {
Print("{");
for (int i = start; i < start + reg_count; i++) {
PrintDRegister(i);
if (i != start + reg_count - 1) {
Print(", ");
}
}
Print("}");
}
int ARMDecoder::FormatDRegister(Instr* instr, const char* format) {
ASSERT(format[0] == 'd');
if (format[1] == 'n') { // 'dn: Dn register
int reg = instr->DnField();
PrintDRegister(reg);
return 2;
} else if (format[1] == 'd') { // 'dd: Dd register
int reg = instr->DdField();
PrintDRegister(reg);
return 2;
} else if (format[1] == 'm') { // 'dm: Dm register
int reg = instr->DmField();
PrintDRegister(reg);
return 2;
} else if (format[1] == 'l') {
ASSERT(STRING_STARTS_WITH(format, "dlist"));
int reg_count = instr->Bits(0, 8) >> 1;
int start = (instr->Bit(22) << 4) | instr->Bits(12, 4);
PrintDRegisterList(start, reg_count);
return 5;
} else if (format[1] == 't') {
ASSERT(STRING_STARTS_WITH(format, "dtbllist"));
int reg_count = instr->Bits(8, 2) + 1;
int start = (instr->Bit(7) << 4) | instr->Bits(16, 4);
PrintDRegisterList(start, reg_count);
return 8;
}
UNREACHABLE();
return -1;
}
int ARMDecoder::FormatQRegister(Instr* instr, const char* format) {
ASSERT(format[0] == 'q');
if (format[1] == 'n') { // 'qn: Qn register
int reg = instr->QnField();
PrintQRegister(reg);
return 2;
} else if (format[1] == 'd') { // 'qd: Qd register
int reg = instr->QdField();
PrintQRegister(reg);
return 2;
} else if (format[1] == 'm') { // 'qm: Qm register
int reg = instr->QmField();
PrintQRegister(reg);
return 2;
}
UNREACHABLE();
return -1;
}
// FormatOption takes a formatting string and interprets it based on
// the current instructions. The format string points to the first
// character of the option string (the option escape has already been
// consumed by the caller.) FormatOption returns the number of
// characters that were consumed from the formatting string.
int ARMDecoder::FormatOption(Instr* instr, const char* format) {
switch (format[0]) {
case 'a': { // 'a: accumulate multiplies
if (instr->Bit(21) == 0) {
Print("ul");
} else {
Print("la");
}
return 1;
}
case 'b': { // 'b: byte loads or stores
if (instr->HasB()) {
Print("b");
}
return 1;
}
case 'c': { // 'cond: conditional execution
ASSERT(STRING_STARTS_WITH(format, "cond"));
PrintCondition(instr);
return 4;
}
case 'd': {
if (format[1] == 'e') { // 'dest: branch destination
ASSERT(STRING_STARTS_WITH(format, "dest"));
int off = (instr->SImmed24Field() << 2) + 8;
uword destination = reinterpret_cast<uword>(instr) + off;
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%#" Px "",
destination);
return 4;
} else {
return FormatDRegister(instr, format);
}
}
case 'q': {
return FormatQRegister(instr, format);
}
case 'i': { // 'imm12_4, imm4_12, immf, or immd
uint16_t immed16;
if (format[3] == 'f') {
ASSERT(STRING_STARTS_WITH(format, "immf"));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%f",
instr->ImmFloatField());
return 4;
} else if (format[3] == 'd') {
ASSERT(STRING_STARTS_WITH(format, "immd"));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%g",
instr->ImmDoubleField());
return 4;
} else if (format[3] == '1') {
ASSERT(STRING_STARTS_WITH(format, "imm12_4"));
immed16 = instr->BkptField();
} else {
ASSERT(format[3] == '4');
if (format[5] == 'v') {
ASSERT(STRING_STARTS_WITH(format, "imm4_vdup"));
int32_t idx = -1;
int32_t imm4 = instr->Bits(16, 4);
if ((imm4 & 1) != 0) idx = imm4 >> 1;
else if ((imm4 & 2) != 0) idx = imm4 >> 2;
else if ((imm4 & 4) != 0) idx = imm4 >> 3;
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d",
idx);
return 9;
} else {
ASSERT(STRING_STARTS_WITH(format, "imm4_12"));
immed16 = instr->MovwField();
}
}
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"0x%x",
immed16);
return 7;
}
case 'l': { // 'l: branch and link
if (instr->HasLink()) {
Print("l");
}
return 1;
}
case 'm': { // 'memop: load/store instructions
ASSERT(STRING_STARTS_WITH(format, "memop"));
if (instr->HasL() ||
// Extra load/store instructions.
((instr->TypeField() == 0) && instr->HasSign() && !instr->HasH())) {
Print("ldr");
} else {
Print("str");
}
return 5;
}
case 'o': {
if (format[3] == '1') {
if (format[4] == '0') {
// 'off10: 10-bit offset for VFP load and store instructions
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d",
instr->Bits(0, 8) << 2);
} else {
// 'off12: 12-bit offset for load and store instructions.
ASSERT(STRING_STARTS_WITH(format, "off12"));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d",
instr->Offset12Field());
}
return 5;
}
// 'off8: 8-bit offset for extra load and store instructions.
ASSERT(STRING_STARTS_WITH(format, "off8"));
int offs8 = (instr->ImmedHField() << 4) | instr->ImmedLField();
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%d",
offs8);
return 4;
}
case 'p': { // 'pu: P and U bits for load and store instructions.
ASSERT(STRING_STARTS_WITH(format, "pu"));
PrintPU(instr);
return 2;
}
case 'r': {
return FormatRegister(instr, format);
}
case 's': {
if (format[1] == 'h') { // 'shift_op or 'shift_rm
if (format[6] == 'o') { // 'shift_op
ASSERT(STRING_STARTS_WITH(format, "shift_op"));
if (instr->TypeField() == 0) {
PrintShiftRm(instr);
} else {
ASSERT(instr->TypeField() == 1);
PrintShiftImm(instr);
}
return 8;
} else { // 'shift_rm
ASSERT(STRING_STARTS_WITH(format, "shift_rm"));
PrintShiftRm(instr);
return 8;
}
} else if (format[1] == 'v') { // 'svc
ASSERT(STRING_STARTS_WITH(format, "svc"));
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"0x%x",
instr->SvcField());
return 3;
} else if (format[1] == 'z') {
// 'sz: Size field of SIMD instructions.
int sz = instr->Bits(20, 2);
char const* sz_str;
switch (sz) {
case 0: sz_str = "b"; break;
case 1: sz_str = "h"; break;
case 2: sz_str = "w"; break;
case 3: sz_str = "l"; break;
default: sz_str = "?"; break;
}
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%s",
sz_str);
return 2;
} else if (format[1] == ' ') {
// 's: S field of data processing instructions.
if (instr->HasS()) {
Print("s");
}
return 1;
} else {
return FormatSRegister(instr, format);
}
}
case 't': { // 'target: target of branch instructions.
ASSERT(STRING_STARTS_WITH(format, "target"));
int off = (instr->SImmed24Field() << 2) + 8;
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
"%+d",
off);
return 6;
}
case 'u': { // 'u: signed or unsigned multiplies.
if (instr->Bit(22) == 0) {
Print("u");
} else {
Print("s");
}
return 1;
}
case 'w': { // 'w: W field of load and store instructions.
if (instr->HasW()) {
Print("!");
}
return 1;
}
case 'x': { // 'x: type of extra load/store instructions.
if (!instr->HasSign()) {
Print("h");
} else if (instr->HasL()) {
if (instr->HasH()) {
Print("sh");
} else {
Print("sb");
}
} else {
Print("d");
}
return 1;
}
default: {
UNREACHABLE();
break;
}
}
UNREACHABLE();
return -1;
}
// Format takes a formatting string for a whole instruction and prints it into
// the output buffer. All escaped options are handed to FormatOption to be
// parsed further.
void ARMDecoder::Format(Instr* instr, const char* format) {
char cur = *format++;
while ((cur != 0) && (buffer_pos_ < (buffer_size_ - 1))) {
if (cur == '\'') { // Single quote is used as the formatting escape.
format += FormatOption(instr, format);
} else {
buffer_[buffer_pos_++] = cur;
}
cur = *format++;
}
buffer_[buffer_pos_] = '\0';
}
// For currently unimplemented decodings the disassembler calls Unknown(instr)
// which will just print "unknown" of the instruction bits.
void ARMDecoder::Unknown(Instr* instr) {
Format(instr, "unknown");
}
void ARMDecoder::DecodeType01(Instr* instr) {
if (!instr->IsDataProcessing()) {
// miscellaneous, multiply, sync primitives, extra loads and stores.
if (instr->IsMiscellaneous()) {
switch (instr->Bits(4, 3)) {
case 1: {
if (instr->Bits(21, 2) == 0x3) {
Format(instr, "clz'cond 'rd, 'rm");
} else if (instr->Bits(21, 2) == 0x1) {
Format(instr, "bx'cond 'rm");
} else {
Unknown(instr);
}
break;
}
case 3: {
if (instr->Bits(21, 2) == 0x1) {
Format(instr, "blx'cond 'rm");
} else {
// Could be inlined constant.
Unknown(instr);
}
break;
}
case 7: {
if ((instr->Bits(21, 2) == 0x1) && (instr->ConditionField() == AL)) {
Format(instr, "bkpt #'imm12_4");
if (instr->BkptField() == Instr::kStopMessageCode) {
const char* message = *reinterpret_cast<const char**>(
reinterpret_cast<intptr_t>(instr) - Instr::kInstrSize);
buffer_pos_ += OS::SNPrint(current_position_in_buffer(),
remaining_size_in_buffer(),
" ; \"%s\"",
message);
}
} else {
// Format(instr, "smc'cond");
Unknown(instr); // Not used.
}
break;
}
default: {
Unknown(instr); // Not used.
break;
}
}
} else if (instr->IsMultiplyOrSyncPrimitive()) {
if (instr->Bit(24) == 0) {
// multiply instructions
switch (instr->Bits(21, 3)) {
case 0: {
// Assembler registers rd, rn, rm are encoded as rn, rm, rs.
Format(instr, "mul'cond's 'rn, 'rm, 'rs");
break;
}
case 1: {
// Assembler registers rd, rn, rm, ra are encoded as rn, rm, rs, rd.
Format(instr, "mla'cond's 'rn, 'rm, 'rs, 'rd");
break;
}
case 2: {
if (TargetCPUFeatures::arm_version() == ARMv5TE) {
Unknown(instr);
return;
}
// Registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs.
Format(instr, "umaal'cond's 'rd, 'rn, 'rm, 'rs");
break;
}
case 3: {
if (TargetCPUFeatures::arm_version() != ARMv7) {
Unknown(instr);
return;
}
// Assembler registers rd, rn, rm, ra are encoded as rn, rm, rs, rd.
Format(instr, "mls'cond's 'rn, 'rm, 'rs, 'rd");
break;
}
case 4: {
// Registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs.
Format(instr, "umull'cond's 'rd, 'rn, 'rm, 'rs");
break;
}
case 5: {
// Registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs.
Format(instr, "umlal'cond's 'rd, 'rn, 'rm, 'rs");
break;
}
case 6: {
// Registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs.
Format(instr, "smull'cond's 'rd, 'rn, 'rm, 'rs");
break;
}
default: {
Unknown(instr); // Not used.
break;
}
}
} else {
if (TargetCPUFeatures::arm_version() == ARMv5TE) {
// strex and ldrex are only supported after ARMv6.
Unknown(instr);
return;
}
// synchronization primitives
switch (instr->Bits(20, 4)) {
case 8: {
Format(instr, "strex'cond 'rd, 'rm, ['rn]");
break;
}
case 9: {
Format(instr, "ldrex'cond 'rd, ['rn]");
break;
}
default: {
Unknown(instr); // Not used.
break;
}
}
}
} else if (instr->Bit(25) == 1) {
// 16-bit immediate loads, msr (immediate), and hints
switch (instr->Bits(20, 5)) {
case 16: {
if (TargetCPUFeatures::arm_version() == ARMv7) {
Format(instr, "movw'cond 'rd, #'imm4_12");
} else {
Unknown(instr);
}
break;
}
case 18: {
if ((instr->Bits(16, 4) == 0) && (instr->Bits(0, 8) == 0)) {
Format(instr, "nop'cond");
} else {
Unknown(instr); // Not used.
}
break;
}
case 20: {
if (TargetCPUFeatures::arm_version() == ARMv7) {
Format(instr, "movt'cond 'rd, #'imm4_12");
} else {
Unknown(instr);
}
break;
}
default: {
Unknown(instr); // Not used.
break;
}
}
} else {
// extra load/store instructions
switch (instr->PUField()) {
case 0: {
if (instr->Bit(22) == 0) {
Format(instr, "'memop'cond'x 'rd2, ['rn], -'rm");
} else {
Format(instr, "'memop'cond'x 'rd2, ['rn], #-'off8");
}
break;
}
case 1: {
if (instr->Bit(22) == 0) {
Format(instr, "'memop'cond'x 'rd2, ['rn], +'rm");
} else {
Format(instr, "'memop'cond'x 'rd2, ['rn], #+'off8");
}
break;
}
case 2: {
if (instr->Bit(22) == 0) {
Format(instr, "'memop'cond'x 'rd2, ['rn, -'rm]'w");
} else {
Format(instr, "'memop'cond'x 'rd2, ['rn, #-'off8]'w");
}
break;
}
case 3: {
if (instr->Bit(22) == 0) {
Format(instr, "'memop'cond'x 'rd2, ['rn, +'rm]'w");
} else {
Format(instr, "'memop'cond'x 'rd2, ['rn, #+'off8]'w");
}
break;
}
default: {
// The PU field is a 2-bit field.
UNREACHABLE();
break;
}
}
}
} else {
switch (instr->OpcodeField()) {
case AND: {
Format(instr, "and'cond's 'rd, 'rn, 'shift_op");
break;
}
case EOR: {
Format(instr, "eor'cond's 'rd, 'rn, 'shift_op");
break;
}
case SUB: {
Format(instr, "sub'cond's 'rd, 'rn, 'shift_op");
break;
}
case RSB: {
Format(instr, "rsb'cond's 'rd, 'rn, 'shift_op");
break;
}
case ADD: {
Format(instr, "add'cond's 'rd, 'rn, 'shift_op");
break;
}
case ADC: {
Format(instr, "adc'cond's 'rd, 'rn, 'shift_op");
break;
}
case SBC: {
Format(instr, "sbc'cond's 'rd, 'rn, 'shift_op");
break;
}
case RSC: {
Format(instr, "rsc'cond's 'rd, 'rn, 'shift_op");
break;
}
case TST: {
if (instr->HasS()) {
Format(instr, "tst'cond 'rn, 'shift_op");
} else {
Unknown(instr); // Not used.
}
break;
}
case TEQ: {
if (instr->HasS()) {
Format(instr, "teq'cond 'rn, 'shift_op");
} else {
Unknown(instr); // Not used.
}
break;
}
case CMP: {
if (instr->HasS()) {
Format(instr, "cmp'cond 'rn, 'shift_op");
} else {
Unknown(instr); // Not used.
}
break;
}
case CMN: {
if (instr->HasS()) {
Format(instr, "cmn'cond 'rn, 'shift_op");
} else {
Unknown(instr); // Not used.
}
break;
}
case ORR: {
Format(instr, "orr'cond's 'rd, 'rn, 'shift_op");
break;
}
case MOV: {
Format(instr, "mov'cond's 'rd, 'shift_op");
break;
}
case BIC: {
Format(instr, "bic'cond's 'rd, 'rn, 'shift_op");
break;
}
case MVN: {
Format(instr, "mvn'cond's 'rd, 'shift_op");
break;
}
default: {
// The Opcode field is a 4-bit field.
UNREACHABLE();
break;
}
}
}
}
void ARMDecoder::DecodeType2(Instr* instr) {
switch (instr->PUField()) {
case 0: {
if (instr->HasW()) {
Unknown(instr); // Not used.
} else {
Format(instr, "'memop'cond'b 'rd, ['rn], #-'off12");
}
break;
}
case 1: {
if (instr->HasW()) {
Unknown(instr); // Not used.
} else {
Format(instr, "'memop'cond'b 'rd, ['rn], #+'off12");
}
break;
}
case 2: {
Format(instr, "'memop'cond'b 'rd, ['rn, #-'off12]'w");
break;
}
case 3: {
Format(instr, "'memop'cond'b 'rd, ['rn, #+'off12]'w");
break;
}
default: {
// The PU field is a 2-bit field.
UNREACHABLE();
break;
}
}
}
void ARMDecoder::DecodeType3(Instr* instr) {
if (instr->IsDivision()) {
if (!TargetCPUFeatures::integer_division_supported()) {
Unknown(instr);
return;
}
if (instr->Bit(21)) {
Format(instr, "udiv'cond 'rn, 'rs, 'rm");
} else {
Format(instr, "sdiv'cond 'rn, 'rs, 'rm");
}
return;
}
switch (instr->PUField()) {
case 0: {
if (instr->HasW()) {
Unknown(instr);
} else {
Format(instr, "'memop'cond'b 'rd, ['rn], -'shift_rm");
}
break;
}
case 1: {
if (instr->HasW()) {
Unknown(instr);
} else {
Format(instr, "'memop'cond'b 'rd, ['rn], +'shift_rm");
}
break;
}
case 2: {
Format(instr, "'memop'cond'b 'rd, ['rn, -'shift_rm]'w");
break;
}
case 3: {
Format(instr, "'memop'cond'b 'rd, ['rn, +'shift_rm]'w");
break;
}
default: {
// The PU field is a 2-bit field.
UNREACHABLE();
break;
}
}
}
void ARMDecoder::DecodeType4(Instr* instr) {
if (instr->Bit(22) == 1) {
Unknown(instr); // Privileged mode currently not supported.
} else if (instr->HasL()) {
Format(instr, "ldm'cond'pu 'rn'w, 'rlist");
} else {
Format(instr, "stm'cond'pu 'rn'w, 'rlist");
}
}
void ARMDecoder::DecodeType5(Instr* instr) {
Format(instr, "b'l'cond 'target ; 'dest");
}
void ARMDecoder::DecodeType6(Instr* instr) {
if (instr->IsVFPDoubleTransfer()) {
if (instr->Bit(8) == 0) {
if (instr->Bit(20) == 1) {
Format(instr, "vmovrrs'cond 'rd, 'rn, {'sm, 'sm1}");
} else {
Format(instr, "vmovsrr'cond {'sm, 'sm1}, 'rd, 'rn");
}
} else {
if (instr->Bit(20) == 1) {
Format(instr, "vmovrrd'cond 'rd, 'rn, 'dm");
} else {
Format(instr, "vmovdrr'cond 'dm, 'rd, 'rn");
}
}
} else if (instr-> IsVFPLoadStore()) {
if (instr->Bit(8) == 0) {
if (instr->Bit(20) == 1) { // vldrs
if (instr->Bit(23) == 1) {
Format(instr, "vldrs'cond 'sd, ['rn, #+'off10]");
} else {
Format(instr, "vldrs'cond 'sd, ['rn, #-'off10]");
}
} else { // vstrs
if (instr->Bit(23) == 1) {
Format(instr, "vstrs'cond 'sd, ['rn, #+'off10]");
} else {
Format(instr, "vstrs'cond 'sd, ['rn, #-'off10]");
}
}
} else {
if (instr->Bit(20) == 1) { // vldrd
if (instr->Bit(23) == 1) {
Format(instr, "vldrd'cond 'dd, ['rn, #+'off10]");
} else {
Format(instr, "vldrd'cond 'dd, ['rn, #-'off10]");
}
} else { // vstrd
if (instr->Bit(23) == 1) {
Format(instr, "vstrd'cond 'dd, ['rn, #+'off10]");
} else {
Format(instr, "vstrd'cond 'dd, ['rn, #-'off10]");
}
}
}
} else if (instr->IsVFPMultipleLoadStore()) {
if (instr->HasL()) { // vldm
if (instr->Bit(8)) { // vldmd
Format(instr, "vldmd'cond'pu 'rn'w, 'dlist");
} else { // vldms
Format(instr, "vldms'cond'pu 'rn'w, 'slist");
}
} else { // vstm
if (instr->Bit(8)) { // vstmd
Format(instr, "vstmd'cond'pu 'rn'w, 'dlist");
} else { // vstms
Format(instr, "vstms'cond'pu 'rn'w, 'slist");
}
}
} else {
Unknown(instr);
}
}
void ARMDecoder::DecodeType7(Instr* instr) {
if (instr->Bit(24) == 1) {
Format(instr, "svc'cond #'svc");
} else if (instr->IsVFPDataProcessingOrSingleTransfer()) {
if (instr->Bit(4) == 0) {
// VFP Data Processing
switch (instr->Bits(20, 4) & 0xb) {
case 0: { // vmla, vmls floating-point
if (instr->Bit(8) == 0) {
if (instr->Bit(6) == 0) {
Format(instr, "vmlas'cond 'sd, 'sn, 'sm");
} else {
Format(instr, "vmlss'cond 'sd, 'sn, 'sm");
}
} else {
if (instr->Bit(6) == 0) {
Format(instr, "vmlad'cond 'dd, 'dn, 'dm");
} else {
Format(instr, "vmlsd'cond 'dd, 'dn, 'dm");
}
}
break;
}
case 1: // vnmla, vnmls, vnmul
default: {
Unknown(instr);
break;
}
case 2: { // vmul
if (instr->Bit(8) == 0) {
Format(instr, "vmuls'cond 'sd, 'sn, 'sm");
} else {
Format(instr, "vmuld'cond 'dd, 'dn, 'dm");
}
break;
}
case 8: { // vdiv
if (instr->Bit(8) == 0) {
Format(instr, "vdivs'cond 'sd, 'sn, 'sm");
} else {
Format(instr, "vdivd'cond 'dd, 'dn, 'dm");
}
break;
}
case 3: { // vadd, vsub floating-point
if (instr->Bit(8) == 0) {
if (instr->Bit(6) == 0) {
Format(instr, "vadds'cond 'sd, 'sn, 'sm");
} else {
Format(instr, "vsubs'cond 'sd, 'sn, 'sm");
}
} else {
if (instr->Bit(6) == 0) {
Format(instr, "vaddd'cond 'dd, 'dn, 'dm");
} else {
Format(instr, "vsubd'cond 'dd, 'dn, 'dm");
}
}
break;
}
case 0xb: { // Other VFP data-processing instructions
if (instr->Bit(6) == 0) { // vmov immediate
if (instr->Bit(8) == 0) {
Format(instr, "vmovs'cond 'sd, #'immf");
} else {
Format(instr, "vmovd'cond 'dd, #'immd");
}
break;
}
switch (instr->Bits(16, 4)) {
case 0: { // vmov register, vabs
switch (instr->Bits(6, 2)) {
case 1: { // vmov register
if (instr->Bit(8) == 0) {
Format(instr, "vmovs'cond 'sd, 'sm");
} else {
Format(instr, "vmovd'cond 'dd, 'dm");
}
break;
}
case 3: { // vabs
if (instr->Bit(8) == 0) {
Format(instr, "vabss'cond 'sd, 'sm");
} else {
Format(instr, "vabsd'cond 'dd, 'dm");
}
break;
}
default: {
Unknown(instr);
break;
}
}
break;
}
case 1: { // vneg, vsqrt
switch (instr->Bits(6, 2)) {
case 1: { // vneg
if (instr->Bit(8) == 0) {
Format(instr, "vnegs'cond 'sd, 'sm");
} else {
Format(instr, "vnegd'cond 'dd, 'dm");
}
break;
}
case 3: { // vsqrt
if (instr->Bit(8) == 0) {
Format(instr, "vsqrts'cond 'sd, 'sm");
} else {
Format(instr, "vsqrtd'cond 'dd, 'dm");
}
break;
}
default: {
Unknown(instr);
break;
}
}
break;
}
case 4: // vcmp, vcmpe
case 5: { // vcmp #0.0, vcmpe #0.0
if (instr->Bit(7) == 1) { // vcmpe
Unknown(instr);
} else {
if (instr->Bit(8) == 0) { // vcmps
if (instr->Bit(16) == 0) {
Format(instr, "vcmps'cond 'sd, 'sm");
} else {
Format(instr, "vcmps'cond 'sd, #0.0");
}
} else { // vcmpd
if (instr->Bit(16) == 0) {
Format(instr, "vcmpd'cond 'dd, 'dm");
} else {
Format(instr, "vcmpd'cond 'dd, #0.0");
}
}
}
break;
}
case 7: { // vcvt between double-precision and single-precision
if (instr->Bit(8) == 0) {
Format(instr, "vcvtds'cond 'dd, 'sm");
} else {
Format(instr, "vcvtsd'cond 'sd, 'dm");
}
break;
}
case 8: { // vcvt, vcvtr between floating-point and integer
if (instr->Bit(8) == 0) {
if (instr->Bit(7) == 0) {
Format(instr, "vcvtsu'cond 'sd, 'sm");
} else {
Format(instr, "vcvtsi'cond 'sd, 'sm");
}
} else {
if (instr->Bit(7) == 0) {
Format(instr, "vcvtdu'cond 'dd, 'sm");
} else {
Format(instr, "vcvtdi'cond 'dd, 'sm");
}
}
break;
}
case 12:
case 13: { // vcvt, vcvtr between floating-point and integer
if (instr->Bit(7) == 0) {
// We only support round-to-zero mode
Unknown(instr);
break;
}
if (instr->Bit(8) == 0) {
if (instr->Bit(16) == 0) {
Format(instr, "vcvtus'cond 'sd, 'sm");
} else {
Format(instr, "vcvtis'cond 'sd, 'sm");
}
} else {
if (instr->Bit(16) == 0) {
Format(instr, "vcvtud'cond 'sd, 'dm");
} else {
Format(instr, "vcvtid'cond 'sd, 'dm");
}
}
break;
}
case 2: // vcvtb, vcvtt
case 3: // vcvtb, vcvtt
case 9: // undefined
case 10: // vcvt between floating-point and fixed-point
case 11: // vcvt between floating-point and fixed-point
case 14: // vcvt between floating-point and fixed-point
case 15: // vcvt between floating-point and fixed-point
default: {
Unknown(instr);
break;
}
}
}
break;
}
} else {
// 8, 16, or 32-bit Transfer between ARM Core and VFP
if ((instr->Bits(21, 3) == 0) && (instr->Bit(8) == 0)) {
if (instr->Bit(20) == 0) {
Format(instr, "vmovs'cond 'sn, 'rd");
} else {
Format(instr, "vmovr'cond 'rd, 'sn");
}
} else if ((instr->Bits(22, 3) == 0) && (instr->Bit(20) == 0) &&
(instr->Bit(8) == 1) && (instr->Bits(5, 2) == 0)) {
if (instr->Bit(21) == 0) {
Format(instr, "vmovd'cond 'dn[0], 'rd");
} else {
Format(instr, "vmovd'cond 'dn[1], 'rd");
}
} else if ((instr->Bits(20, 4) == 0xf) && (instr->Bit(8) == 0)) {
if (instr->Bits(12, 4) == 0xf) {
Format(instr, "vmrs'cond APSR, FPSCR");
} else {
Format(instr, "vmrs'cond 'rd, FPSCR");
}
} else {
Unknown(instr);
}
}
} else {
Unknown(instr);
}
}
void ARMDecoder::DecodeSIMDDataProcessing(Instr* instr) {
ASSERT(instr->ConditionField() == kSpecialCondition);
if (instr->Bit(6) == 1) {
if ((instr->Bits(8, 4) == 8) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 2) == 0)) {
Format(instr, "vaddq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 13) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 2) == 0) && (instr->Bit(21) == 0)) {
Format(instr, "vaddqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 8) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 2) == 2)) {
Format(instr, "vsubq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 13) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 2) == 0) && (instr->Bit(21) == 1)) {
Format(instr, "vsubqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 9) && (instr->Bit(4) == 1) &&
(instr->Bits(23, 2) == 0)) {
Format(instr, "vmulq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 13) && (instr->Bit(4) == 1) &&
(instr->Bits(23, 2) == 2) && (instr->Bit(21) == 0)) {
Format(instr, "vmulqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 4) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 5) == 4)) {
Format(instr, "vshlqi'sz 'qd, 'qm, 'qn");
} else if ((instr->Bits(8, 4) == 4) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 5) == 6)) {
Format(instr, "vshlqu'sz 'qd, 'qm, 'qn");
} else if ((instr->Bits(8, 4) == 1) && (instr->Bit(4) == 1) &&
(instr->Bits(20, 2) == 0) && (instr->Bits(23, 2) == 2)) {
Format(instr, "veorq 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 1) && (instr->Bit(4) == 1) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vornq 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 1) && (instr->Bit(4) == 1) &&
(instr->Bits(20, 2) == 2) && (instr->Bits(23, 2) == 0)) {
if (instr->QmField() == instr->QnField()) {
Format(instr, "vmovq 'qd, 'qm");
} else {
Format(instr, "vorrq 'qd, 'qm");
}
} else if ((instr->Bits(8, 4) == 1) && (instr->Bit(4) == 1) &&
(instr->Bits(20, 2) == 0) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vandq 'qd, 'qn, 'qm");
} else if ((instr->Bits(7, 5) == 11) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 5) == 7) &&
(instr->Bits(16, 4) == 0)) {
Format(instr, "vmvnq 'qd, 'qm");
} else if ((instr->Bits(8, 4) == 15) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 2) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vminqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 15) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 0) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vmaxqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 7) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 3) &&
(instr->Bit(7) == 0) && (instr->Bits(16, 4) == 9)) {
Format(instr, "vabsqs 'qd, 'qm");
} else if ((instr->Bits(8, 4) == 7) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 3) &&
(instr->Bit(7) == 1) && (instr->Bits(16, 4) == 9)) {
Format(instr, "vnegqs 'qd, 'qm");
} else if ((instr->Bits(7, 5) == 10) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 3) &&
(instr->Bits(16, 4) == 11)) {
Format(instr, "vrecpeqs 'qd, 'qm");
} else if ((instr->Bits(8, 4) == 15) && (instr->Bit(4) == 1) &&
(instr->Bits(20, 2) == 0) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vrecpsqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 5) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 3) &&
(instr->Bit(7) == 1) && (instr->Bits(16, 4) == 11)) {
Format(instr, "vrsqrteqs 'qd, 'qm");
} else if ((instr->Bits(8, 4) == 15) && (instr->Bit(4) == 1) &&
(instr->Bits(20, 2) == 2) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vrsqrtsqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 12) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 3) &&
(instr->Bit(7) == 0)) {
int32_t imm4 = instr->Bits(16, 4);
if (imm4 & 1) {
Format(instr, "vdupb 'qd, 'dm['imm4_vdup]");
} else if (imm4 & 2) {
Format(instr, "vduph 'qd, 'dm['imm4_vdup]");
} else if (imm4 & 4) {
Format(instr, "vdupw 'qd, 'dm['imm4_vdup]");
} else {
Unknown(instr);
}
} else if ((instr->Bits(8, 4) == 1) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 3) && (instr->Bits(23, 2) == 3) &&
(instr->Bit(7) == 1) && (instr->Bits(16, 4) == 10)) {
Format(instr, "vzipqw 'qd, 'qm");
} else if ((instr->Bits(8, 4) == 8) && (instr->Bit(4) == 1) &&
(instr->Bits(23, 2) == 2)) {
Format(instr, "vceqq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 14) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 0) && (instr->Bits(23, 2) == 0)) {
Format(instr, "vceqqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 3) && (instr->Bit(4) == 1) &&
(instr->Bits(23, 2) == 0)) {
Format(instr, "vcgeq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 3) && (instr->Bit(4) == 1) &&
(instr->Bits(23, 2) == 2)) {
Format(instr, "vcugeq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 14) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 0) && (instr->Bits(23, 2) == 2)) {
Format(instr, "vcgeqs 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 3) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 2) == 0)) {
Format(instr, "vcgtq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 3) && (instr->Bit(4) == 0) &&
(instr->Bits(23, 2) == 2)) {
Format(instr, "vcugtq'sz 'qd, 'qn, 'qm");
} else if ((instr->Bits(8, 4) == 14) && (instr->Bit(4) == 0) &&
(instr->Bits(20, 2) == 2) && (instr->Bits(23, 2) == 2)) {
Format(instr, "vcgtqs 'qd, 'qn, 'qm");
} else {
Unknown(instr);
}
} else {
if ((instr->Bits(23, 2) == 3) && (instr->Bits(20, 2) == 3) &&
(instr->Bits(10, 2) == 2) && (instr->Bit(4) == 0)) {
Format(instr, "vtbl 'dd, 'dtbllist, 'dm");
} else {
Unknown(instr);
}
}
}
void ARMDecoder::InstructionDecode(uword pc) {
Instr* instr = Instr::At(pc);
if (instr->ConditionField() == kSpecialCondition) {
if ((instr->InstructionBits() == static_cast<int32_t>(0xf57ff01f)) &&
(TargetCPUFeatures::arm_version() != ARMv5TE)) {
Format(instr, "clrex");
} else {
if (instr->IsSIMDDataProcessing()) {
DecodeSIMDDataProcessing(instr);
} else {
Unknown(instr);
}
}
} else {
switch (instr->TypeField()) {
case 0:
case 1: {
DecodeType01(instr);
break;
}
case 2: {
DecodeType2(instr);
break;
}
case 3: {
DecodeType3(instr);
break;
}
case 4: {
DecodeType4(instr);
break;
}
case 5: {
DecodeType5(instr);
break;
}
case 6: {
DecodeType6(instr);
break;
}
case 7: {
DecodeType7(instr);
break;
}
default: {
// The type field is 3-bits in the ARM encoding.
UNREACHABLE();
break;
}
}
}
}
void Disassembler::DecodeInstruction(char* hex_buffer, intptr_t hex_size,
char* human_buffer, intptr_t human_size,
int* out_instr_size, uword pc) {
ARMDecoder decoder(human_buffer, human_size);
decoder.InstructionDecode(pc);
int32_t instruction_bits = Instr::At(pc)->InstructionBits();
OS::SNPrint(hex_buffer, hex_size, "%08x", instruction_bits);
if (out_instr_size) {
*out_instr_size = Instr::kInstrSize;
}
}
} // namespace dart
#endif // defined TARGET_ARCH_ARM