ppsspp/Core/Util/DisArm64.cpp
2021-02-15 11:59:45 -08:00

1097 lines
38 KiB
C++

// Copyright (c) 2015- 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/.
// Basic ARM64 disassembler.
// No promises of accuracy, mostly just made to debug JIT code.
// Does enough to understand what's going on without having to resort to an
// external disassembler all the time...
#include <cstdlib>
#include <cstring>
#include "Common/Arm64Emitter.h"
#include "Common/StringUtils.h"
#include "Core/Util/DisArm64.h"
struct Instruction {
char text[128];
bool undefined;
bool badbits;
bool oddbits;
};
static const char * const shiftnames[4] = { "lsl", "lsr", "asr", "ror" };
static const char * const extendnames[8] = { "uxtb", "uxth", "uxtw", "uxtx", "sxtb", "sxth", "sxtw", "sxtx" };
static const char * const condnames[16] = {
"eq", // Equal
"ne", // Not equal
"cs", // Carry Set "HS"
"cc", // Carry Clear "LO"
"mi", // Minus (Negative)
"pl", // Plus
"vs", // Overflow
"vc", // No Overflow
"hi", // Unsigned higher
"ls", // Unsigned lower or same
"ge", // Signed greater than or equal
"lt", // Signed less than
"gt", // Signed greater than
"le", // Signed less than or equal
"al", // Always (unconditional) 14
};
int SignExtend26(int x) {
return (x & 0x02000000) ? (0xFC000000 | x) : (x & 0x3FFFFFF);
}
int SignExtend19(int x) {
return (x & 0x00040000) ? (0xFFF80000 | x) : (x & 0x7FFFF);
}
int SignExtend9(int x) {
return (x & 0x00000100) ? (0xFFFFFE00 | x) : (x & 0x1FF);
}
int SignExtend7(int x) {
return (x & 0x00000040) ? (0xFFFFFF80 | x) : (x & 0x7F);
}
int SignExtend12(int x) {
return (x & 0x00000800) ? (0xFFFFF000 | x) : (x & 0xFFF);
}
int HighestSetBit(int value) {
int highest = 0;
for (int i = 0; i < 32; i++) {
if (value & (1 << i))
highest = i;
}
return highest;
}
int LowestSetBit(int value, int maximum = 32) {
for (int i = 0; i < maximum; i++) {
if (value & (1 << i))
return i;
}
return maximum;
}
static uint64_t Ones(int len) {
if (len == 0x40) {
return 0xFFFFFFFFFFFFFFFF;
}
return (1ULL << len) - 1;
}
static uint64_t Replicate(uint64_t value, int esize) {
uint64_t out = 0;
value &= Ones(esize);
for (int i = 0; i < 64; i += esize) {
out |= value << i;
}
return out;
}
static uint64_t ROR(uint64_t value, int amount, int esize) {
uint64_t rotated = (value >> amount) | (value << (esize - amount));
return rotated & Ones(esize);
}
void DecodeBitMasks(int immN, int imms, int immr, uint64_t *tmask, uint64_t *wmask) {
// Compute log2 of element size
// 2^len must be in range [2, M]
int len = HighestSetBit((immN << 6) | ((~imms) & 0x3f));
// if len < 1 then ReservedValue();
// assert M >= (1 << len);
// Determine S, R and S - R parameters
int levels = Ones(len);
uint32_t S = imms & levels;
uint32_t R = immr & levels;
int diff = S - R; // 6-bit subtract with borrow
int esize = 1 << len;
int d = diff & Ones(len - 1);
uint32_t welem = Ones(S + 1);
uint32_t telem = Ones(d + 1);
if (wmask) {
uint64_t rotated = ROR(welem, R, esize);
*wmask = Replicate(rotated, esize);
}
if (tmask) {
*tmask = Replicate(telem, esize);
}
}
static void DataProcessingImmediate(uint32_t w, uint64_t addr, Instruction *instr) {
int Rd = w & 0x1f;
int Rn = (w >> 5) & 0x1f;
char r = ((w >> 31) & 1) ? 'x' : 'w';
if (((w >> 23) & 0x3f) == 0x25) {
// Constant initialization.
int imm16 = (w >> 5) & 0xFFFF;
int opc = (w >> 29) & 3;
int shift = ((w >> 21) & 0x3) * 16;
const char *opnames[4] = { "movn", "(undef)", "movz", "movk" };
snprintf(instr->text, sizeof(instr->text), "%s %c%d, #0x%04x << %d", opnames[opc], r, Rd, imm16, shift);
} else if (((w >> 24) & 0x1F) == 0x10) {
// Address generation relative to PC
int op = w >> 31;
int imm = (SignExtend19(w >> 5) << 2) | ((w >> 29) & 3);
if (op & 1) imm <<= 12;
u64 daddr = addr + imm;
snprintf(instr->text, sizeof(instr->text), "%s x%d, #0x%04x%08x", op ? "adrp" : "adr", Rd, (u32)(daddr >> 32), (u32)(daddr & 0xFFFFFFFF));
} else if (((w >> 24) & 0x1F) == 0x11) {
// Add/subtract immediate value
int op = (w >> 30) & 1;
int imm = ((w >> 10) & 0xFFF);
int shift = ((w >> 22) & 0x1) * 12;
int s = ((w >> 29) & 1);
imm <<= shift;
if (s && Rd == 31) {
snprintf(instr->text, sizeof(instr->text), "cmp %c%d, #%d", r, Rn, imm);
} else if (!shift && Rn == 31 && imm == 0) {
snprintf(instr->text, sizeof(instr->text), "mov %c%d, sp", r, Rd);
} else if (!shift && Rd == 31 && imm == 0) {
snprintf(instr->text, sizeof(instr->text), "mov sp, %c%d", r, Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, %c%d, #%d", op == 0 ? "add" : "sub", s ? "s" : "", r, Rd, r, Rn, imm);
}
} else if (((w >> 23) & 0x3f) == 0x24) {
int immr = (w >> 16) & 0x3f;
int imms = (w >> 10) & 0x3f;
int N = (w >> 22) & 1;
int opc = (w >> 29) & 3;
const char *opname[4] = { "and", "orr", "eor", "ands" };
uint64_t wmask;
DecodeBitMasks(N, imms, immr, NULL, &wmask);
if (((w >> 31) & 1) && wmask & 0xFFFFFFFF00000000ULL)
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, #0x%x%08x", opname[opc], r, Rd, r, Rn, (uint32_t)(wmask >> 32), (uint32_t)(wmask & 0xFFFFFFFF));
else
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, #0x%x", opname[opc], r, Rd, r, Rn, (uint32_t)wmask);
} else if (((w >> 23) & 0x3f) == 0x26) {
int N = (w >> 22) & 1;
int opc = (w >> 29) & 3;
int immr = (w >> 16) & 0x3f;
int imms = (w >> 10) & 0x3f;
const char *opname[4] = { "sbfm", "bfm", "ubfm" };
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, #%d, #%d", opname[opc], r, Rd, r, Rn, immr, imms);
} else {
snprintf(instr->text, sizeof(instr->text), "(DPI %08x)", w);
}
}
static const char *GetSystemRegName(int o0, int op1, int CRn, int CRm, int op2) {
if (o0 == 3 && op1 == 3 && CRn == 4 && CRm == 2 && op2 == 0) {
return "nzcv";
} else if (o0 == 3 && op1 == 3 && CRn == 4 && CRm == 4 && op2 == 0) {
return "fpsr";
} else {
return "(unknown)";
}
}
static void BranchExceptionAndSystem(uint32_t w, uint64_t addr, Instruction *instr, SymbolCallback symbolCallback) {
char buffer[128];
int Rt = w & 0x1f;
int Rn = (w >> 5) & 0x1f;
if (((w >> 26) & 0x1F) == 5) {
// Unconditional branch / branch+link
int offset = SignExtend26(w) << 2;
uint64_t target = addr + offset;
if (symbolCallback && symbolCallback(buffer, sizeof(buffer), (uint8_t *)(uintptr_t)target)) {
snprintf(instr->text, sizeof(instr->text), "b%s %s", (w >> 31) ? "l" : "", buffer);
} else {
snprintf(instr->text, sizeof(instr->text), "b%s %04x%08x", (w >> 31) ? "l" : "", (uint32_t)(target >> 32), (uint32_t)(target & 0xFFFFFFFF));
}
} else if (((w >> 25) & 0x3F) == 0x1A) {
// Compare and branch
int op = (w >> 24) & 1;
const char *opname[2] = { "cbz", "cbnz" };
char r = ((w >> 31) & 1) ? 'x' : 'w';
int offset = SignExtend19(w >> 5);
snprintf(instr->text, sizeof(instr->text), "%s %c%d", opname[op], r, Rt);
} else if (((w >> 25) & 0x3F) == 0x1B) {
// Test and branch
snprintf(instr->text, sizeof(instr->text), "(test & branch %08x)", w);
} else if (((w >> 25) & 0x7F) == 0x2A) {
// Conditional branch
int offset = SignExtend19(w >> 5) << 2;
uint64_t target = addr + offset;
int cond = w & 0xF;
snprintf(instr->text, sizeof(instr->text), "b.%s %04x%08x", condnames[cond], (uint32_t)(target >> 32), (uint32_t)(target & 0xFFFFFFFF));
} else if ((w >> 24) == 0xD4) {
if (((w >> 21) & 0x7) == 1 && Rt == 0) {
int imm = (w >> 5) & 0xFFFF;
snprintf(instr->text, sizeof(instr->text), "brk #%d", imm);
} else {
snprintf(instr->text, sizeof(instr->text), "(exception-gen %08x)", w);
}
} else if (((w >> 20) & 0xFFC) == 0xD50) {
bool L = (w >> 21) & 1; // read
// Could check them all at once, but feels better to do it like the manual says.
int o0 = (w >> 19) & 3;
int op1 = (w >> 16) & 7;
int CRn = (w >> 12) & 0xF;
int CRm = (w >> 8) & 0xf;
int op2 = (w >> 5) & 0x7;
const char *sysreg = GetSystemRegName(o0, op1, CRn, CRm, op2);
if (L) {
snprintf(instr->text, sizeof(instr->text), "mrs x%d, %s", Rt, sysreg);
} else {
snprintf(instr->text, sizeof(instr->text), "msr %s, x%d", sysreg, Rt);
}
} else if (((w >> 25) & 0x7F) == 0x6B) {
int op = (w >> 21) & 3;
const char *opname[4] = { "b", "bl", "ret", "(unk)" };
snprintf(instr->text, sizeof(instr->text), "%s x%d", opname[op], Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "(BRX ?? %08x)", w);
}
}
bool Arm64AnalyzeLoadStore(uint64_t addr, uint32_t w, Arm64LSInstructionInfo *info) {
*info = {};
info->instructionSize = 4;
int id = (w >> 25) & 0xF;
switch (id) {
case 4: case 6: case 0xC: case 0xE:
break;
default:
return false; // not the expected instruction
}
info->size = w >> 30;
info->Rt = (w & 0x1F);
info->Rn = ((w >> 5) & 0x1F);
info->Rm = ((w >> 16) & 0x1F);
int opc = (w >> 22) & 0x3;
if (opc == 0 || opc == 2) {
info->isMemoryWrite = true;
}
if (((w >> 27) & 7) == 7) {
int V = (w >> 26) & 1;
if (V == 0) {
info->isIntegerLoadStore = true;
} else {
info->isFPLoadStore = true;
}
} else {
info->isPairLoadStore = true;
// TODO
}
return true;
}
static void LoadStore(uint32_t w, uint64_t addr, Instruction *instr) {
int size = w >> 30;
int imm9 = SignExtend9((w >> 12) & 0x1FF);
int Rt = (w & 0x1F);
int Rn = ((w >> 5) & 0x1F);
int Rm = ((w >> 16) & 0x1F);
int option = (w >> 13) & 0x7;
int opc = (w >> 22) & 0x3;
char r = size == 3 ? 'x' : 'w';
const char *opname[4] = { "str", "ldr", "str", "ldr" };
const char *sizeSuffix[4] = { "b", "h", "", "" };
if (((w >> 27) & 7) == 7) {
int V = (w >> 26) & 1;
bool index_unsigned = ((w >> 24) & 3) == 1;
bool index_post = !index_unsigned && ((w >> 10) & 3) == 1;
bool index_pre = !index_unsigned && ((w >> 10) & 3) == 3;
if (V == 0) {
const char *signExt = ((opc & 0x2) && size < 3) ? "s" : "";
int imm12 = SignExtend12((w >> 10) & 0xFFF) << size;
// Integer type
if (index_unsigned) {
snprintf(instr->text, sizeof(instr->text), "%s%s%s %c%d, [x%d, #%d]", opname[opc], signExt, sizeSuffix[size], r, Rt, Rn, imm12);
return;
} else if (index_post) {
snprintf(instr->text, sizeof(instr->text), "%s%s%s %c%d, [x%d], #%d", opname[opc], signExt, sizeSuffix[size], r, Rt, Rn, SignExtend9(imm9));
return;
} else if (index_pre) {
snprintf(instr->text, sizeof(instr->text), "%s%s%s %c%d, [x%d, #%d]!", opname[opc], signExt, sizeSuffix[size], r, Rt, Rn, SignExtend9(imm9));
return;
} else {
// register offset
int S = (w >> 12) & 1;
char index_w = (option & 3) == 2 ? 'w' : 'x';
// TODO: Needs index support
snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, [x%d + %c%d]", opname[opc], sizeSuffix[size], r, Rt, Rn, index_w, Rm);
return;
}
} else {
// FP/Vector type
if ((opc & 2) && size == 0) {
size = 4;
}
char vr = "bhsdq"[size];
int imm12 = SignExtend12((w >> 10) & 0xFFF) << size;
if (index_unsigned) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, [x%d, #%d]", opname[opc], vr, Rt, Rn, imm12);
return;
} else if (index_post) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, [x%d], #%d", opname[opc], vr, Rt, Rn, SignExtend9(imm9));
return;
} else if (index_pre) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, [x%d, #%d]!", opname[opc], vr, Rt, Rn, SignExtend9(imm9));
return;
} else {
snprintf(instr->text, sizeof(instr->text), "(loadstore-fp-vector %08x)", w);
return;
}
}
} else if (((w >> 25) & 0x1D) == 0x14) {
// load/store pair
int Rt2 = (w >> 10) & 0x1f;
bool load = (w >> 22) & 1;
int index_type = ((w >> 23) & 3);
bool sf = (w >> 31) != 0;
bool V = (w >> 26) & 1;
int op = (w >> 30);
int offset = SignExtend7((w >> 15) & 0x7f);
if (V) {
offset <<= 2;
switch (op) {
case 0:
r = 's';
break;
case 1:
r = 'd';
if (index_type == 2)
offset <<= 1;
break;
case 2:
r = 'q';
if (index_type == 2)
offset <<= 2;
}
} else {
r = sf ? 'x' : 'w';
offset <<= (sf ? 3 : 2);
}
if (index_type == 2) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d, #%d]", load ? "ldp" : "stp", r, Rt, r, Rt2, Rn, offset);
return;
} else if (index_type == 1) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d], #%d", load ? "ldp" : "stp", r, Rt, r, Rt2, Rn, offset);
return;
} else if (index_type == 3) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d, #%d]!", load ? "ldp" : "stp", r, Rt, r, Rt2, Rn, offset);
return;
} else if (index_type == 0) {
// LDNP/STNP (ldp/stp with non-temporal hint). Automatically signed offset.
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d, #%d]", load ? "ldnp" : "stnp", r, Rt, r, Rt2, Rn, offset);
return;
}
}
snprintf(instr->text, sizeof(instr->text), "(LS %08x)", w);
}
static void DataProcessingRegister(uint32_t w, uint64_t addr, Instruction *instr) {
int Rd = w & 0x1F;
int Rn = (w >> 5) & 0x1F;
int Rm = (w >> 16) & 0x1F;
char r = ((w >> 31) & 1) ? 'x' : 'w';
if (((w >> 21) & 0x2FF) == 0x2D6) {
// Data processing
int opcode2 = (w >> 16) & 0x1F;
int opcode = (w >> 10) & 0x3F;
// Data-processing (1 source)
const char *opname[8] = { "rbit", "rev16", "rev32", "(unk)", "clz", "cls" };
const char *op = opcode2 >= 8 ? "unk" : opname[opcode];
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", op, r, Rd, r, Rn);
} else if (((w >> 21) & 0x2FF) == 0x0D6) {
const char *opname[32] = {
0, 0, "udiv", "sdiv", 0, 0, 0, 0,
"lslv", "lsrv", "asrv", "rorv", 0, 0, 0, 0,
"crc32b", "crc32h", "crc32w", 0, "crc32cb", "crc32ch", "crc32cw", 0,
};
int opcode = (w >> 10) & 0x3F;
// Data processing (2 source)
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opname[opcode], r, Rd, r, Rn, r, Rm);
} else if (((w >> 24) & 0x1f) == 0xA) {
// Logical (shifted register)
int shift = (w >> 22) & 0x3;
int imm6 = (w >> 10) & 0x3f;
int N = (w >> 21) & 1;
int opc = (((w >> 29) & 3) << 1) | N;
const char *opnames[8] = { "and", "bic", "orr", "orn", "eor", "eon", "ands", "bics" };
if (opc == 2 && Rn == 31) {
// Special case for MOV (which is constructed from an ORR)
if (imm6 != 0) {
snprintf(instr->text, sizeof(instr->text), "mov %c%d, %c%d, %s #%d", r, Rd, r, Rm, shiftnames[shift], imm6);
} else {
snprintf(instr->text, sizeof(instr->text), "mov %c%d, %c%d", r, Rd, r, Rm);
}
} else if (imm6 == 0 && shift == 0) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opnames[opc], r, Rd, r, Rn, r, Rm);
} else {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %s #%d", opnames[opc], r, Rd, r, Rn, r, Rm, shiftnames[shift], imm6);
}
} else if (((w >> 21) & 0xf9) == 0x58) {
// Add/sub/cmp (shifted register)
bool S = (w >> 29) & 1;
int shift = (w >> 22) & 0x3;
int imm6 = (w >> 10) & 0x3f;
int opc = ((w >> 29) & 3);
const char *opnames[8] = { "add", "adds", "sub", "subs"};
if (imm6 == 0 && shift == 0) {
if (Rd == 31 && opc == 3) {
// It's a CMP
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", "cmp", r, Rn, r, Rm);
} else if (Rn == 31 && opc == 2) {
// It's a NEG
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", "neg", r, Rd, r, Rm);
} else {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opnames[opc], r, Rd, r, Rn, r, Rm);
}
} else {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %s #%d", opnames[opc], r, Rd, r, Rn, r, Rm, shiftnames[shift], imm6);
}
} else if (((w >> 21) & 0xFF) == 0x59) {
// Add/sub (extended register)
bool S = (w >> 29) & 1;
bool sub = (w >> 30) & 1;
int option = (w >> 13) & 0x7;
int imm3 = (w >> 10) & 0x7;
if (Rd == 31 && sub && S) {
// It's a CMP
snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, %c%d, %s", "cmp", S ? "s" : "", r, Rn, r, Rm, extendnames[option]);
} else {
snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, %c%d, %c%d, %s", sub ? "sub" : "add", S ? "s" : "", r, Rd, r, Rn, r, Rm, extendnames[option]);
}
} else if (((w >> 21) & 0xFF) == 0xD6 && ((w >> 12) & 0xF) == 2) {
// Variable shifts
int opc = (w >> 10) & 3;
snprintf(instr->text, sizeof(instr->text), "%sv %c%d, %c%d, %c%d", shiftnames[opc], r, Rd, r, Rn, r, Rm);
} else if (((w >> 21) & 0xFF) == 0xD4) {
// Conditional select
int op = (w >> 30) & 1;
int op2 = (w >> 10) & 3;
int cond = (w >> 12) & 0xf;
const char *opnames[4] = { "csel", "csinc", "csinv", "csneg" };
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %s", opnames[(op << 1) | op2], r, Rd, r, Rn, r, Rm, condnames[cond]);
} else if (((w >> 24) & 0x1f) == 0x1b) {
// Data processing - 3 source
int op31 = (w >> 21) & 0x7;
int o0 = (w >> 15) & 1;
int Ra = (w >> 10) & 0x1f;
const char *opnames[8] = { 0, 0, "maddl", "msubl", "smulh", 0, 0, 0 };
if (op31 == 0) {
// madd/msub supports both 32-bit and 64-bit modes
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %c%d", o0 ? "msub" : "madd", r, Rd, r, Rn, r, Rm, r, Ra);
} else {
// The rest are 64-bit accumulator, 32-bit operands
char sign = (op31 >> 2) ? 'u' : 's';
int opn = (op31 & 0x3) << 1 | o0;
if (opn < 4 && Ra == 31) {
snprintf(instr->text, sizeof(instr->text), "%cmull x%d, w%d, w%d", sign, Rd, Rn, Rm);
} else {
snprintf(instr->text, sizeof(instr->text), "%c%s x%d, w%d, w%d, x%d", sign, opnames[opn], Rd, Rn, Rm, Ra);
}
}
} else {
// Logical (extended register)
snprintf(instr->text, sizeof(instr->text), "(DPR %08x)", w);
}
}
inline bool GetQ(uint32_t w) { return (w >> 30) & 1; }
inline bool GetU(uint32_t w) { return (w >> 29) & 1; }
const char *GetArrangement(bool Q, bool sz) {
if (Q == 0 && sz == 0) return "2s";
else if (Q == 1 && sz == 0) return "4s";
else if (Q == 1 && sz == 1) return "2d";
else return "ERROR";
}
// (w >> 25) & 0xF == 7
static void FPandASIMD1(uint32_t w, uint64_t addr, Instruction *instr) {
int Rd = w & 0x1f;
int Rn = (w >> 5) & 0x1f;
int Rm = (w >> 16) & 0x1f;
if (((w >> 21) & 0x4F9) == 0x71) {
switch ((w >> 10) & 3) {
case 1: case 3: {
int opcode = (w >> 11) & 0x1f;
int sz = (w >> 22) & 3;
int Q = GetQ(w);
int U = GetU(w);
const char *opnames000[32] = {
"shadd", "sqadd", "srhadd", 0,
"shsub", "sqsub", "cmgt", "cmge",
"sshl", "sqshl", "srshl", "sqrshl",
"smax", "smin", "sabd", "saba",
"add", "cmtst", "mla", "mul",
"smaxp", "sminp", "sqdmulh", "addp",
"fmaxnm", "fmla", "fadd", "fmulx",
"fcmeq", 0, "fmax", "frecps",
};
const char *opnames100[32] = {
"uhadd", "uqadd", "urhadd", 0,
"uhsub", "uqsub", "cmhi", "cmhs",
"ushl", "uqshl", "urshl", "uqrshl",
"umax", "umin", "uabd", "uaba",
"sub", "cmeq", "mls", "pmul",
"umaxp", "uminp", "sqrdmulh", "addp",
"fmaxnmp", 0, "faddp", "fmul",
"fcmge", "facge", "fmaxp", "fdiv",
};
const char *opnames010[8] = {
"fminm", "fmls", "fsub", 0,
0, 0, "fmin", "frsqrts",
};
const char *opnames110[8] = {
"fminnmp", 0, "fabd", 0,
"fcmgt", "facgt", "fminp", 0,
};
char r = Q ? 'q' : 'd';
const char *opname = nullptr;
bool fp = false;
bool nosize = false;
if (U == 0) {
if (opcode < 0x18) {
opname = opnames000[opcode];
} else if ((sz & 0x2) == 0) {
opname = opnames000[opcode];
fp = true;
} else if ((sz & 0x2) == 2) {
opname = opnames010[opcode - 0x18];
fp = true;
}
if (!opname && opcode == 3 && (sz & 2) == 0) {
opname = !(sz & 1) ? "and" : "bic";
nosize = true;
} else if (!opname && opcode == 3 && (sz & 2) == 2) {
opname = !(sz & 1) ? "orr" : "orn";
nosize = true;
}
} else if (U == 1) {
if (opcode < 0x18) {
opname = opnames100[opcode];
} else if ((sz & 0x2) == 0) {
opname = opnames100[opcode];
fp = true;
} else if ((sz & 0x2) == 2) {
opname = opnames110[opcode - 0x18];
fp = true;
}
if (!opname && opcode == 3 && (sz & 2) == 0) {
opname = !(sz & 1) ? "eor" : "bsl";
if (!strcmp(opname, "eor"))
nosize = true;
} else if (!opname && opcode == 3 && (sz & 2) == 2) {
opname = !(sz & 1) ? "bit" : "bif";
}
}
int size = (fp ? ((sz & 1) ? 64 : 32) : (8 << sz));
if (opname != nullptr) {
if (!nosize) {
snprintf(instr->text, sizeof(instr->text), "%s.%d %c%d, %c%d, %c%d", opname, size, r, Rd, r, Rn, r, Rm);
} else {
if (!strcmp(opname, "orr") && Rn == Rm) {
snprintf(instr->text, sizeof(instr->text), "mov %c%d, %c%d", r, Rd, r, Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opname, r, Rd, r, Rn, r, Rm);
}
}
} else {
snprintf(instr->text, sizeof(instr->text), "(asimd three-same %08x)", w);
}
break;
}
case 0:
snprintf(instr->text, sizeof(instr->text), "(asimd three-different %08x)", w);
break;
case 2:
if (((w >> 17) & 0xf) == 0) {
int opcode = (w >> 12) & 0x1F;
int sz = (w >> 22) & 3;
int Q = GetQ(w);
int U = GetU(w);
const char *opname = nullptr;
bool narrow = false;
if (!U) {
switch (opcode) {
case 0: opname = "rev64"; break;
case 1: opname = "rev16"; break;
case 2: opname = "saddlp"; break;
case 3: opname = "suqadd"; break;
case 4: opname = "cls"; break;
case 5: opname = "cnt"; break;
case 6: opname = "sadalp"; break;
case 7: opname = "sqabs"; break;
case 8: opname = "cmgt"; break;
case 9: opname = "cmeq"; break;
case 0xA: opname = "cmlt"; break;
case 0xB: opname = "abs"; break;
case 0x12: opname = "xtn"; narrow = true; break;
case 0x14: opname = "sqxtn"; narrow = true; break;
default:
if (!(sz & 0x2)) {
switch (opcode) {
case 0x16: opname = "fcvtn"; break;
case 0x17: opname = "fcvtl"; break;
case 0x18: opname = "frintn"; break;
case 0x19: opname = "frintm"; break;
case 0x1a: opname = "fcvtns"; break;
case 0x1b: opname = "fcvtms"; break;
case 0x1c: opname = "fcvtas"; break;
case 0x1d: opname = "scvtf"; break;
}
} else {
switch (opcode) {
case 0xc: opname = "fcmgt"; break;
case 0xd: opname = "fcmeq"; break;
case 0xe: opname = "fcmlt"; break;
case 0xf: opname = "fabs"; break;
case 0x18: opname = "frintp"; break;
case 0x19: opname = "frintz"; break;
case 0x1a: opname = "fcvtps"; break;
case 0x1b: opname = "fcvtzs"; break;
case 0x1c: opname = "urepce"; break;
case 0x1d: opname = "frepce"; break;
}
}
}
} else {
switch (opcode) {
case 0: opname = "rev32"; break;
case 2: opname = "uaddlp"; break;
case 3: opname = "usqadd"; break;
case 4: opname = "clz"; break;
case 6: opname = "uadalp"; break;
case 7: opname = "sqneg"; break;
case 8: opname = "cmge"; break; // with zero
case 9: opname = "cmle"; break; // with zero
case 0xB: opname = "neg"; break;
case 0x12: opname = "sqxtun"; narrow = true; break;
case 0x13: opname = "shll"; break;
case 0x14: opname = "uqxtn"; narrow = true; break;
case 5: if (sz == 0) opname = "not"; else opname = "rbit"; break;
default:
if (!(sz & 0x2)) {
switch (opcode) {
case 0x16: opname = "fcvtxn"; break;
case 0x18: opname = "frinta"; break;
case 0x19: opname = "frintx"; break;
case 0x1a: opname = "fcvtnu"; break;
case 0x1b: opname = "fcvtmu"; break;
case 0x1c: opname = "fcvtau"; break;
case 0x1d: opname = "ucvtf"; break;
}
} else {
switch (opcode) {
case 0xC: opname = "fcmge"; break; // with zero
case 0xD: opname = "fcmge"; break; // with zero
case 0xF: opname = "fneg"; break;
case 0x19: opname = "frinti"; break;
case 0x1a: opname = "fcvtpu"; break;
case 0x1b: opname = "fcvtzu"; break;
case 0x1c: opname = "ursqrte"; break;
case 0x1d: opname = "frsqrte"; break;
case 0x1f: opname = "fsqrt"; break;
}
}
}
}
if (opname) {
if (narrow) {
int esize = 8 << sz;
const char *two = ""; // todo
snprintf(instr->text, sizeof(instr->text), "%s%s.%d.%d d%d, q%d", opname, two, esize, esize * 2, Rd, Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "%s", opname);
}
} else {
// Very similar to scalar two-reg misc. can we share code?
snprintf(instr->text, sizeof(instr->text), "(asimd vector two-reg misc %08x)", w);
}
} else if (((w >> 17) & 0xf) == 1) {
snprintf(instr->text, sizeof(instr->text), "(asimd across lanes %08x)", w);
} else {
goto bail;
}
}
} else if (((w >> 21) & 0x4F9) == 0x70) {
if (((w >> 10) & 0x21) == 1) {
if (((w >> 11) & 3) == 3) {
// From GPR
snprintf(instr->text, sizeof(instr->text), "(asimd copy gpr %08x)", w);
} else {
int imm5 = (w >> 16) & 0x1F;
int size = LowestSetBit(imm5, 5);
int imm4 = (w >> 11) & 0xF;
int dst_index = imm5 >> (size + 1);
int src_index = imm4 >> size;
int op = (w >> 29) & 1;
char s = "bhsd"[size];
if (op == 0 && imm4 == 0) {
// DUP (element)
int idxdsize = (imm5 & 8) ? 128 : 64;
char r = "dq"[idxdsize == 128];
snprintf(instr->text, sizeof(instr->text), "dup %c%d, %c%d.%c[%d]", r, Rd, r, Rn, s, dst_index);
} else {
int idxdsize = (imm4 & 8) ? 128 : 64;
char r = "dq"[idxdsize == 128];
snprintf(instr->text, sizeof(instr->text), "ins %c%d.%c[%d], %c%d.%c[%d]", r, Rd, s, dst_index, r, Rn, s, src_index);
}
}
}
} else if (((w >> 21) & 0x4F8) == 0x78) {
if ((w >> 10) & 1) {
if (((w >> 19) & 0xf) == 0) {
snprintf(instr->text, sizeof(instr->text), "(asimd modified immediate %08x)", w);
} else {
bool Q = GetQ(w);
bool U = GetU(w);
int immh = (w >> 19) & 0xf;
int immb = (w >> 16) & 7;
int opcode = (w >> 11) & 0x1f;
const char *opnamesU0[32] = {
"sshr", 0, "ssra", 0,
"srshr", 0, "srsra", 0,
0, 0, "shl", 0,
0, 0, "sqshl", 0,
"shrn", "rshrn", "sqshrn", "sqrshrn",
"sshll", 0, 0, 0,
0, 0, 0, 0,
"scvtf", 0, 0, "fcvtzs",
};
const char *opnamesU1[32] = {
"ushr", 0, "usra", 0,
"urshr", 0, "ursra", 0,
"sri", 0, "sli", 0,
"sqslu", 0, "uqshl", 0,
"sqshrun", "sqrshrun", "uqshrn", "uqrshrn",
"ushll", 0, 0, 0,
0, 0, 0, 0,
"ucvtf", 0, 0, "fcvtzu",
};
const char *opname = U ? opnamesU1[opcode] : opnamesU0[opcode];
const char *two = Q ? "2" : ""; // TODO: This doesn't apply to all the ops
if (opname && (!strcmp(opname, "scvtf") || !strcmp(opname, "ucvtf"))) {
int esize = (8 << HighestSetBit(immh));
int shift = 2 * esize - ((immh << 3) | immb);
int r = Q ? 'q' : 'd';
snprintf(instr->text, sizeof(instr->text), "%ccvtf %c%d.s, %c%d.s, #%d", U ? 'u' : 's', r, Rd, r, Rn, shift);
} else if (opname && (!strcmp(opname, "ushr") || !strcmp(opname, "sshr"))) {
int esize = (8 << HighestSetBit(immh));
int shift = esize * 2 - ((immh << 3) | immb);
int r = Q ? 'q' : 'd';
snprintf(instr->text, sizeof(instr->text), "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, shift);
} else if (opname && (!strcmp(opname, "rshrn") || !strcmp(opname, "shrn"))) {
int esize = (8 << HighestSetBit(immh));
int shift = esize * 2 - ((immh << 3) | immb);
snprintf(instr->text, sizeof(instr->text), "%s%s.%d.%d d%d, q%d, #%d", opname, two, esize, esize * 2, Rd, Rn, shift);
} else if (opname && (!strcmp(opname, "shl"))) {
int esize = (8 << HighestSetBit(immh));
int r = Q ? 'q' : 'd';
int shift = ((immh << 3) | immb) - esize;
snprintf(instr->text, sizeof(instr->text), "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, shift);
} else if (opname) {
int esize = (8 << HighestSetBit(immh));
int shift = ((immh << 3) | immb) - esize;
if (shift == 0 && opcode == 0x14) {
snprintf(instr->text, sizeof(instr->text), "%cxtl%s.%d.%d q%d, d%d", U ? 'u' : 's', two, esize * 2, esize, Rd, Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "%s%s.%d.%d q%d, d%d, #%d", opname, two, esize * 2, esize, Rd, Rn, shift);
}
} else {
snprintf(instr->text, sizeof(instr->text), "(asimd shift-by-immediate %08x)", w);
}
}
} else {
bool Q = GetQ(w);
bool U = GetU(w);
int size = (w >> 22) & 3;
bool L = (w >> 21) & 1;
bool M = (w >> 20) & 1;
bool H = (w >> 11) & 1;
int opcode = (w >> 12) & 0xf;
if (size & 0x2) {
const char *opname = 0;
switch (opcode) {
case 1: opname = "fmla"; break;
case 5: opname = "fmls"; break;
case 9: opname = "fmul"; break;
}
int index;
if ((size & 1) == 0) {
index = (H << 1) | (int)L;
} else {
index = H;
}
char r = Q ? 'q' : 'd';
const char *arrangement = GetArrangement(Q, size & 1);
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d.%s[%d]", opname, r, Rd, r, Rn, r, Rm, arrangement, index);
} else {
snprintf(instr->text, sizeof(instr->text), "(asimd vector x indexed elem %08x)", w);
}
}
} else {
bail:
snprintf(instr->text, sizeof(instr->text), "(FP1 %08x)", w);
}
}
// (w >> 25) & 0xF == f
static void FPandASIMD2(uint32_t w, uint64_t addr, Instruction *instr) {
int Rd = w & 0x1f;
int Rn = (w >> 5) & 0x1f;
int Rm = (w >> 16) & 0x1f;
int type = (w >> 22) & 0x3;
int sf = (w >> 31);
if (((w >> 21) & 0x2F9) == 0xF0) {
int rmode = (w >> 19) & 3;
int opcode = (w >> 16) & 7;
int scale = 64 - ((w >> 10) & 0x3f);
char fr = type == 1 ? 'd' : 's';
char ir = sf == 1 ? 'x' : 'w';
char sign = (opcode & 1) ? 'u' : 's';
if (rmode == 0) {
snprintf(instr->text, sizeof(instr->text), "%ccvtf %c%d, %c%d, #%d", sign, fr, Rd, ir, Rn, scale);
} else if (rmode == 3) {
snprintf(instr->text, sizeof(instr->text), "fcvtz%c %c%d, %c%d, #%d", sign, ir, Rd, fr, Rn, scale);
} else {
snprintf(instr->text, sizeof(instr->text), "(float<->fixed %08x)", w);
}
} else if (((w >> 21) & 0x2F9) == 0xF1) {
int opcode = (w >> 16) & 7;
if (((w >> 10) & 3) == 0) {
if (((w >> 10) & 7) == 4) {
uint8_t uimm8 = (w >> 13) & 0xff;
float fl_imm = Arm64Gen::FPImm8ToFloat(uimm8);
char fr = ((w >> 22) & 1) ? 'd' : 's';
snprintf(instr->text, sizeof(instr->text), "fmov %c%d, #%f", fr, Rd, fl_imm);
} else if (((w >> 10) & 0xf) == 8) {
int opcode2 = w & 0x1f;
int e = opcode2 >> 4;
int z = (opcode2 >> 3) & 1;
char r = type == 1 ? 'd' : 's';
if (z) {
snprintf(instr->text, sizeof(instr->text), "fcmp %c%d, #0.0", r, Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "fcmp %c%d, %c%d", r, Rn, r, Rm);
}
} else if (((w >> 10) & 0x1f) == 0x10) {
// snprintf(instr->text, sizeof(instr->text), "(data 1-source %08x)", w);
const char *opnames[4] = { "fmov", "fabs", "fneg", "fsqrt" };
int opc = (w >> 15) & 0x3;
snprintf(instr->text, sizeof(instr->text), "%s s%d, s%d", opnames[opc], Rd, Rn); // TODO: Support doubles too
} else if (((w >> 10) & 0x1bf) == 0x180) {
// Generalized FMOV
char ir = sf ? 'x' : 'w';
bool tosimd = (opcode & 0x1);
char fr = ((w >> 22) & 1) ? 'd' : 's';
if (tosimd) {
snprintf(instr->text, sizeof(instr->text), "fmov %c%d, %c%d", fr, Rd, ir, Rn);
} else {
snprintf(instr->text, sizeof(instr->text), "fmov %c%d, %c%d", ir, Rd, fr, Rn);
}
} else if (((w >> 10) & 0x3f) == 0x0 && opcode == 0) {
char ir = sf ? 'x' : 'w';
char roundmode = "npmz"[(w >> 19) & 3];
if (opcode & 0x4)
roundmode = 'a';
char fr = ((w >> 22) & 1) ? 'd' : 's';
snprintf(instr->text, sizeof(instr->text), "fcvt%cs %c%d, %c%d", roundmode, ir, Rd, fr, Rn);
} else if ((opcode & 6) == 2) {
char ir = sf ? 'x' : 'w';
char fr = ((w >> 22) & 1) ? 'd' : 's';
char sign = (opcode & 1) ? 'u' : 's';
snprintf(instr->text, sizeof(instr->text), "%ccvtf %c%d, %c%d", sign, fr, Rd, ir, Rn);
}
} else if (((w >> 10) & 3) == 1) {
snprintf(instr->text, sizeof(instr->text), "(float cond compare %08x)", w);
} else if (((w >> 10) & 3) == 2) {
int opc = (w >> 12) & 0xf;
const char *opnames[9] = { "fmul", "fdiv", "fadd", "fsub", "fmax", "fmin", "fmaxnm", "fminnm", "fnmul" };
char r = ((w >> 22) & 1) ? 'd' : 's';
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opnames[opc], r, Rd, r, Rn, r, Rm);
} else if (((w >> 10) & 3) == 3) {
char fr = ((w >> 22) & 1) ? 'd' : 's';
int cond = (w >> 12) & 0xf;
snprintf(instr->text, sizeof(instr->text), "fcsel %c%d, %c%d, %c%d, %s", fr, Rd, fr, Rn, fr, Rm, condnames[cond]);
}
} else if (((w >> 21) & 0x2F8) == 0xF8) {
int opcode = ((w >> 15) & 1) | ((w >> 20) & 2);
const char *opnames[9] = { "fmadd", "fmsub", "fnmadd", "fnmsub" };
int size = (w >> 22) & 1;
char r = size ? 'd' : 's';
int Ra = (w >> 10) & 0x1f;
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %c%d", opnames[opcode], r, Rd, r, Rn, r, Rm, r, Ra);
} else if (((w >> 21) & 0x2F9) == 0x2F1) {
if (((w >> 10) & 3) == 0) {
snprintf(instr->text, sizeof(instr->text), "(asimd scalar three different %08x)", w);
} else if (((w >> 10) & 1) == 1) {
snprintf(instr->text, sizeof(instr->text), "(asimd scalar three same %08x)", w);
} else if (((w >> 10) & 3) == 2) {
// asimd scalar two-reg misc: This is a particularly sad and messy encoding :/
if (((w >> 17) & 0xf) == 0) {
int sz = (w >> 22) & 3;
char r = (sz & 1) ? 'd' : 's';
int opcode = (w >> 12) & 0x1f;
bool U = ((w >> 29) & 1);
const char *opname = NULL;
bool zero = false;
bool sign_suffix = false;
bool sign_prefix = false;
if ((sz & 2) == 0) {
switch (opcode) {
case 0x1a: opname = "fcvtn"; sign_suffix = true; break;
case 0x1b: opname = "fcvtm"; sign_suffix = true; break;
case 0x1c: opname = "fcvta"; sign_suffix = true; break;
case 0x1d: opname = "cvtf"; sign_prefix = true; break;
}
} else {
if (U == 0) {
switch (opcode) {
case 0xC: opname = "fcmgt"; zero = true; break;
case 0xD: opname = "fcmeq"; zero = true; break;
case 0xE: opname = "fcmlt"; zero = true; break;
case 0x1A: opname = "fcvtp"; sign_suffix = true; break;
case 0x1B: opname = "fcvtz"; sign_suffix = true; break;
}
} else {
switch (opcode) {
case 0x1A: opname = "fcvtp"; sign_suffix = true; break;
case 0x1B: opname = "fcvtz"; sign_suffix = true; break;
}
}
}
if (!opname) { // These ignore size.
if (U == 0) {
switch (opcode) {
case 3: opname = "suqadd"; break;
case 7: opname = "sqabs"; break;
case 8: opname = "cmgt"; zero = true; break;
case 9: opname = "cmeq"; zero = true; break;
case 0xa: opname = "cmlt"; zero = true; break;
case 0xb: opname = "abs"; break;
case 0xc: opname = "sqxtn?"; break;
}
} else {
switch (opcode) {
case 3: opname = "usqadd"; break;
case 7: opname = "sqneg"; break;
case 8: opname = "cmge"; zero = true; break;
case 9: opname = "cmle"; zero = true; break;
case 0xB: opname = "neg"; break;
case 0x12: opname = "sqxtun?"; break;
case 0x14: opname = "uqxtn?"; break;
}
}
}
if (opname) {
if (sign_suffix) {
char sign = U ? 'u' : 's';
snprintf(instr->text, sizeof(instr->text), "%s%c %c%d, %c%d", opname, sign, r, Rd, r, Rn);
} else if (sign_prefix) {
char sign = U ? 'u' : 's';
snprintf(instr->text, sizeof(instr->text), "%c%s %c%d, %c%d", sign, opname, r, Rd, r, Rn);
} else if (!zero) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", opname, r, Rd, r, Rn);
} else if (zero) {
snprintf(instr->text, sizeof(instr->text), "%s %c%d, #0.0", opname, r, Rd);
}
} else {
snprintf(instr->text, sizeof(instr->text), "(asimd scalar two-reg misc %08x)", w);
}
} else if (((w >> 17) & 0xf) == 8) {
snprintf(instr->text, sizeof(instr->text), "(asimd scalar pair-wise %08x)", w);
} else {
snprintf(instr->text, sizeof(instr->text), "(asimd scalar stuff %08x)", w);
}
} else {
snprintf(instr->text, sizeof(instr->text), "(asimd stuff %08x)", w);
}
} else if (((w >> 21) & 0x2F1) == 0x2F0) {
// many lines
snprintf(instr->text, sizeof(instr->text), "(asimd stuff %08x)", w);
} else {
snprintf(instr->text, sizeof(instr->text), "(FP2 %08x)", w);
}
}
static void DisassembleInstruction(uint32_t w, uint64_t addr, Instruction *instr, SymbolCallback symbolCallback) {
memset(instr, 0, sizeof(*instr));
// Identify the main encoding groups. See C3.1 A64 instruction index by encoding
int id = (w >> 25) & 0xF;
switch (id) {
case 0: case 1: case 2: case 3: // 00xx
instr->undefined = true;
break;
case 8: case 9:
DataProcessingImmediate(w, addr, instr);
break;
case 0xA: case 0xB:
BranchExceptionAndSystem(w, addr, instr, symbolCallback);
break;
case 4: case 6: case 0xC: case 0xE:
LoadStore(w, addr, instr);
break;
case 5: case 0xD:
DataProcessingRegister(w, addr, instr);
break;
case 7:
FPandASIMD1(w, addr, instr);
break;
case 0xF:
FPandASIMD2(w, addr, instr);
break;
}
}
void Arm64Dis(uint64_t addr, uint32_t w, char *output, int bufsize, bool includeWord, SymbolCallback symbolCallback) {
Instruction instr;
DisassembleInstruction(w, addr, &instr, symbolCallback);
char temp[256];
if (includeWord) {
snprintf(output, bufsize, "%08x\t%s", w, instr.text);
} else {
snprintf(output, bufsize, "%s", instr.text);
}
if (instr.undefined || instr.badbits || instr.oddbits) {
if (instr.undefined) snprintf(output, bufsize, "%08x\t[undefined instr]", w);
if (instr.badbits) snprintf(output, bufsize, "%08x\t[illegal bits]", w);
// strcat(output, " ? (extra bits)");
if (instr.oddbits) {
snprintf(temp, sizeof(temp), " [unexpected bits %08x]", w);
strcat(output, temp);
}
}
// zap tabs
while (*output) {
if (*output == '\t')
*output = ' ';
output++;
}
}