Source code
Revision control
Copy as Markdown
Other Tools
// Copyright 2015, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of ARM Limited nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "jit/arm64/vixl/Disasm-vixl.h"
#include "mozilla/Sprintf.h"
#include <cstdlib>
namespace vixl {
Disassembler::Disassembler() {
buffer_size_ = 256;
buffer_ = reinterpret_cast<char*>(malloc(buffer_size_));
buffer_pos_ = 0;
own_buffer_ = true;
code_address_offset_ = 0;
}
Disassembler::Disassembler(char* text_buffer, int buffer_size) {
buffer_size_ = buffer_size;
buffer_ = text_buffer;
buffer_pos_ = 0;
own_buffer_ = false;
code_address_offset_ = 0;
}
Disassembler::~Disassembler() {
if (own_buffer_) {
free(buffer_);
}
}
char* Disassembler::GetOutput() {
return buffer_;
}
void Disassembler::VisitAddSubImmediate(const Instruction* instr) {
bool rd_is_zr = RdIsZROrSP(instr);
bool stack_op = (rd_is_zr || RnIsZROrSP(instr)) &&
(instr->ImmAddSub() == 0) ? true : false;
const char *mnemonic = "";
const char *form = "'Rds, 'Rns, 'IAddSub";
const char *form_cmp = "'Rns, 'IAddSub";
const char *form_mov = "'Rds, 'Rns";
switch (instr->Mask(AddSubImmediateMask)) {
case ADD_w_imm:
case ADD_x_imm: {
mnemonic = "add";
if (stack_op) {
mnemonic = "mov";
form = form_mov;
}
break;
}
case ADDS_w_imm:
case ADDS_x_imm: {
mnemonic = "adds";
if (rd_is_zr) {
mnemonic = "cmn";
form = form_cmp;
}
break;
}
case SUB_w_imm:
case SUB_x_imm: mnemonic = "sub"; break;
case SUBS_w_imm:
case SUBS_x_imm: {
mnemonic = "subs";
if (rd_is_zr) {
mnemonic = "cmp";
form = form_cmp;
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitAddSubShifted(const Instruction* instr) {
bool rd_is_zr = RdIsZROrSP(instr);
bool rn_is_zr = RnIsZROrSP(instr);
const char *mnemonic = "";
const char *form = "'Rd, 'Rn, 'Rm'NDP";
const char *form_cmp = "'Rn, 'Rm'NDP";
const char *form_neg = "'Rd, 'Rm'NDP";
switch (instr->Mask(AddSubShiftedMask)) {
case ADD_w_shift:
case ADD_x_shift: mnemonic = "add"; break;
case ADDS_w_shift:
case ADDS_x_shift: {
mnemonic = "adds";
if (rd_is_zr) {
mnemonic = "cmn";
form = form_cmp;
}
break;
}
case SUB_w_shift:
case SUB_x_shift: {
mnemonic = "sub";
if (rn_is_zr) {
mnemonic = "neg";
form = form_neg;
}
break;
}
case SUBS_w_shift:
case SUBS_x_shift: {
mnemonic = "subs";
if (rd_is_zr) {
mnemonic = "cmp";
form = form_cmp;
} else if (rn_is_zr) {
mnemonic = "negs";
form = form_neg;
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitAddSubExtended(const Instruction* instr) {
bool rd_is_zr = RdIsZROrSP(instr);
const char *mnemonic = "";
Extend mode = static_cast<Extend>(instr->ExtendMode());
const char *form = ((mode == UXTX) || (mode == SXTX)) ?
"'Rds, 'Rns, 'Xm'Ext" : "'Rds, 'Rns, 'Wm'Ext";
const char *form_cmp = ((mode == UXTX) || (mode == SXTX)) ?
"'Rns, 'Xm'Ext" : "'Rns, 'Wm'Ext";
switch (instr->Mask(AddSubExtendedMask)) {
case ADD_w_ext:
case ADD_x_ext: mnemonic = "add"; break;
case ADDS_w_ext:
case ADDS_x_ext: {
mnemonic = "adds";
if (rd_is_zr) {
mnemonic = "cmn";
form = form_cmp;
}
break;
}
case SUB_w_ext:
case SUB_x_ext: mnemonic = "sub"; break;
case SUBS_w_ext:
case SUBS_x_ext: {
mnemonic = "subs";
if (rd_is_zr) {
mnemonic = "cmp";
form = form_cmp;
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitAddSubWithCarry(const Instruction* instr) {
bool rn_is_zr = RnIsZROrSP(instr);
const char *mnemonic = "";
const char *form = "'Rd, 'Rn, 'Rm";
const char *form_neg = "'Rd, 'Rm";
switch (instr->Mask(AddSubWithCarryMask)) {
case ADC_w:
case ADC_x: mnemonic = "adc"; break;
case ADCS_w:
case ADCS_x: mnemonic = "adcs"; break;
case SBC_w:
case SBC_x: {
mnemonic = "sbc";
if (rn_is_zr) {
mnemonic = "ngc";
form = form_neg;
}
break;
}
case SBCS_w:
case SBCS_x: {
mnemonic = "sbcs";
if (rn_is_zr) {
mnemonic = "ngcs";
form = form_neg;
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLogicalImmediate(const Instruction* instr) {
bool rd_is_zr = RdIsZROrSP(instr);
bool rn_is_zr = RnIsZROrSP(instr);
const char *mnemonic = "";
const char *form = "'Rds, 'Rn, 'ITri";
if (instr->ImmLogical() == 0) {
// The immediate encoded in the instruction is not in the expected format.
Format(instr, "unallocated", "(LogicalImmediate)");
return;
}
switch (instr->Mask(LogicalImmediateMask)) {
case AND_w_imm:
case AND_x_imm: mnemonic = "and"; break;
case ORR_w_imm:
case ORR_x_imm: {
mnemonic = "orr";
unsigned reg_size = (instr->SixtyFourBits() == 1) ? kXRegSize
: kWRegSize;
if (rn_is_zr && !IsMovzMovnImm(reg_size, instr->ImmLogical())) {
mnemonic = "mov";
form = "'Rds, 'ITri";
}
break;
}
case EOR_w_imm:
case EOR_x_imm: mnemonic = "eor"; break;
case ANDS_w_imm:
case ANDS_x_imm: {
mnemonic = "ands";
if (rd_is_zr) {
mnemonic = "tst";
form = "'Rn, 'ITri";
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
bool Disassembler::IsMovzMovnImm(unsigned reg_size, uint64_t value) {
VIXL_ASSERT((reg_size == kXRegSize) ||
((reg_size == kWRegSize) && (value <= 0xffffffff)));
// Test for movz: 16 bits set at positions 0, 16, 32 or 48.
if (((value & UINT64_C(0xffffffffffff0000)) == 0) ||
((value & UINT64_C(0xffffffff0000ffff)) == 0) ||
((value & UINT64_C(0xffff0000ffffffff)) == 0) ||
((value & UINT64_C(0x0000ffffffffffff)) == 0)) {
return true;
}
// Test for movn: NOT(16 bits set at positions 0, 16, 32 or 48).
if ((reg_size == kXRegSize) &&
(((~value & UINT64_C(0xffffffffffff0000)) == 0) ||
((~value & UINT64_C(0xffffffff0000ffff)) == 0) ||
((~value & UINT64_C(0xffff0000ffffffff)) == 0) ||
((~value & UINT64_C(0x0000ffffffffffff)) == 0))) {
return true;
}
if ((reg_size == kWRegSize) &&
(((value & 0xffff0000) == 0xffff0000) ||
((value & 0x0000ffff) == 0x0000ffff))) {
return true;
}
return false;
}
void Disassembler::VisitLogicalShifted(const Instruction* instr) {
bool rd_is_zr = RdIsZROrSP(instr);
bool rn_is_zr = RnIsZROrSP(instr);
const char *mnemonic = "";
const char *form = "'Rd, 'Rn, 'Rm'NLo";
switch (instr->Mask(LogicalShiftedMask)) {
case AND_w:
case AND_x: mnemonic = "and"; break;
case BIC_w:
case BIC_x: mnemonic = "bic"; break;
case EOR_w:
case EOR_x: mnemonic = "eor"; break;
case EON_w:
case EON_x: mnemonic = "eon"; break;
case BICS_w:
case BICS_x: mnemonic = "bics"; break;
case ANDS_w:
case ANDS_x: {
mnemonic = "ands";
if (rd_is_zr) {
mnemonic = "tst";
form = "'Rn, 'Rm'NLo";
}
break;
}
case ORR_w:
case ORR_x: {
mnemonic = "orr";
if (rn_is_zr && (instr->ImmDPShift() == 0) && (instr->ShiftDP() == LSL)) {
mnemonic = "mov";
form = "'Rd, 'Rm";
}
break;
}
case ORN_w:
case ORN_x: {
mnemonic = "orn";
if (rn_is_zr) {
mnemonic = "mvn";
form = "'Rd, 'Rm'NLo";
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitConditionalCompareRegister(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Rn, 'Rm, 'INzcv, 'Cond";
switch (instr->Mask(ConditionalCompareRegisterMask)) {
case CCMN_w:
case CCMN_x: mnemonic = "ccmn"; break;
case CCMP_w:
case CCMP_x: mnemonic = "ccmp"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitConditionalCompareImmediate(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Rn, 'IP, 'INzcv, 'Cond";
switch (instr->Mask(ConditionalCompareImmediateMask)) {
case CCMN_w_imm:
case CCMN_x_imm: mnemonic = "ccmn"; break;
case CCMP_w_imm:
case CCMP_x_imm: mnemonic = "ccmp"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitConditionalSelect(const Instruction* instr) {
bool rnm_is_zr = (RnIsZROrSP(instr) && RmIsZROrSP(instr));
bool rn_is_rm = (instr->Rn() == instr->Rm());
const char *mnemonic = "";
const char *form = "'Rd, 'Rn, 'Rm, 'Cond";
const char *form_test = "'Rd, 'CInv";
const char *form_update = "'Rd, 'Rn, 'CInv";
Condition cond = static_cast<Condition>(instr->Condition());
bool invertible_cond = (cond != al) && (cond != nv);
switch (instr->Mask(ConditionalSelectMask)) {
case CSEL_w:
case CSEL_x: mnemonic = "csel"; break;
case CSINC_w:
case CSINC_x: {
mnemonic = "csinc";
if (rnm_is_zr && invertible_cond) {
mnemonic = "cset";
form = form_test;
} else if (rn_is_rm && invertible_cond) {
mnemonic = "cinc";
form = form_update;
}
break;
}
case CSINV_w:
case CSINV_x: {
mnemonic = "csinv";
if (rnm_is_zr && invertible_cond) {
mnemonic = "csetm";
form = form_test;
} else if (rn_is_rm && invertible_cond) {
mnemonic = "cinv";
form = form_update;
}
break;
}
case CSNEG_w:
case CSNEG_x: {
mnemonic = "csneg";
if (rn_is_rm && invertible_cond) {
mnemonic = "cneg";
form = form_update;
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitBitfield(const Instruction* instr) {
unsigned s = instr->ImmS();
unsigned r = instr->ImmR();
unsigned rd_size_minus_1 =
((instr->SixtyFourBits() == 1) ? kXRegSize : kWRegSize) - 1;
const char *mnemonic = "";
const char *form = "";
const char *form_shift_right = "'Rd, 'Rn, 'IBr";
const char *form_extend = "'Rd, 'Wn";
const char *form_bfiz = "'Rd, 'Rn, 'IBZ-r, 'IBs+1";
const char *form_bfx = "'Rd, 'Rn, 'IBr, 'IBs-r+1";
const char *form_lsl = "'Rd, 'Rn, 'IBZ-r";
switch (instr->Mask(BitfieldMask)) {
case SBFM_w:
case SBFM_x: {
mnemonic = "sbfx";
form = form_bfx;
if (r == 0) {
form = form_extend;
if (s == 7) {
mnemonic = "sxtb";
} else if (s == 15) {
mnemonic = "sxth";
} else if ((s == 31) && (instr->SixtyFourBits() == 1)) {
mnemonic = "sxtw";
} else {
form = form_bfx;
}
} else if (s == rd_size_minus_1) {
mnemonic = "asr";
form = form_shift_right;
} else if (s < r) {
mnemonic = "sbfiz";
form = form_bfiz;
}
break;
}
case UBFM_w:
case UBFM_x: {
mnemonic = "ubfx";
form = form_bfx;
if (r == 0) {
form = form_extend;
if (s == 7) {
mnemonic = "uxtb";
} else if (s == 15) {
mnemonic = "uxth";
} else {
form = form_bfx;
}
}
if (s == rd_size_minus_1) {
mnemonic = "lsr";
form = form_shift_right;
} else if (r == s + 1) {
mnemonic = "lsl";
form = form_lsl;
} else if (s < r) {
mnemonic = "ubfiz";
form = form_bfiz;
}
break;
}
case BFM_w:
case BFM_x: {
mnemonic = "bfxil";
form = form_bfx;
if (s < r) {
mnemonic = "bfi";
form = form_bfiz;
}
}
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitExtract(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Rd, 'Rn, 'Rm, 'IExtract";
switch (instr->Mask(ExtractMask)) {
case EXTR_w:
case EXTR_x: {
if (instr->Rn() == instr->Rm()) {
mnemonic = "ror";
form = "'Rd, 'Rn, 'IExtract";
} else {
mnemonic = "extr";
}
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitPCRelAddressing(const Instruction* instr) {
switch (instr->Mask(PCRelAddressingMask)) {
case ADR: Format(instr, "adr", "'Xd, 'AddrPCRelByte"); break;
case ADRP: Format(instr, "adrp", "'Xd, 'AddrPCRelPage"); break;
default: Format(instr, "unimplemented", "(PCRelAddressing)");
}
}
void Disassembler::VisitConditionalBranch(const Instruction* instr) {
switch (instr->Mask(ConditionalBranchMask)) {
case B_cond: Format(instr, "b.'CBrn", "'TImmCond"); break;
default: VIXL_UNREACHABLE();
}
}
void Disassembler::VisitUnconditionalBranchToRegister(
const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "'Xn";
switch (instr->Mask(UnconditionalBranchToRegisterMask)) {
case BR: mnemonic = "br"; break;
case BLR: mnemonic = "blr"; break;
case RET: {
mnemonic = "ret";
if (instr->Rn() == kLinkRegCode) {
form = NULL;
}
break;
}
default: form = "(UnconditionalBranchToRegister)";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitUnconditionalBranch(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'TImmUncn";
switch (instr->Mask(UnconditionalBranchMask)) {
case B: mnemonic = "b"; break;
case BL: mnemonic = "bl"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitDataProcessing1Source(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Rd, 'Rn";
switch (instr->Mask(DataProcessing1SourceMask)) {
#define FORMAT(A, B) \
case A##_w: \
case A##_x: mnemonic = B; break;
FORMAT(RBIT, "rbit");
FORMAT(REV16, "rev16");
FORMAT(REV, "rev");
FORMAT(CLZ, "clz");
FORMAT(CLS, "cls");
#undef FORMAT
case REV32_x: mnemonic = "rev32"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitDataProcessing2Source(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "'Rd, 'Rn, 'Rm";
const char *form_wwx = "'Wd, 'Wn, 'Xm";
switch (instr->Mask(DataProcessing2SourceMask)) {
#define FORMAT(A, B) \
case A##_w: \
case A##_x: mnemonic = B; break;
FORMAT(UDIV, "udiv");
FORMAT(SDIV, "sdiv");
FORMAT(LSLV, "lsl");
FORMAT(LSRV, "lsr");
FORMAT(ASRV, "asr");
FORMAT(RORV, "ror");
#undef FORMAT
case CRC32B: mnemonic = "crc32b"; break;
case CRC32H: mnemonic = "crc32h"; break;
case CRC32W: mnemonic = "crc32w"; break;
case CRC32X: mnemonic = "crc32x"; form = form_wwx; break;
case CRC32CB: mnemonic = "crc32cb"; break;
case CRC32CH: mnemonic = "crc32ch"; break;
case CRC32CW: mnemonic = "crc32cw"; break;
case CRC32CX: mnemonic = "crc32cx"; form = form_wwx; break;
default: form = "(DataProcessing2Source)";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitDataProcessing3Source(const Instruction* instr) {
bool ra_is_zr = RaIsZROrSP(instr);
const char *mnemonic = "";
const char *form = "'Xd, 'Wn, 'Wm, 'Xa";
const char *form_rrr = "'Rd, 'Rn, 'Rm";
const char *form_rrrr = "'Rd, 'Rn, 'Rm, 'Ra";
const char *form_xww = "'Xd, 'Wn, 'Wm";
const char *form_xxx = "'Xd, 'Xn, 'Xm";
switch (instr->Mask(DataProcessing3SourceMask)) {
case MADD_w:
case MADD_x: {
mnemonic = "madd";
form = form_rrrr;
if (ra_is_zr) {
mnemonic = "mul";
form = form_rrr;
}
break;
}
case MSUB_w:
case MSUB_x: {
mnemonic = "msub";
form = form_rrrr;
if (ra_is_zr) {
mnemonic = "mneg";
form = form_rrr;
}
break;
}
case SMADDL_x: {
mnemonic = "smaddl";
if (ra_is_zr) {
mnemonic = "smull";
form = form_xww;
}
break;
}
case SMSUBL_x: {
mnemonic = "smsubl";
if (ra_is_zr) {
mnemonic = "smnegl";
form = form_xww;
}
break;
}
case UMADDL_x: {
mnemonic = "umaddl";
if (ra_is_zr) {
mnemonic = "umull";
form = form_xww;
}
break;
}
case UMSUBL_x: {
mnemonic = "umsubl";
if (ra_is_zr) {
mnemonic = "umnegl";
form = form_xww;
}
break;
}
case SMULH_x: {
mnemonic = "smulh";
form = form_xxx;
break;
}
case UMULH_x: {
mnemonic = "umulh";
form = form_xxx;
break;
}
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitCompareBranch(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Rt, 'TImmCmpa";
switch (instr->Mask(CompareBranchMask)) {
case CBZ_w:
case CBZ_x: mnemonic = "cbz"; break;
case CBNZ_w:
case CBNZ_x: mnemonic = "cbnz"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitTestBranch(const Instruction* instr) {
const char *mnemonic = "";
// If the top bit of the immediate is clear, the tested register is
// disassembled as Wt, otherwise Xt. As the top bit of the immediate is
// encoded in bit 31 of the instruction, we can reuse the Rt form, which
// uses bit 31 (normally "sf") to choose the register size.
const char *form = "'Rt, 'IS, 'TImmTest";
switch (instr->Mask(TestBranchMask)) {
case TBZ: mnemonic = "tbz"; break;
case TBNZ: mnemonic = "tbnz"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitMoveWideImmediate(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Rd, 'IMoveImm";
// Print the shift separately for movk, to make it clear which half word will
// be overwritten. Movn and movz print the computed immediate, which includes
// shift calculation.
switch (instr->Mask(MoveWideImmediateMask)) {
case MOVN_w:
case MOVN_x:
if ((instr->ImmMoveWide()) || (instr->ShiftMoveWide() == 0)) {
if ((instr->SixtyFourBits() == 0) && (instr->ImmMoveWide() == 0xffff)) {
mnemonic = "movn";
} else {
mnemonic = "mov";
form = "'Rd, 'IMoveNeg";
}
} else {
mnemonic = "movn";
}
break;
case MOVZ_w:
case MOVZ_x:
if ((instr->ImmMoveWide()) || (instr->ShiftMoveWide() == 0))
mnemonic = "mov";
else
mnemonic = "movz";
break;
case MOVK_w:
case MOVK_x: mnemonic = "movk"; form = "'Rd, 'IMoveLSL"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
#define LOAD_STORE_LIST(V) \
V(STRB_w, "strb", "'Wt") \
V(STRH_w, "strh", "'Wt") \
V(STR_w, "str", "'Wt") \
V(STR_x, "str", "'Xt") \
V(LDRB_w, "ldrb", "'Wt") \
V(LDRH_w, "ldrh", "'Wt") \
V(LDR_w, "ldr", "'Wt") \
V(LDR_x, "ldr", "'Xt") \
V(LDRSB_x, "ldrsb", "'Xt") \
V(LDRSH_x, "ldrsh", "'Xt") \
V(LDRSW_x, "ldrsw", "'Xt") \
V(LDRSB_w, "ldrsb", "'Wt") \
V(LDRSH_w, "ldrsh", "'Wt") \
V(STR_b, "str", "'Bt") \
V(STR_h, "str", "'Ht") \
V(STR_s, "str", "'St") \
V(STR_d, "str", "'Dt") \
V(LDR_b, "ldr", "'Bt") \
V(LDR_h, "ldr", "'Ht") \
V(LDR_s, "ldr", "'St") \
V(LDR_d, "ldr", "'Dt") \
V(STR_q, "str", "'Qt") \
V(LDR_q, "ldr", "'Qt")
void Disassembler::VisitLoadStorePreIndex(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStorePreIndex)";
switch (instr->Mask(LoadStorePreIndexMask)) {
#define LS_PREINDEX(A, B, C) \
case A##_pre: mnemonic = B; form = C ", ['Xns'ILS]!"; break;
LOAD_STORE_LIST(LS_PREINDEX)
#undef LS_PREINDEX
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStorePostIndex(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStorePostIndex)";
switch (instr->Mask(LoadStorePostIndexMask)) {
#define LS_POSTINDEX(A, B, C) \
case A##_post: mnemonic = B; form = C ", ['Xns]'ILS"; break;
LOAD_STORE_LIST(LS_POSTINDEX)
#undef LS_POSTINDEX
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStoreUnsignedOffset(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStoreUnsignedOffset)";
switch (instr->Mask(LoadStoreUnsignedOffsetMask)) {
#define LS_UNSIGNEDOFFSET(A, B, C) \
case A##_unsigned: mnemonic = B; form = C ", ['Xns'ILU]"; break;
LOAD_STORE_LIST(LS_UNSIGNEDOFFSET)
#undef LS_UNSIGNEDOFFSET
case PRFM_unsigned: mnemonic = "prfm"; form = "'PrefOp, ['Xns'ILU]";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStoreRegisterOffset(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStoreRegisterOffset)";
switch (instr->Mask(LoadStoreRegisterOffsetMask)) {
#define LS_REGISTEROFFSET(A, B, C) \
case A##_reg: mnemonic = B; form = C ", ['Xns, 'Offsetreg]"; break;
LOAD_STORE_LIST(LS_REGISTEROFFSET)
#undef LS_REGISTEROFFSET
case PRFM_reg: mnemonic = "prfm"; form = "'PrefOp, ['Xns, 'Offsetreg]";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStoreUnscaledOffset(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "'Wt, ['Xns'ILS]";
const char *form_x = "'Xt, ['Xns'ILS]";
const char *form_b = "'Bt, ['Xns'ILS]";
const char *form_h = "'Ht, ['Xns'ILS]";
const char *form_s = "'St, ['Xns'ILS]";
const char *form_d = "'Dt, ['Xns'ILS]";
const char *form_q = "'Qt, ['Xns'ILS]";
const char *form_prefetch = "'PrefOp, ['Xns'ILS]";
switch (instr->Mask(LoadStoreUnscaledOffsetMask)) {
case STURB_w: mnemonic = "sturb"; break;
case STURH_w: mnemonic = "sturh"; break;
case STUR_w: mnemonic = "stur"; break;
case STUR_x: mnemonic = "stur"; form = form_x; break;
case STUR_b: mnemonic = "stur"; form = form_b; break;
case STUR_h: mnemonic = "stur"; form = form_h; break;
case STUR_s: mnemonic = "stur"; form = form_s; break;
case STUR_d: mnemonic = "stur"; form = form_d; break;
case STUR_q: mnemonic = "stur"; form = form_q; break;
case LDURB_w: mnemonic = "ldurb"; break;
case LDURH_w: mnemonic = "ldurh"; break;
case LDUR_w: mnemonic = "ldur"; break;
case LDUR_x: mnemonic = "ldur"; form = form_x; break;
case LDUR_b: mnemonic = "ldur"; form = form_b; break;
case LDUR_h: mnemonic = "ldur"; form = form_h; break;
case LDUR_s: mnemonic = "ldur"; form = form_s; break;
case LDUR_d: mnemonic = "ldur"; form = form_d; break;
case LDUR_q: mnemonic = "ldur"; form = form_q; break;
case LDURSB_x: form = form_x; VIXL_FALLTHROUGH();
case LDURSB_w: mnemonic = "ldursb"; break;
case LDURSH_x: form = form_x; VIXL_FALLTHROUGH();
case LDURSH_w: mnemonic = "ldursh"; break;
case LDURSW_x: mnemonic = "ldursw"; form = form_x; break;
case PRFUM: mnemonic = "prfum"; form = form_prefetch; break;
default: form = "(LoadStoreUnscaledOffset)";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadLiteral(const Instruction* instr) {
const char *mnemonic = "ldr";
const char *form = "(LoadLiteral)";
switch (instr->Mask(LoadLiteralMask)) {
case LDR_w_lit: form = "'Wt, 'ILLiteral 'LValue"; break;
case LDR_x_lit: form = "'Xt, 'ILLiteral 'LValue"; break;
case LDR_s_lit: form = "'St, 'ILLiteral 'LValue"; break;
case LDR_d_lit: form = "'Dt, 'ILLiteral 'LValue"; break;
case LDR_q_lit: form = "'Qt, 'ILLiteral 'LValue"; break;
case LDRSW_x_lit: {
mnemonic = "ldrsw";
form = "'Xt, 'ILLiteral 'LValue";
break;
}
case PRFM_lit: {
mnemonic = "prfm";
form = "'PrefOp, 'ILLiteral 'LValue";
break;
}
default: mnemonic = "unimplemented";
}
Format(instr, mnemonic, form);
}
#define LOAD_STORE_PAIR_LIST(V) \
V(STP_w, "stp", "'Wt, 'Wt2", "2") \
V(LDP_w, "ldp", "'Wt, 'Wt2", "2") \
V(LDPSW_x, "ldpsw", "'Xt, 'Xt2", "2") \
V(STP_x, "stp", "'Xt, 'Xt2", "3") \
V(LDP_x, "ldp", "'Xt, 'Xt2", "3") \
V(STP_s, "stp", "'St, 'St2", "2") \
V(LDP_s, "ldp", "'St, 'St2", "2") \
V(STP_d, "stp", "'Dt, 'Dt2", "3") \
V(LDP_d, "ldp", "'Dt, 'Dt2", "3") \
V(LDP_q, "ldp", "'Qt, 'Qt2", "4") \
V(STP_q, "stp", "'Qt, 'Qt2", "4")
void Disassembler::VisitLoadStorePairPostIndex(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStorePairPostIndex)";
switch (instr->Mask(LoadStorePairPostIndexMask)) {
#define LSP_POSTINDEX(A, B, C, D) \
case A##_post: mnemonic = B; form = C ", ['Xns]'ILP" D; break;
LOAD_STORE_PAIR_LIST(LSP_POSTINDEX)
#undef LSP_POSTINDEX
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStorePairPreIndex(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStorePairPreIndex)";
switch (instr->Mask(LoadStorePairPreIndexMask)) {
#define LSP_PREINDEX(A, B, C, D) \
case A##_pre: mnemonic = B; form = C ", ['Xns'ILP" D "]!"; break;
LOAD_STORE_PAIR_LIST(LSP_PREINDEX)
#undef LSP_PREINDEX
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStorePairOffset(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "(LoadStorePairOffset)";
switch (instr->Mask(LoadStorePairOffsetMask)) {
#define LSP_OFFSET(A, B, C, D) \
case A##_off: mnemonic = B; form = C ", ['Xns'ILP" D "]"; break;
LOAD_STORE_PAIR_LIST(LSP_OFFSET)
#undef LSP_OFFSET
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitLoadStorePairNonTemporal(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form;
switch (instr->Mask(LoadStorePairNonTemporalMask)) {
case STNP_w: mnemonic = "stnp"; form = "'Wt, 'Wt2, ['Xns'ILP2]"; break;
case LDNP_w: mnemonic = "ldnp"; form = "'Wt, 'Wt2, ['Xns'ILP2]"; break;
case STNP_x: mnemonic = "stnp"; form = "'Xt, 'Xt2, ['Xns'ILP3]"; break;
case LDNP_x: mnemonic = "ldnp"; form = "'Xt, 'Xt2, ['Xns'ILP3]"; break;
case STNP_s: mnemonic = "stnp"; form = "'St, 'St2, ['Xns'ILP2]"; break;
case LDNP_s: mnemonic = "ldnp"; form = "'St, 'St2, ['Xns'ILP2]"; break;
case STNP_d: mnemonic = "stnp"; form = "'Dt, 'Dt2, ['Xns'ILP3]"; break;
case LDNP_d: mnemonic = "ldnp"; form = "'Dt, 'Dt2, ['Xns'ILP3]"; break;
case STNP_q: mnemonic = "stnp"; form = "'Qt, 'Qt2, ['Xns'ILP4]"; break;
case LDNP_q: mnemonic = "ldnp"; form = "'Qt, 'Qt2, ['Xns'ILP4]"; break;
default: form = "(LoadStorePairNonTemporal)";
}
Format(instr, mnemonic, form);
}
// clang-format off
#define LOAD_STORE_EXCLUSIVE_LIST(V) \
V(STXRB_w, "stxrb", "'Ws, 'Wt") \
V(STXRH_w, "stxrh", "'Ws, 'Wt") \
V(STXR_w, "stxr", "'Ws, 'Wt") \
V(STXR_x, "stxr", "'Ws, 'Xt") \
V(LDXRB_w, "ldxrb", "'Wt") \
V(LDXRH_w, "ldxrh", "'Wt") \
V(LDXR_w, "ldxr", "'Wt") \
V(LDXR_x, "ldxr", "'Xt") \
V(STXP_w, "stxp", "'Ws, 'Wt, 'Wt2") \
V(STXP_x, "stxp", "'Ws, 'Xt, 'Xt2") \
V(LDXP_w, "ldxp", "'Wt, 'Wt2") \
V(LDXP_x, "ldxp", "'Xt, 'Xt2") \
V(STLXRB_w, "stlxrb", "'Ws, 'Wt") \
V(STLXRH_w, "stlxrh", "'Ws, 'Wt") \
V(STLXR_w, "stlxr", "'Ws, 'Wt") \
V(STLXR_x, "stlxr", "'Ws, 'Xt") \
V(LDAXRB_w, "ldaxrb", "'Wt") \
V(LDAXRH_w, "ldaxrh", "'Wt") \
V(LDAXR_w, "ldaxr", "'Wt") \
V(LDAXR_x, "ldaxr", "'Xt") \
V(STLXP_w, "stlxp", "'Ws, 'Wt, 'Wt2") \
V(STLXP_x, "stlxp", "'Ws, 'Xt, 'Xt2") \
V(LDAXP_w, "ldaxp", "'Wt, 'Wt2") \
V(LDAXP_x, "ldaxp", "'Xt, 'Xt2") \
V(STLRB_w, "stlrb", "'Wt") \
V(STLRH_w, "stlrh", "'Wt") \
V(STLR_w, "stlr", "'Wt") \
V(STLR_x, "stlr", "'Xt") \
V(LDARB_w, "ldarb", "'Wt") \
V(LDARH_w, "ldarh", "'Wt") \
V(LDAR_w, "ldar", "'Wt") \
V(LDAR_x, "ldar", "'Xt") \
V(CAS_w, "cas", "'Ws, 'Wt") \
V(CAS_x, "cas", "'Xs, 'Xt") \
V(CASA_w, "casa", "'Ws, 'Wt") \
V(CASA_x, "casa", "'Xs, 'Xt") \
V(CASL_w, "casl", "'Ws, 'Wt") \
V(CASL_x, "casl", "'Xs, 'Xt") \
V(CASAL_w, "casal", "'Ws, 'Wt") \
V(CASAL_x, "casal", "'Xs, 'Xt") \
V(CASB, "casb", "'Ws, 'Wt") \
V(CASAB, "casab", "'Ws, 'Wt") \
V(CASLB, "caslb", "'Ws, 'Wt") \
V(CASALB, "casalb", "'Ws, 'Wt") \
V(CASH, "cash", "'Ws, 'Wt") \
V(CASAH, "casah", "'Ws, 'Wt") \
V(CASLH, "caslh", "'Ws, 'Wt") \
V(CASALH, "casalh", "'Ws, 'Wt") \
V(CASP_w, "casp", "'Ws, 'W(s+1), 'Wt, 'W(t+1)") \
V(CASP_x, "casp", "'Xs, 'X(s+1), 'Xt, 'X(t+1)") \
V(CASPA_w, "caspa", "'Ws, 'W(s+1), 'Wt, 'W(t+1)") \
V(CASPA_x, "caspa", "'Xs, 'X(s+1), 'Xt, 'X(t+1)") \
V(CASPL_w, "caspl", "'Ws, 'W(s+1), 'Wt, 'W(t+1)") \
V(CASPL_x, "caspl", "'Xs, 'X(s+1), 'Xt, 'X(t+1)") \
V(CASPAL_w, "caspal", "'Ws, 'W(s+1), 'Wt, 'W(t+1)") \
V(CASPAL_x, "caspal", "'Xs, 'X(s+1), 'Xt, 'X(t+1)")
// clang-format on
void Disassembler::VisitLoadStoreExclusive(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form;
switch (instr->Mask(LoadStoreExclusiveMask)) {
#define LSX(A, B, C) \
case A: \
mnemonic = B; \
form = C ", ['Xns]"; \
break;
LOAD_STORE_EXCLUSIVE_LIST(LSX)
#undef LSX
default:
form = "(LoadStoreExclusive)";
}
switch (instr->Mask(LoadStoreExclusiveMask)) {
case CASP_w:
case CASP_x:
case CASPA_w:
case CASPA_x:
case CASPL_w:
case CASPL_x:
case CASPAL_w:
case CASPAL_x:
if ((instr->Rs() % 2 == 1) || (instr->Rt() % 2 == 1)) {
mnemonic = "unallocated";
form = "(LoadStoreExclusive)";
}
break;
}
Format(instr, mnemonic, form);
}
#define ATOMIC_MEMORY_SIMPLE_LIST(V) \
V(LDADD, "add") \
V(LDCLR, "clr") \
V(LDEOR, "eor") \
V(LDSET, "set") \
V(LDSMAX, "smax") \
V(LDSMIN, "smin") \
V(LDUMAX, "umax") \
V(LDUMIN, "umin")
void Disassembler::VisitAtomicMemory(const Instruction* instr) {
const int kMaxAtomicOpMnemonicLength = 16;
const char* mnemonic;
const char* form = "'Ws, 'Wt, ['Xns]";
switch (instr->Mask(AtomicMemoryMask)) {
#define AMS(A, MN) \
case A##B: \
mnemonic = MN "b"; \
break; \
case A##AB: \
mnemonic = MN "ab"; \
break; \
case A##LB: \
mnemonic = MN "lb"; \
break; \
case A##ALB: \
mnemonic = MN "alb"; \
break; \
case A##H: \
mnemonic = MN "h"; \
break; \
case A##AH: \
mnemonic = MN "ah"; \
break; \
case A##LH: \
mnemonic = MN "lh"; \
break; \
case A##ALH: \
mnemonic = MN "alh"; \
break; \
case A##_w: \
mnemonic = MN; \
break; \
case A##A_w: \
mnemonic = MN "a"; \
break; \
case A##L_w: \
mnemonic = MN "l"; \
break; \
case A##AL_w: \
mnemonic = MN "al"; \
break; \
case A##_x: \
mnemonic = MN; \
form = "'Xs, 'Xt, ['Xns]"; \
break; \
case A##A_x: \
mnemonic = MN "a"; \
form = "'Xs, 'Xt, ['Xns]"; \
break; \
case A##L_x: \
mnemonic = MN "l"; \
form = "'Xs, 'Xt, ['Xns]"; \
break; \
case A##AL_x: \
mnemonic = MN "al"; \
form = "'Xs, 'Xt, ['Xns]"; \
break;
ATOMIC_MEMORY_SIMPLE_LIST(AMS)
// SWP has the same semantics as ldadd etc but without the store aliases.
AMS(SWP, "swp")
#undef AMS
case LDAPRB:
mnemonic = "ldaprb";
form = "'Wt, ['Xns]";
break;
case LDAPRH:
mnemonic = "ldaprh";
form = "'Wt, ['Xns]";
break;
case LDAPR_w:
mnemonic = "ldapr";
form = "'Wt, ['Xns]";
break;
case LDAPR_x:
mnemonic = "ldapr";
form = "'Xt, ['Xns]";
break;
default:
mnemonic = "unimplemented";
form = "(AtomicMemory)";
}
const char* prefix = "";
switch (instr->Mask(AtomicMemoryMask)) {
#define AMS(A, MN) \
case A##AB: \
case A##ALB: \
case A##AH: \
case A##ALH: \
case A##A_w: \
case A##AL_w: \
case A##A_x: \
case A##AL_x: \
prefix = "ld"; \
break; \
case A##B: \
case A##LB: \
case A##H: \
case A##LH: \
case A##_w: \
case A##L_w: { \
prefix = "ld"; \
unsigned rt = instr->Rt(); \
if (Register(rt, 32).IsZero()) { \
prefix = "st"; \
form = "'Ws, ['Xns]"; \
} \
break; \
} \
case A##_x: \
case A##L_x: { \
prefix = "ld"; \
unsigned rt = instr->Rt(); \
if (Register(rt, 64).IsZero()) { \
prefix = "st"; \
form = "'Xs, ['Xns]"; \
} \
break; \
}
ATOMIC_MEMORY_SIMPLE_LIST(AMS)
#undef AMS
}
char buffer[kMaxAtomicOpMnemonicLength];
if (strlen(prefix) > 0) {
snprintf(buffer, kMaxAtomicOpMnemonicLength, "%s%s", prefix, mnemonic);
mnemonic = buffer;
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitFPCompare(const Instruction* instr) {
const char *mnemonic = "unimplemented";
const char *form = "'Fn, 'Fm";
const char *form_zero = "'Fn, #0.0";
switch (instr->Mask(FPCompareMask)) {
case FCMP_s_zero:
case FCMP_d_zero: form = form_zero; VIXL_FALLTHROUGH();
case FCMP_s:
case FCMP_d: mnemonic = "fcmp"; break;
case FCMPE_s_zero:
case FCMPE_d_zero: form = form_zero; VIXL_FALLTHROUGH();
case FCMPE_s:
case FCMPE_d: mnemonic = "fcmpe"; break;
default: form = "(FPCompare)";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitFPConditionalCompare(const Instruction* instr) {
const char *mnemonic = "unmplemented";
const char *form = "'Fn, 'Fm, 'INzcv, 'Cond";
switch (instr->Mask(FPConditionalCompareMask)) {
case FCCMP_s:
case FCCMP_d: mnemonic = "fccmp"; break;
case FCCMPE_s:
case FCCMPE_d: mnemonic = "fccmpe"; break;
default: form = "(FPConditionalCompare)";
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitFPConditionalSelect(const Instruction* instr) {
const char *mnemonic = "";
const char *form = "'Fd, 'Fn, 'Fm, 'Cond";
switch (instr->Mask(FPConditionalSelectMask)) {
case FCSEL_s:
case FCSEL_d: mnemonic = "fcsel"; break;
default: VIXL_UNREACHABLE();
}
Format(instr, mnemonic, form);
}
void Disassembler::VisitFPDataProcessing1Source(