mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-12-18 02:48:28 +00:00
1097 lines
38 KiB
C++
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++;
|
|
}
|
|
}
|