// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. // ARMv7-A disassembler for ARM state: the integer instructions, VFPv4 and NEON. // It replaces Gareth McCaughan's disarm, which this file used to hold. // Syntax is UAL with upper case mnemonics: "VADDNE.f32 s0, s1, s2", "LDRB r0, [r1, #-4]!". #include #include #include #include #include "Common/CommonTypes.h" #include "ext/disarm.h" namespace { const char *const condNames[16] = { "EQ", "NE", "CS", "CC", "MI", "PL", "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "", "", }; const char *const regNames[16] = { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc", }; const char *const shiftNames[4] = { "lsl", "lsr", "asr", "ror" }; const char *const shiftMnemonics[4] = { "LSL", "LSR", "ASR", "ROR" }; const char *const dataProcNames[16] = { "AND", "EOR", "SUB", "RSB", "ADD", "ADC", "SBC", "RSC", "TST", "TEQ", "CMP", "CMN", "ORR", "MOV", "BIC", "MVN", }; struct Out { Out(char *b, size_t s) : buf(b), size(s) { buf[0] = 0; } void Add(const char *fmt, ...) { if (len + 1 >= size) return; va_list args; va_start(args, fmt); int n = vsnprintf(buf + len, size - len, fmt, args); va_end(args); if (n > 0) len = std::min(size - 1, len + (size_t)n); } char *buf; size_t size; size_t len = 0; }; inline u32 Bits(u32 w, int hi, int lo) { return (w >> lo) & ((1U << (hi - lo + 1)) - 1); } inline bool Bit(u32 w, int b) { return (w >> b) & 1; } inline const char *R(int r) { return regNames[r & 15]; } inline const char *Cond(u32 w) { return condNames[w >> 28]; } inline u32 RotR(u32 v, int amount) { amount &= 31; return amount ? (v >> amount) | (v << (32 - amount)) : v; } inline int SignExtend(u32 v, int bits) { return (int)(v << (32 - bits)) >> (32 - bits); } void AddImm(Out &o, u32 v) { if (v < 256) o.Add("#%u", v); else o.Add("#0x%x", v); } // VFPExpandImm. The same 8-bit format is used by NEON's VMOV.f32. float ExpandFloatImm(int imm8) { float f = (float)((imm8 & 0xF) + 16) / 16.0f; int exp = (((imm8 >> 4) & 7) ^ 4) - 3; while (exp > 0) { f *= 2.0f; exp--; } while (exp < 0) { f /= 2.0f; exp++; } return (imm8 & 0x80) ? -f : f; } void AddFloat(Out &o, float f) { char temp[32]; snprintf(temp, sizeof(temp), "%g", f); o.Add("#%s%s", temp, strpbrk(temp, ".e") ? "" : ".0"); } void AddShiftImm(Out &o, int type, int imm5) { if (type == 0 && imm5 == 0) return; if (type == 3 && imm5 == 0) o.Add(", rrx"); else o.Add(", %s #%d", shiftNames[type], imm5 == 0 ? 32 : imm5); } void AddRegList(Out &o, u32 list) { o.Add("{"); bool first = true; for (int i = 0; i < 16; i++) { if (list & (1 << i)) { o.Add("%s%s", first ? "" : ", ", R(i)); first = false; } } o.Add("}"); } // "[rn, offset]{!}" or "[rn], offset". An empty offset prints as "[rn]". void AddAddress(Out &o, int rn, bool pre, bool writeback, const char *offset) { if (!pre) o.Add("[%s], %s", R(rn), offset); else if (offset[0]) o.Add("[%s, %s]%s", R(rn), offset, writeback ? "!" : ""); else o.Add("[%s]%s", R(rn), writeback ? "!" : ""); } bool DisDataProcessing(u32 w, Out &o) { int opc = Bits(w, 24, 21); bool s = Bit(w, 20); int rn = Bits(w, 19, 16); int rd = Bits(w, 15, 12); bool compare = opc >= 8 && opc <= 11; if (compare && !s) return false; const char *sfx = s && !compare ? "S" : ""; const char *cond = Cond(w); // MOV with a shifted register is printed as the shift. if (opc == 13 && !Bit(w, 25)) { int type = Bits(w, 6, 5); int rm = Bits(w, 3, 0); if (Bit(w, 4)) { if (Bit(w, 7)) return false; o.Add("%s%s%s %s, %s, %s", shiftMnemonics[type], sfx, cond, R(rd), R(rm), R(Bits(w, 11, 8))); return true; } int imm5 = Bits(w, 11, 7); if (type == 3 && imm5 == 0) { o.Add("RRX%s%s %s, %s", sfx, cond, R(rd), R(rm)); return true; } else if (type != 0 || imm5 != 0) { o.Add("%s%s%s %s, %s, #%d", shiftMnemonics[type], sfx, cond, R(rd), R(rm), imm5 ? imm5 : 32); return true; } } o.Add("%s%s%s ", dataProcNames[opc], sfx, cond); if (compare) o.Add("%s, ", R(rn)); else if (opc == 13 || opc == 15) o.Add("%s, ", R(rd)); else o.Add("%s, %s, ", R(rd), R(rn)); if (Bit(w, 25)) { AddImm(o, RotR(w & 0xFF, 2 * Bits(w, 11, 8))); } else { int type = Bits(w, 6, 5); o.Add("%s", R(Bits(w, 3, 0))); if (Bit(w, 4)) { if (Bit(w, 7)) return false; o.Add(", %s %s", shiftNames[type], R(Bits(w, 11, 8))); } else { AddShiftImm(o, type, Bits(w, 11, 7)); } } return true; } bool DisMultiply(u32 w, Out &o) { bool s = Bit(w, 20); int hi = Bits(w, 19, 16); int lo = Bits(w, 15, 12); int rm = Bits(w, 11, 8); int rn = Bits(w, 3, 0); const char *sfx = s ? "S" : ""; const char *cond = Cond(w); switch (Bits(w, 23, 21)) { case 0: o.Add("MUL%s%s %s, %s, %s", sfx, cond, R(hi), R(rn), R(rm)); return true; case 1: o.Add("MLA%s%s %s, %s, %s, %s", sfx, cond, R(hi), R(rn), R(rm), R(lo)); return true; case 2: if (s) return false; o.Add("UMAAL%s %s, %s, %s, %s", cond, R(lo), R(hi), R(rn), R(rm)); return true; case 3: if (s) return false; o.Add("MLS%s %s, %s, %s, %s", cond, R(hi), R(rn), R(rm), R(lo)); return true; default: { static const char *const names[4] = { "UMULL", "UMLAL", "SMULL", "SMLAL" }; o.Add("%s%s%s %s, %s, %s, %s", names[Bits(w, 22, 21)], sfx, cond, R(lo), R(hi), R(rn), R(rm)); return true; } } } bool DisSynchronization(u32 w, Out &o) { int rn = Bits(w, 19, 16); int rd = Bits(w, 15, 12); int rt = Bits(w, 3, 0); const char *cond = Cond(w); int op = Bits(w, 23, 20); static const char *const sizes[4] = { "", "D", "B", "H" }; if (op == 0 || op == 4) { o.Add("SWP%s%s %s, %s, [%s]", op == 4 ? "B" : "", cond, R(rd), R(rt), R(rn)); } else if ((op & 8) == 0) { return false; } else if (op & 1) { o.Add("LDREX%s%s %s, ", sizes[(op >> 1) & 3], cond, R(rd)); if (op == 0xB) o.Add("%s, ", R(rd + 1)); o.Add("[%s]", R(rn)); } else { o.Add("STREX%s%s %s, %s, ", sizes[(op >> 1) & 3], cond, R(rd), R(rt)); if (op == 0xA) o.Add("%s, ", R(rt + 1)); o.Add("[%s]", R(rn)); } return true; } bool DisExtraLoadStore(u32 w, Out &o) { bool pre = Bit(w, 24), up = Bit(w, 23), writeback = Bit(w, 21), load = Bit(w, 20); int rn = Bits(w, 19, 16); int rt = Bits(w, 15, 12); const char *name = nullptr; bool dual = false; switch (Bits(w, 6, 5)) { case 1: name = load ? "LDRH" : "STRH"; break; case 2: name = load ? "LDRSB" : "LDRD"; dual = !load; break; case 3: name = load ? "LDRSH" : "STRD"; dual = !load; break; } bool unprivileged = !pre && writeback; if (unprivileged && dual) return false; char offset[32]; if (Bit(w, 22)) { int imm = (Bits(w, 11, 8) << 4) | Bits(w, 3, 0); if (pre && up && imm == 0) offset[0] = 0; else snprintf(offset, sizeof(offset), "#%s%d", up ? "" : "-", imm); } else { snprintf(offset, sizeof(offset), "%s%s", up ? "" : "-", R(Bits(w, 3, 0))); } o.Add("%s%s%s %s, ", name, unprivileged ? "T" : "", Cond(w), R(rt)); if (dual) o.Add("%s, ", R(rt + 1)); AddAddress(o, rn, pre, writeback, offset); return true; } bool DisLoadStore(u32 w, Out &o) { bool pre = Bit(w, 24), up = Bit(w, 23), byte = Bit(w, 22), writeback = Bit(w, 21), load = Bit(w, 20); int rn = Bits(w, 19, 16); int rt = Bits(w, 15, 12); bool unprivileged = !pre && writeback; char offset[48]; if (!Bit(w, 25)) { int imm = w & 0xFFF; if (pre && up && imm == 0) offset[0] = 0; else snprintf(offset, sizeof(offset), "#%s%d", up ? "" : "-", imm); } else { int type = Bits(w, 6, 5); int imm5 = Bits(w, 11, 7); int len = snprintf(offset, sizeof(offset), "%s%s", up ? "" : "-", R(Bits(w, 3, 0))); if (type == 3 && imm5 == 0) snprintf(offset + len, sizeof(offset) - len, ", rrx"); else if (type != 0 || imm5 != 0) snprintf(offset + len, sizeof(offset) - len, ", %s #%d", shiftNames[type], imm5 ? imm5 : 32); } o.Add("%s%s%s%s %s, ", load ? "LDR" : "STR", byte ? "B" : "", unprivileged ? "T" : "", Cond(w), R(rt)); AddAddress(o, rn, pre, writeback, offset); return true; } // MRS, MSR (register), BX, BLX, CLZ, BKPT, QADD and friends. bool DisMisc(u32 w, Out &o) { int op = Bits(w, 22, 21); const char *cond = Cond(w); switch (Bits(w, 6, 4)) { case 0: if (Bit(w, 9)) return false; if (!(op & 1)) { o.Add("MRS%s %s, %s", cond, R(Bits(w, 15, 12)), Bit(w, 22) ? "spsr" : "apsr"); } else { int mask = Bits(w, 19, 16); if (op == 1 && (mask & 3) == 0) { if (mask == 0) return false; o.Add("MSR%s APSR_%s%s, %s", cond, (mask & 8) ? "nzcvq" : "", (mask & 4) ? "g" : "", R(Bits(w, 3, 0))); } else { o.Add("MSR%s %s_%s%s%s%s, %s", cond, Bit(w, 22) ? "SPSR" : "CPSR", (mask & 8) ? "f" : "", (mask & 4) ? "s" : "", (mask & 2) ? "x" : "", (mask & 1) ? "c" : "", R(Bits(w, 3, 0))); } } return true; case 1: if (op == 1) o.Add("BX%s %s", cond, R(Bits(w, 3, 0))); else if (op == 3) o.Add("CLZ%s %s, %s", cond, R(Bits(w, 15, 12)), R(Bits(w, 3, 0))); else return false; return true; case 2: if (op != 1) return false; o.Add("BXJ%s %s", cond, R(Bits(w, 3, 0))); return true; case 3: if (op != 1) return false; o.Add("BLX%s %s", cond, R(Bits(w, 3, 0))); return true; case 5: { static const char *const names[4] = { "QADD", "QSUB", "QDADD", "QDSUB" }; o.Add("%s%s %s, %s, %s", names[op], cond, R(Bits(w, 15, 12)), R(Bits(w, 3, 0)), R(Bits(w, 19, 16))); return true; } case 7: if (op == 1) o.Add("BKPT #%d", (Bits(w, 19, 8) << 4) | Bits(w, 3, 0)); else if (op == 3) o.Add("SMC%s #%d", cond, Bits(w, 3, 0)); else return false; return true; default: return false; } } // SMLA and the other 16-bit multiplies. bool DisHalfwordMultiply(u32 w, Out &o) { int rd = Bits(w, 19, 16); int ra = Bits(w, 15, 12); int rm = Bits(w, 11, 8); int rn = Bits(w, 3, 0); char x = Bit(w, 5) ? 'T' : 'B'; char y = Bit(w, 6) ? 'T' : 'B'; const char *cond = Cond(w); switch (Bits(w, 22, 21)) { case 0: o.Add("SMLA%c%c%s %s, %s, %s, %s", x, y, cond, R(rd), R(rn), R(rm), R(ra)); break; case 1: if (Bit(w, 5)) o.Add("SMULW%c%s %s, %s, %s", y, cond, R(rd), R(rn), R(rm)); else o.Add("SMLAW%c%s %s, %s, %s, %s", y, cond, R(rd), R(rn), R(rm), R(ra)); break; case 2: o.Add("SMLAL%c%c%s %s, %s, %s, %s", x, y, cond, R(ra), R(rd), R(rn), R(rm)); break; case 3: o.Add("SMUL%c%c%s %s, %s, %s", x, y, cond, R(rd), R(rn), R(rm)); break; } return true; } // MOVW, MOVT, MSR (immediate) and the hints. bool DisMovImmAndHints(u32 w, Out &o) { const char *cond = Cond(w); int rd = Bits(w, 15, 12); switch (Bits(w, 22, 21)) { case 0: o.Add("MOVW%s %s, #0x%x", cond, R(rd), (Bits(w, 19, 16) << 12) | Bits(w, 11, 0)); return true; case 2: o.Add("MOVT%s %s, #0x%x", cond, R(rd), (Bits(w, 19, 16) << 12) | Bits(w, 11, 0)); return true; default: break; } int mask = Bits(w, 19, 16); if (!Bit(w, 22) && mask == 0) { static const char *const hints[5] = { "NOP", "YIELD", "WFE", "WFI", "SEV" }; int hint = Bits(w, 7, 0); if (hint < 5) o.Add("%s%s", hints[hint], cond); else if (hint >= 0xF0) o.Add("DBG%s #%d", cond, hint & 15); else o.Add("HINT%s #%d", cond, hint); return true; } u32 imm = RotR(w & 0xFF, 2 * Bits(w, 11, 8)); if (!Bit(w, 22) && (mask & 3) == 0) { o.Add("MSR%s APSR_%s%s, ", cond, (mask & 8) ? "nzcvq" : "", (mask & 4) ? "g" : ""); } else { o.Add("MSR%s %s_%s%s%s%s, ", cond, Bit(w, 22) ? "SPSR" : "CPSR", (mask & 8) ? "f" : "", (mask & 4) ? "s" : "", (mask & 2) ? "x" : "", (mask & 1) ? "c" : ""); } AddImm(o, imm); return true; } bool DisMedia(u32 w, Out &o) { int op1 = Bits(w, 24, 20); int op2 = Bits(w, 7, 5); int rn = Bits(w, 19, 16); int rd = Bits(w, 15, 12); int rs = Bits(w, 11, 8); int rm = Bits(w, 3, 0); const char *cond = Cond(w); if ((op1 & 0x18) == 0) { // Parallel add and subtract. static const char *const prefixes[2][4] = { { nullptr, "S", "Q", "SH" }, { nullptr, "U", "UQ", "UH" } }; static const char *const ops[8] = { "ADD16", "ASX", "SAX", "SUB16", "ADD8", nullptr, nullptr, "SUB8" }; const char *prefix = prefixes[Bit(w, 22)][op1 & 3]; if (!prefix || !ops[op2] || Bit(w, 23)) return false; o.Add("%s%s%s %s, %s, %s", prefix, ops[op2], cond, R(rd), R(rn), R(rm)); return true; } if ((op1 & 0x18) == 0x08) { // Packing, unpacking, saturation and reversal. int sub = op1 & 7; int imm5 = Bits(w, 11, 7); if (sub == 0 && (op2 & 1) == 0) { bool tb = Bit(w, 6); o.Add("PKH%s%s %s, %s, %s", tb ? "TB" : "BT", cond, R(rd), R(rn), R(rm)); if (tb || imm5) o.Add(", %s #%d", tb ? "asr" : "lsl", (tb && imm5 == 0) ? 32 : imm5); return true; } if ((sub & 2) && (op2 & 1) == 0) { bool isUnsigned = (sub & 4) != 0; int sat = Bits(w, 20, 16) + (isUnsigned ? 0 : 1); o.Add("%s%s %s, #%d, %s", isUnsigned ? "USAT" : "SSAT", cond, R(rd), sat, R(rm)); if (Bit(w, 6)) o.Add(", asr #%d", imm5 ? imm5 : 32); else if (imm5) o.Add(", lsl #%d", imm5); return true; } if (op2 == 3) { static const char *const names[8] = { "SXTAB16", nullptr, "SXTAB", "SXTAH", "UXTAB16", nullptr, "UXTAB", "UXTAH" }; const char *name = names[sub]; if (!name) return false; if (rn == 15) o.Add("%.3s%s%s %s, %s", name, name + 4, cond, R(rd), R(rm)); else o.Add("%s%s %s, %s, %s", name, cond, R(rd), R(rn), R(rm)); int rot = Bits(w, 11, 10); if (rot) o.Add(", ror #%d", rot * 8); return true; } if (sub == 0 && op2 == 5) { o.Add("SEL%s %s, %s, %s", cond, R(rd), R(rn), R(rm)); return true; } if ((sub == 2 || sub == 6) && op2 == 1) { bool isUnsigned = sub == 6; o.Add("%s%s %s, #%d, %s", isUnsigned ? "USAT16" : "SSAT16", cond, R(rd), Bits(w, 19, 16) + (isUnsigned ? 0 : 1), R(rm)); return true; } const char *name = nullptr; if (sub == 3 && op2 == 1) name = "REV"; else if (sub == 3 && op2 == 5) name = "REV16"; else if (sub == 7 && op2 == 1) name = "RBIT"; else if (sub == 7 && op2 == 5) name = "REVSH"; if (!name) return false; o.Add("%s%s %s, %s", name, cond, R(rd), R(rm)); return true; } if ((op1 & 0x18) == 0x10) { // Signed multiplies and divides. Rd is in 19:16 here, Ra in 15:12. int sub = op1 & 7; const char *x = (op2 & 1) ? "X" : ""; if (sub == 0 && op2 < 4) { const char *name = op2 < 2 ? (rd == 15 ? "SMUAD" : "SMLAD") : (rd == 15 ? "SMUSD" : "SMLSD"); o.Add("%s%s%s %s, %s, %s", name, x, cond, R(rn), R(rm), R(rs)); if (rd != 15) o.Add(", %s", R(rd)); return true; } if ((sub == 1 || sub == 3) && op2 == 0) { o.Add("%s%s %s, %s, %s", sub == 1 ? "SDIV" : "UDIV", cond, R(rn), R(rm), R(rs)); return true; } if (sub == 4 && op2 < 4) { o.Add("%s%s%s %s, %s, %s, %s", op2 < 2 ? "SMLALD" : "SMLSLD", x, cond, R(rd), R(rn), R(rm), R(rs)); return true; } if (sub == 5 && (op2 < 2 || op2 >= 6)) { const char *name = op2 >= 6 ? "SMMLS" : (rd == 15 ? "SMMUL" : "SMMLA"); o.Add("%s%s%s %s, %s, %s", name, x, cond, R(rn), R(rm), R(rs)); if (rd != 15 || op2 >= 6) o.Add(", %s", R(rd)); return true; } return false; } if (op1 == 0x18 && op2 == 0) { if (rd == 15) o.Add("USAD8%s %s, %s, %s", cond, R(rn), R(rm), R(rs)); else o.Add("USADA8%s %s, %s, %s, %s", cond, R(rn), R(rm), R(rs), R(rd)); return true; } int lsb = Bits(w, 11, 7); int msb = Bits(w, 20, 16); if ((op1 & 0x1E) == 0x1A && (op2 & 3) == 2) { o.Add("SBFX%s %s, %s, #%d, #%d", cond, R(rd), R(rm), lsb, msb + 1); return true; } if ((op1 & 0x1E) == 0x1E && (op2 & 3) == 2) { o.Add("UBFX%s %s, %s, #%d, #%d", cond, R(rd), R(rm), lsb, msb + 1); return true; } if ((op1 & 0x1E) == 0x1C && (op2 & 3) == 0) { if (msb < lsb) return false; if (rm == 15) o.Add("BFC%s %s, #%d, #%d", cond, R(rd), lsb, msb - lsb + 1); else o.Add("BFI%s %s, %s, #%d, #%d", cond, R(rd), R(rm), lsb, msb - lsb + 1); return true; } if (op1 == 0x1F && op2 == 7) { o.Add("UDF #%d", (Bits(w, 19, 8) << 4) | Bits(w, 3, 0)); return true; } return false; } bool DisBlockTransfer(u32 w, Out &o) { bool pre = Bit(w, 24), up = Bit(w, 23), user = Bit(w, 22), writeback = Bit(w, 21), load = Bit(w, 20); int rn = Bits(w, 19, 16); u32 list = w & 0xFFFF; int count = 0; for (int i = 0; i < 16; i++) count += (list >> i) & 1; bool stack = load ? (!pre && up) : (pre && !up); if (rn == 13 && writeback && !user && stack && count >= 2) { o.Add("%s%s ", load ? "POP" : "PUSH", Cond(w)); } else { static const char *const modes[2][2] = { { "DA", "IA" }, { "DB", "IB" } }; o.Add("%s%s%s %s%s, ", load ? "LDM" : "STM", modes[pre][up], Cond(w), R(rn), writeback ? "!" : ""); } AddRegList(o, list); if (user) o.Add("^"); return true; } // VFP int SReg(int hi4, int lo1) { return (hi4 << 1) | lo1; } int DReg(int hi1, int lo4) { return (hi1 << 4) | lo4; } // A VFP register from a 4-bit field and the extra bit, as S or D depending on dbl. int VfpReg(bool dbl, int field, int extra) { return dbl ? DReg(extra, field) : SReg(field, extra); } bool DisVfpLoadStore(u32 w, Out &o) { bool pre = Bit(w, 24), up = Bit(w, 23), writeback = Bit(w, 21), load = Bit(w, 20); bool dbl = Bit(w, 8); int rn = Bits(w, 19, 16); int imm8 = Bits(w, 7, 0); char t = dbl ? 'd' : 's'; int vd = VfpReg(dbl, Bits(w, 15, 12), Bit(w, 22)); const char *cond = Cond(w); if (pre && !writeback) { o.Add("%s%s %c%d, ", load ? "VLDR" : "VSTR", cond, t, vd); int offset = imm8 * 4; if (up && offset == 0) o.Add("[%s]", R(rn)); else o.Add("[%s, #%s%d]", R(rn), up ? "" : "-", offset); return true; } if (pre == up || (dbl && (imm8 & 1)) || imm8 == 0) return false; int count = dbl ? imm8 / 2 : imm8; bool stack = load ? (!pre && up) : (pre && !up); if (rn == 13 && writeback && stack) { o.Add("%s%s ", load ? "VPOP" : "VPUSH", cond); } else { o.Add("%s%s%s %s%s, ", load ? "VLDM" : "VSTM", pre ? "DB" : "IA", cond, R(rn), writeback ? "!" : ""); } if (count == 1) o.Add("{%c%d}", t, vd); else o.Add("{%c%d-%c%d}", t, vd, t, vd + count - 1); return true; } bool DisVfp64Transfer(u32 w, Out &o) { if (Bits(w, 7, 6) != 0 || !Bit(w, 4)) return false; bool toCore = Bit(w, 20); int rt = Bits(w, 15, 12); int rt2 = Bits(w, 19, 16); const char *cond = Cond(w); if (Bit(w, 8)) { int dm = DReg(Bit(w, 5), Bits(w, 3, 0)); if (toCore) o.Add("VMOV%s %s, %s, d%d", cond, R(rt), R(rt2), dm); else o.Add("VMOV%s d%d, %s, %s", cond, dm, R(rt), R(rt2)); } else { int sm = SReg(Bits(w, 3, 0), Bit(w, 5)); if (toCore) o.Add("VMOV%s %s, %s, s%d, s%d", cond, R(rt), R(rt2), sm, sm + 1); else o.Add("VMOV%s s%d, s%d, %s, %s", cond, sm, sm + 1, R(rt), R(rt2)); } return true; } // Size and index of a scalar from VMOV's opc1:opc2 field. bool DecodeScalarIndex(int opc1, int opc2, int *size, int *index) { if (opc1 & 2) { *size = 8; *index = ((opc1 & 1) << 2) | opc2; } else if (opc2 & 1) { *size = 16; *index = ((opc1 & 1) << 1) | (opc2 >> 1); } else if (opc2 == 0) { *size = 32; *index = opc1 & 1; } else { return false; } return true; } const char *VfpSysRegName(int reg) { switch (reg) { case 0: return "fpsid"; case 1: return "fpscr"; case 6: return "mvfr1"; case 7: return "mvfr0"; case 8: return "fpexc"; default: return nullptr; } } // Moves between a core register and an S register, a scalar or a system register, and VDUP. bool DisVfpTransfer(u32 w, Out &o) { bool load = Bit(w, 20); int a = Bits(w, 23, 21); int rt = Bits(w, 15, 12); const char *cond = Cond(w); if (Bits(w, 3, 0) != 0) return false; if (!Bit(w, 8)) { if (a == 0) { int sn = SReg(Bits(w, 19, 16), Bit(w, 7)); if (load) o.Add("VMOV%s %s, s%d", cond, R(rt), sn); else o.Add("VMOV%s s%d, %s", cond, sn, R(rt)); return true; } else if (a == 7) { const char *reg = VfpSysRegName(Bits(w, 19, 16)); if (!reg) return false; if (!load) o.Add("VMSR%s %s, %s", cond, reg, R(rt)); else if (rt == 15) o.Add("VMRS%s APSR_nzcv, %s", cond, reg); else o.Add("VMRS%s %s, %s", cond, R(rt), reg); return true; } return false; } int dn = DReg(Bit(w, 7), Bits(w, 19, 16)); if (!load && (a & 4)) { if (Bit(w, 6)) return false; int be = (Bit(w, 22) << 1) | Bit(w, 5); if (be == 3) return false; static const int sizes[3] = { 32, 16, 8 }; bool q = Bit(w, 21); if (q) o.Add("VDUP%s.%d q%d, %s", cond, sizes[be], dn >> 1, R(rt)); else o.Add("VDUP%s.%d d%d, %s", cond, sizes[be], dn, R(rt)); return true; } int size, index; if (!DecodeScalarIndex(Bits(w, 22, 21), Bits(w, 6, 5), &size, &index)) return false; if (!load) { o.Add("VMOV%s.%d d%d[%d], %s", cond, size, dn, index, R(rt)); } else { bool isUnsigned = Bit(w, 23); if (size == 32 && isUnsigned) return false; if (size == 32) o.Add("VMOV%s.32 %s, d%d[%d]", cond, R(rt), dn, index); else o.Add("VMOV%s.%c%d %s, d%d[%d]", cond, isUnsigned ? 'u' : 's', size, R(rt), dn, index); } return true; } bool DisVfpData(u32 w, Out &o) { bool dbl = Bit(w, 8); char t = dbl ? 'd' : 's'; const char *dt = dbl ? ".f64" : ".f32"; const char *cond = Cond(w); int vd = VfpReg(dbl, Bits(w, 15, 12), Bit(w, 22)); int vn = VfpReg(dbl, Bits(w, 19, 16), Bit(w, 7)); int vm = VfpReg(dbl, Bits(w, 3, 0), Bit(w, 5)); bool op6 = Bit(w, 6); int opc1 = (Bit(w, 23) << 2) | Bits(w, 21, 20); if (opc1 != 7) { static const char *const names[7][2] = { { "VMLA", "VMLS" }, { "VNMLS", "VNMLA" }, { "VMUL", "VNMUL" }, { "VADD", "VSUB" }, { "VDIV", nullptr }, { "VFNMS", "VFNMA" }, { "VFMA", "VFMS" }, }; const char *name = names[opc1][op6]; if (!name) return false; o.Add("%s%s%s %c%d, %c%d, %c%d", name, cond, dt, t, vd, t, vn, t, vm); return true; } if (!op6) { if (Bits(w, 7, 4) != 0) return false; o.Add("VMOV%s%s %c%d, ", cond, dt, t, vd); AddFloat(o, ExpandFloatImm((Bits(w, 19, 16) << 4) | Bits(w, 3, 0))); return true; } bool op7 = Bit(w, 7); int sd = SReg(Bits(w, 15, 12), Bit(w, 22)); int sm = SReg(Bits(w, 3, 0), Bit(w, 5)); int dd = DReg(Bit(w, 22), Bits(w, 15, 12)); int dm = DReg(Bit(w, 5), Bits(w, 3, 0)); int opc2 = Bits(w, 19, 16); switch (opc2) { case 0: o.Add("%s%s%s %c%d, %c%d", op7 ? "VABS" : "VMOV", cond, dt, t, vd, t, vm); return true; case 1: o.Add("%s%s%s %c%d, %c%d", op7 ? "VSQRT" : "VNEG", cond, dt, t, vd, t, vm); return true; case 2: case 3: if (dbl) return false; o.Add("VCVT%c%s%s s%d, s%d", op7 ? 'T' : 'B', cond, (opc2 & 1) ? ".f16.f32" : ".f32.f16", sd, sm); return true; case 4: o.Add("%s%s%s %c%d, %c%d", op7 ? "VCMPE" : "VCMP", cond, dt, t, vd, t, vm); return true; case 5: if (Bits(w, 5, 0) != 0) return false; o.Add("%s%s%s %c%d, #0.0", op7 ? "VCMPE" : "VCMP", cond, dt, t, vd); return true; case 7: if (!op7) return false; if (dbl) o.Add("VCVT%s.f32.f64 s%d, d%d", cond, sd, dm); else o.Add("VCVT%s.f64.f32 d%d, s%d", cond, dd, sm); return true; case 8: o.Add("VCVT%s%s.%c32 %c%d, s%d", cond, dt, op7 ? 's' : 'u', t, vd, sm); return true; case 12: case 13: o.Add("VCVT%s%s.%c32%s s%d, %c%d", op7 ? "" : "R", cond, (opc2 & 1) ? 's' : 'u', dt, sd, t, vm); return true; case 10: case 11: case 14: case 15: { // Fixed point, in place. int size = op7 ? 32 : 16; int imm = SReg(Bits(w, 3, 0), Bit(w, 5)); char fx = Bit(w, 16) ? 'u' : 's'; if (Bit(w, 18)) o.Add("VCVT%s.%c%d%s %c%d, %c%d, #%d", cond, fx, size, dt, t, vd, t, vd, size - imm); else o.Add("VCVT%s%s.%c%d %c%d, %c%d, #%d", cond, dt, fx, size, t, vd, t, vd, size - imm); return true; } default: return false; } } bool DisCoprocessor(u32 w, Out &o) { if (Bits(w, 27, 24) == 0xF) { o.Add("SVC%s #0x%x", Cond(w), Bits(w, 23, 0)); return true; } if ((Bits(w, 11, 8) & 0xE) != 0xA) return false; if (Bits(w, 27, 25) == 6) { if (Bits(w, 24, 21) == 2) return DisVfp64Transfer(w, o); if (Bits(w, 24, 21) == 0) return false; return DisVfpLoadStore(w, o); } return Bit(w, 4) ? DisVfpTransfer(w, o) : DisVfpData(w, o); } // NEON int NeonD(u32 w) { return DReg(Bit(w, 22), Bits(w, 15, 12)); } int NeonN(u32 w) { return DReg(Bit(w, 7), Bits(w, 19, 16)); } int NeonM(u32 w) { return DReg(Bit(w, 5), Bits(w, 3, 0)); } // Register text for a D register number, printed as Q if q is set. struct V { V(bool q, int d) { snprintf(s, sizeof(s), q ? "q%d" : "d%d", q ? d >> 1 : d); } char s[8]; }; enum class Dt { NONE, SU, // .s or .u depending on U I, S, U, F, P, PLAIN, // just the size: .8 }; void AddDt(Out &o, Dt dt, bool u, int bits) { switch (dt) { case Dt::NONE: break; case Dt::SU: o.Add(".%c%d", u ? 'u' : 's', bits); break; case Dt::I: o.Add(".i%d", bits); break; case Dt::S: o.Add(".s%d", bits); break; case Dt::U: o.Add(".u%d", bits); break; case Dt::F: o.Add(".f32"); break; case Dt::P: o.Add(".p%d", bits); break; case Dt::PLAIN: o.Add(".%d", bits); break; } } bool DisNeon3Same(u32 w, Out &o) { int a = Bits(w, 11, 8); bool b = Bit(w, 4); bool u = Bit(w, 24); int c = Bits(w, 21, 20); bool q = Bit(w, 6); const char *name = nullptr; Dt dt = Dt::SU; bool shiftOrder = false; bool pairwise = false; bool fsz = (c & 1) == 0; // the float ops only exist with sz == 0 bool cHi = (c & 2) != 0; switch (a) { case 0: name = b ? "VQADD" : "VHADD"; break; case 1: if (!b) { name = "VRHADD"; } else { static const char *const logic[2][4] = { { "VAND", "VBIC", "VORR", "VORN" }, { "VEOR", "VBSL", "VBIT", "VBIF" } }; name = logic[u][c]; dt = Dt::NONE; if (!u && c == 2 && NeonN(w) == NeonM(w)) { o.Add("VMOV %s, %s", V(q, NeonD(w)).s, V(q, NeonM(w)).s); return true; } } break; case 2: name = b ? "VQSUB" : "VHSUB"; break; case 3: name = b ? "VCGE" : "VCGT"; break; case 4: name = b ? "VQSHL" : "VSHL"; shiftOrder = true; break; case 5: name = b ? "VQRSHL" : "VRSHL"; shiftOrder = true; break; case 6: name = b ? "VMIN" : "VMAX"; break; case 7: name = b ? "VABA" : "VABD"; break; case 8: name = b ? (u ? "VCEQ" : "VTST") : (u ? "VSUB" : "VADD"); dt = (b && !u) ? Dt::PLAIN : Dt::I; break; case 9: if (!b) { name = u ? "VMLS" : "VMLA"; dt = Dt::I; } else { name = "VMUL"; dt = u ? Dt::P : Dt::I; } break; case 10: name = b ? "VPMIN" : "VPMAX"; pairwise = true; break; case 11: if (!b) { name = u ? "VQRDMULH" : "VQDMULH"; dt = Dt::S; } else if (!u) { name = "VPADD"; dt = Dt::I; pairwise = true; } break; case 12: if (b && !u && fsz) name = cHi ? "VFMS" : "VFMA"; dt = Dt::F; break; case 13: if (!b) { name = u ? (cHi ? "VABD" : "VPADD") : (cHi ? "VSUB" : "VADD"); pairwise = u && !cHi; } else if (!u) { name = cHi ? "VMLS" : "VMLA"; } else if (!cHi) { name = "VMUL"; } if (!fsz) name = nullptr; dt = Dt::F; break; case 14: if (!b) { name = u ? (cHi ? "VCGT" : "VCGE") : (cHi ? nullptr : "VCEQ"); } else if (u) { name = cHi ? "VACGT" : "VACGE"; } if (!fsz) name = nullptr; dt = Dt::F; break; case 15: if (!b) { name = cHi ? (u ? "VPMIN" : "VMIN") : (u ? "VPMAX" : "VMAX"); pairwise = u; } else if (!u) { name = cHi ? "VRSQRTS" : "VRECPS"; } if (!fsz) name = nullptr; dt = Dt::F; break; } if (!name || (pairwise && q)) return false; if (c == 3 && dt != Dt::NONE && dt != Dt::F && a != 8 && a != 0 && a != 2 && a != 4 && a != 5) return false; o.Add("%s", name); AddDt(o, dt, u, 8 << c); if (shiftOrder) o.Add(" %s, %s, %s", V(q, NeonD(w)).s, V(q, NeonM(w)).s, V(q, NeonN(w)).s); else o.Add(" %s, %s, %s", V(q, NeonD(w)).s, V(q, NeonN(w)).s, V(q, NeonM(w)).s); return true; } bool DisNeon3Diff(u32 w, Out &o) { int a = Bits(w, 11, 8); bool u = Bit(w, 24); int size = Bits(w, 21, 20); enum Shape { LONG, WIDE, NARROW } shape = LONG; const char *name = nullptr; Dt dt = Dt::SU; int bits = 8 << size; switch (a) { case 0: name = "VADDL"; break; case 1: name = "VADDW"; shape = WIDE; break; case 2: name = "VSUBL"; break; case 3: name = "VSUBW"; shape = WIDE; break; case 4: name = u ? "VRADDHN" : "VADDHN"; shape = NARROW; dt = Dt::I; bits *= 2; break; case 5: name = "VABAL"; break; case 6: name = u ? "VRSUBHN" : "VSUBHN"; shape = NARROW; dt = Dt::I; bits *= 2; break; case 7: name = "VABDL"; break; case 8: name = "VMLAL"; break; case 9: name = u ? nullptr : "VQDMLAL"; dt = Dt::S; break; case 10: name = "VMLSL"; break; case 11: name = u ? nullptr : "VQDMLSL"; dt = Dt::S; break; case 12: name = "VMULL"; break; case 13: name = u ? nullptr : "VQDMULL"; dt = Dt::S; break; case 14: name = u ? nullptr : "VMULL"; dt = Dt::P; break; } if (!name) return false; o.Add("%s", name); AddDt(o, dt, u, bits); int d = NeonD(w), n = NeonN(w), m = NeonM(w); switch (shape) { case LONG: o.Add(" q%d, d%d, d%d", d >> 1, n, m); break; case WIDE: o.Add(" q%d, q%d, d%d", d >> 1, n >> 1, m); break; case NARROW: o.Add(" d%d, q%d, q%d", d, n >> 1, m >> 1); break; } return true; } bool DisNeonScalar(u32 w, Out &o) { int a = Bits(w, 11, 8); bool u = Bit(w, 24); int size = Bits(w, 21, 20); if (size == 0) return false; int bits = 8 << size; const char *name = nullptr; Dt dt = Dt::I; bool longOp = false; switch (a) { case 0: name = "VMLA"; break; case 1: name = "VMLA"; dt = Dt::F; break; case 2: name = "VMLAL"; dt = Dt::SU; longOp = true; break; case 3: name = u ? nullptr : "VQDMLAL"; dt = Dt::S; longOp = true; break; case 4: name = "VMLS"; break; case 5: name = "VMLS"; dt = Dt::F; break; case 6: name = "VMLSL"; dt = Dt::SU; longOp = true; break; case 7: name = u ? nullptr : "VQDMLSL"; dt = Dt::S; longOp = true; break; case 8: name = "VMUL"; break; case 9: name = "VMUL"; dt = Dt::F; break; case 10: name = "VMULL"; dt = Dt::SU; longOp = true; break; case 11: name = u ? nullptr : "VQDMULL"; dt = Dt::S; longOp = true; break; case 12: name = "VQDMULH"; dt = Dt::S; break; case 13: name = "VQRDMULH"; dt = Dt::S; break; } if (!name || (dt == Dt::F && size != 2)) return false; int dm, index; if (size == 1) { dm = Bits(w, 2, 0); index = (Bit(w, 5) << 1) | Bit(w, 3); } else { dm = Bits(w, 3, 0); index = Bit(w, 5); } o.Add("%s", name); AddDt(o, dt, u, bits); int d = NeonD(w), n = NeonN(w); if (longOp) o.Add(" q%d, d%d, d%d[%d]", d >> 1, n, dm, index); else o.Add(" %s, %s, d%d[%d]", V(u, d).s, V(u, n).s, dm, index); return true; } bool DisNeonShift(u32 w, Out &o) { int a = Bits(w, 11, 8); bool u = Bit(w, 24); bool l = Bit(w, 7); bool q = Bit(w, 6); int imm6 = Bits(w, 21, 16); int esize; if (l) esize = 64; else if (imm6 & 0x20) esize = 32; else if (imm6 & 0x10) esize = 16; else esize = 8; int rightShift = (l ? 64 : 2 * esize) - imm6; int leftShift = l ? imm6 : imm6 - esize; int d = NeonD(w), m = NeonM(w); const char *name = nullptr; Dt dt = Dt::SU; int shift = rightShift; switch (a) { case 0: name = "VSHR"; break; case 1: name = "VSRA"; break; case 2: name = "VRSHR"; break; case 3: name = "VRSRA"; break; case 4: name = u ? "VSRI" : nullptr; dt = Dt::PLAIN; break; case 5: name = u ? "VSLI" : "VSHL"; dt = u ? Dt::PLAIN : Dt::I; shift = leftShift; break; case 6: name = u ? "VQSHLU" : nullptr; dt = Dt::S; shift = leftShift; break; case 7: name = "VQSHL"; shift = leftShift; break; case 8: case 9: { if (l) return false; bool round = q; if (a == 8 && !u) { name = round ? "VRSHRN" : "VSHRN"; dt = Dt::I; } else if (a == 8) { name = round ? "VQRSHRUN" : "VQSHRUN"; dt = Dt::S; } else { name = round ? "VQRSHRN" : "VQSHRN"; } o.Add("%s", name); AddDt(o, dt, u, esize * 2); o.Add(" d%d, q%d, #%d", d, m >> 1, rightShift); return true; } case 10: if (l || q) return false; if (leftShift == 0) { o.Add("VMOVL.%c%d q%d, d%d", u ? 'u' : 's', esize, d >> 1, m); } else { o.Add("VSHLL.%c%d q%d, d%d, #%d", u ? 'u' : 's', esize, d >> 1, m, leftShift); } return true; case 14: case 15: { if (l || !(imm6 & 0x20)) return false; char sign = u ? 'u' : 's'; if (Bit(w, 8)) o.Add("VCVT.%c32.f32", sign); else o.Add("VCVT.f32.%c32", sign); o.Add(" %s, %s, #%d", V(q, d).s, V(q, m).s, 64 - imm6); return true; } } if (!name) return false; o.Add("%s", name); AddDt(o, dt, u, esize); o.Add(" %s, %s, #%d", V(q, d).s, V(q, m).s, shift); return true; } bool DisNeonModImm(u32 w, Out &o) { bool op = Bit(w, 5); bool q = Bit(w, 6); int cmode = Bits(w, 11, 8); u32 imm8 = (Bit(w, 24) << 7) | (Bits(w, 18, 16) << 4) | Bits(w, 3, 0); const char *name; char dt[8]; unsigned long long value = imm8; bool isFloat = false; if (cmode < 8) { // 32-bit, shifted by 0/8/16/24. bool orr = cmode & 1; name = op ? (orr ? "VBIC" : "VMVN") : (orr ? "VORR" : "VMOV"); strcpy(dt, ".i32"); value <<= 8 * (cmode >> 1); } else if (cmode < 12) { bool orr = cmode & 1; name = op ? (orr ? "VBIC" : "VMVN") : (orr ? "VORR" : "VMOV"); strcpy(dt, ".i16"); value <<= 8 * ((cmode >> 1) & 1); } else if (cmode < 14) { name = op ? "VMVN" : "VMOV"; strcpy(dt, ".i32"); value = (cmode == 12) ? ((value << 8) | 0xFF) : ((value << 16) | 0xFFFF); } else if (cmode == 14) { name = "VMOV"; if (op) { strcpy(dt, ".i64"); value = 0; for (int i = 0; i < 8; i++) { if (imm8 & (1 << i)) value |= 0xFFULL << (i * 8); } } else { strcpy(dt, ".i8"); } } else { if (op) return false; name = "VMOV"; strcpy(dt, ".f32"); isFloat = true; } o.Add("%s%s %s, ", name, dt, V(q, NeonD(w)).s); if (isFloat) AddFloat(o, ExpandFloatImm(imm8)); else o.Add("#0x%llx", value); return true; } bool DisNeon2Misc(u32 w, Out &o) { int size = Bits(w, 19, 18); int a = Bits(w, 17, 16); int b = Bits(w, 10, 6); bool q = Bit(w, 6); int bits = 8 << size; int d = NeonD(w), m = NeonM(w); const char *name = nullptr; Dt dt = Dt::NONE; switch (a) { case 0: switch (b >> 1) { case 0: name = "VREV64"; dt = Dt::PLAIN; break; case 1: name = "VREV32"; dt = Dt::PLAIN; break; case 2: name = "VREV16"; dt = Dt::PLAIN; break; case 4: case 5: name = "VPADDL"; dt = Bit(w, 7) ? Dt::U : Dt::S; break; case 8: name = "VCLS"; dt = Dt::S; break; case 9: name = "VCLZ"; dt = Dt::I; break; case 10: name = "VCNT"; dt = Dt::PLAIN; break; case 11: name = "VMVN"; break; case 12: case 13: name = "VPADAL"; dt = Bit(w, 7) ? Dt::U : Dt::S; break; case 14: name = "VQABS"; dt = Dt::S; break; case 15: name = "VQNEG"; dt = Dt::S; break; } break; case 1: { static const char *const names[8] = { "VCGT", "VCGE", "VCEQ", "VCLE", "VCLT", nullptr, "VABS", "VNEG" }; int op = Bits(w, 9, 7); bool f = Bit(w, 10); name = names[op]; if (!name) return false; o.Add("%s", name); if (f) { if (size != 2) return false; o.Add(".f32"); } else { AddDt(o, op == 2 ? Dt::I : Dt::S, false, bits); } o.Add(" %s, %s", V(q, d).s, V(q, m).s); if (op < 5) o.Add(", #0"); return true; } case 2: switch (b) { case 0x08: o.Add("VMOVN.i%d d%d, q%d", bits * 2, d, m >> 1); return true; case 0x09: o.Add("VQMOVUN.s%d d%d, q%d", bits * 2, d, m >> 1); return true; case 0x0A: case 0x0B: o.Add("VQMOVN.%c%d d%d, q%d", Bit(w, 6) ? 'u' : 's', bits * 2, d, m >> 1); return true; case 0x0C: o.Add("VSHLL.i%d q%d, d%d, #%d", bits, d >> 1, m, bits); return true; case 0x18: if (size != 1) return false; o.Add("VCVT.f16.f32 d%d, q%d", d, m >> 1); return true; case 0x1C: if (size != 1) return false; o.Add("VCVT.f32.f16 q%d, d%d", d >> 1, m); return true; } switch (b >> 1) { case 0: name = "VSWP"; break; case 1: name = "VTRN"; dt = Dt::PLAIN; break; case 2: name = "VUZP"; dt = Dt::PLAIN; break; case 3: name = "VZIP"; dt = Dt::PLAIN; break; } if ((b >> 1) >= 2 && size == 2 && !q) return false; break; case 3: if ((b & 0x1A) == 0x10 || (b & 0x1A) == 0x12) { name = (b & 2) ? "VRSQRTE" : "VRECPE"; dt = Bit(w, 8) ? Dt::F : Dt::U; } else if ((b & 0x18) == 0x18) { static const char *const conv[4] = { ".f32.s32", ".f32.u32", ".s32.f32", ".u32.f32" }; o.Add("VCVT%s %s, %s", conv[Bits(w, 8, 7)], V(q, d).s, V(q, m).s); return true; } break; } if (!name) return false; o.Add("%s", name); AddDt(o, dt, false, bits); o.Add(" %s, %s", V(q, d).s, V(q, m).s); return true; } bool DisNeonData(u32 w, Out &o) { if (!Bit(w, 23)) return DisNeon3Same(w, o); if (Bit(w, 4)) { if (Bits(w, 21, 19) == 0 && !Bit(w, 7)) return DisNeonModImm(w, o); return DisNeonShift(w, o); } if (Bits(w, 21, 20) != 3) return Bit(w, 6) ? DisNeonScalar(w, o) : DisNeon3Diff(w, o); bool q = Bit(w, 6); int d = NeonD(w), n = NeonN(w), m = NeonM(w); if (!Bit(w, 24)) { o.Add("VEXT.8 %s, %s, %s, #%d", V(q, d).s, V(q, n).s, V(q, m).s, Bits(w, 11, 8)); return true; } if (!Bit(w, 11)) return DisNeon2Misc(w, o); if (Bits(w, 11, 10) == 2) { int len = Bits(w, 9, 8) + 1; o.Add("%s.8 d%d, {d%d", Bit(w, 6) ? "VTBX" : "VTBL", d, n); if (len > 1) o.Add("-d%d", n + len - 1); o.Add("}, d%d", m); return true; } if (Bits(w, 11, 8) == 0xC) { int imm4 = Bits(w, 19, 16); int size, index; if (imm4 & 1) { size = 8; index = imm4 >> 1; } else if (imm4 & 2) { size = 16; index = imm4 >> 2; } else if (imm4 & 4) { size = 32; index = imm4 >> 3; } else { return false; } o.Add("VDUP.%d %s, d%d[%d]", size, V(q, d).s, m, index); return true; } return false; } bool DisNeonLoadStore(u32 w, Out &o) { bool load = Bit(w, 21); int rn = Bits(w, 19, 16); int rm = Bits(w, 3, 0); int d = NeonD(w); const char *name = load ? "VLD" : "VST"; int n, regs, inc, size, align = 0; bool lanes = false, allLanes = false; int index = 0; if (!Bit(w, 23)) { size = Bits(w, 7, 6); switch (Bits(w, 11, 8)) { case 7: n = 1; regs = 1; inc = 1; break; case 10: n = 1; regs = 2; inc = 1; break; case 6: n = 1; regs = 3; inc = 1; break; case 2: n = 1; regs = 4; inc = 1; break; case 8: n = 2; regs = 2; inc = 1; break; case 9: n = 2; regs = 2; inc = 2; break; case 3: n = 2; regs = 4; inc = 1; break; case 4: n = 3; regs = 3; inc = 1; break; case 5: n = 3; regs = 3; inc = 2; break; case 0: n = 4; regs = 4; inc = 1; break; case 1: n = 4; regs = 4; inc = 2; break; default: return false; } int a = Bits(w, 5, 4); if (a) align = 32 << a; } else if (Bits(w, 11, 10) == 3) { if (!load) return false; allLanes = true; n = Bits(w, 9, 8) + 1; size = Bits(w, 7, 6); bool t = Bit(w, 5); if (n == 1) { regs = t ? 2 : 1; inc = 1; } else { regs = n; inc = t ? 2 : 1; } if (Bit(w, 4)) { if (n == 1 || n == 2) align = (8 << size) * n; else if (n == 4) align = size == 2 ? 64 : (size == 3 ? 128 : 32 << size); else return false; } } else { lanes = true; size = Bits(w, 11, 10); n = Bits(w, 9, 8) + 1; regs = n; int ia = Bits(w, 7, 4); index = ia >> (size + 1); inc = 1; if (n > 1 && ((size == 1 && (ia & 2)) || (size == 2 && (ia & 4)))) inc = 2; switch (n) { case 1: if (size == 1 && (ia & 1)) align = 16; else if (size == 2 && (ia & 3) == 3) align = 32; break; case 2: if (ia & 1) align = 16 << size; break; case 4: if (size == 2 && (ia & 3)) align = 32 << (ia & 3); else if (size < 2 && (ia & 1)) align = 32 << size; break; } } o.Add("%s%d.%d {", name, n, 8 << size); if (n == 1 && !lanes && !allLanes && regs > 1) { o.Add("d%d-d%d", d, d + regs - 1); } else { for (int i = 0; i < regs; i++) { o.Add("%sd%d", i ? ", " : "", d + i * inc); if (lanes) o.Add("[%d]", index); else if (allLanes) o.Add("[]"); } } o.Add("}, [%s", R(rn)); if (align) o.Add(":%d", align); o.Add("]"); if (rm == 13) o.Add("!"); else if (rm != 15) o.Add(", %s", R(rm)); return true; } bool DisUnconditional(u32 addr, u32 w, Out &o) { if (Bits(w, 27, 25) == 1) return DisNeonData(w, o); if (Bits(w, 27, 24) == 4 && !Bit(w, 20)) return DisNeonLoadStore(w, o); if ((w & 0xFFFFFDFF) == 0xF1010000) { o.Add("SETEND %s", Bit(w, 9) ? "be" : "le"); return true; } if ((w & 0xFFFFFF00) == 0xF57FF000) { static const char *const options[16] = { nullptr, nullptr, "oshst", "osh", nullptr, nullptr, "nshst", "nsh", nullptr, nullptr, "ishst", "ish", nullptr, nullptr, "st", "sy", }; int option = Bits(w, 3, 0); switch (Bits(w, 7, 4)) { case 1: o.Add("CLREX"); return true; case 4: case 5: o.Add("%s ", Bits(w, 7, 4) == 4 ? "DSB" : "DMB"); if (options[option]) o.Add("%s", options[option]); else o.Add("#%d", option); return true; case 6: o.Add("ISB %s", option == 15 ? "sy" : ""); return true; default: return false; } } if ((Bits(w, 27, 24) == 5 || (Bits(w, 27, 24) == 7 && !Bit(w, 4))) && Bits(w, 21, 20) == 1 && Bits(w, 15, 12) == 15) { bool up = Bit(w, 23); o.Add("%s [%s", Bit(w, 22) ? "PLD" : "PLDW", R(Bits(w, 19, 16))); if (Bits(w, 27, 24) == 5) { int imm = Bits(w, 11, 0); if (up && imm == 0) o.Add("]"); else o.Add(", #%s%d]", up ? "" : "-", imm); } else { o.Add(", %s%s", up ? "" : "-", R(Bits(w, 3, 0))); AddShiftImm(o, Bits(w, 6, 5), Bits(w, 11, 7)); o.Add("]"); } return true; } if (Bits(w, 27, 25) == 5) { u32 target = addr + 8 + (SignExtend(Bits(w, 23, 0), 24) << 2) + (Bit(w, 24) << 1); o.Add("BLX 0x%08x", target); return true; } return false; } bool Disassemble(u32 addr, u32 w, Out &o) { if ((w >> 28) == 0xF) return DisUnconditional(addr, w, o); switch (Bits(w, 27, 25)) { case 0: if (Bit(w, 4) && Bit(w, 7)) { if (Bits(w, 6, 5) == 0) return Bit(w, 24) ? DisSynchronization(w, o) : DisMultiply(w, o); return DisExtraLoadStore(w, o); } if (Bits(w, 24, 23) == 2 && !Bit(w, 20)) return Bit(w, 7) ? DisHalfwordMultiply(w, o) : DisMisc(w, o); return DisDataProcessing(w, o); case 1: if (Bits(w, 24, 23) == 2 && !Bit(w, 20)) return DisMovImmAndHints(w, o); return DisDataProcessing(w, o); case 2: return DisLoadStore(w, o); case 3: return Bit(w, 4) ? DisMedia(w, o) : DisLoadStore(w, o); case 4: return DisBlockTransfer(w, o); case 5: { u32 target = addr + 8 + (SignExtend(Bits(w, 23, 0), 24) << 2); o.Add("%s%s 0x%08x", Bit(w, 24) ? "BL" : "B", Cond(w), target); return true; } default: return DisCoprocessor(w, o); } } } // namespace const char *ArmRegName(int r) { return R(r); } bool ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info) { *info = {}; info->instructionSize = 4; // TODO return false; } void ArmDis(unsigned int addr, unsigned int w, char *output, int bufsize, bool includeWord) { char text[256]; Out o(text, sizeof(text)); if (!Disassemble(addr, w, o)) snprintf(text, sizeof(text), "[undefined instr]"); if (includeWord) snprintf(output, bufsize, "%08x %s", w, text); else snprintf(output, bufsize, "%s", text); }