mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
Rewrites most of DisArm64.cpp. Previously missing: test-and-branch, exceptions other than brk, hints and barriers, adc/sbc, conditional compare, extr, literal loads, exclusives, unscaled and FP register offset loads, ldpsw, SIMD structure loads/stores, scalar pairwise, fixed-point conversions, fccmp, most two-reg misc ops, modified immediates, smov/umov/dup from GPR, and the scalar shift forms. Fixed: sp printed as x31, unsigned load offsets sign-extended, ldrsb and friends shown as stores, adrp ignoring the page, double precision 1-source ops shown as single, fcmpe shown as fcmp, smulh/umulh, the bitmask decode for 64-bit elements, and fpcr shown as fpsr. Common aliases are used now (cset, mul, tst, cmn, lsl/asr/ubfx, sxtw...). TestArm64Disasm runs two examples of every emitter function through it. The expected strings were verified with Tools/arm64_disasm_check.py, which compares all 833 instruction words against llvm-mc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1717 lines
52 KiB
C++
1717 lines
52 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.
|
|
|
|
// Made to debug JIT code: it covers everything Common/Arm64Emitter can emit (unittest/TestArm64Disasm.cpp
|
|
// checks that), and not much beyond.
|
|
//
|
|
// Vector instructions use a compact syntax: the element size goes on the mnemonic and the register
|
|
// is q or d by width, so "fadd.32 q1, q2, q3" is "fadd v1.4s, v2.4s, v3.4s". Widening and narrowing
|
|
// ops list the destination element size first ("xtn.32.64 d0, q3").
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "Common/Arm64Emitter.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Util/DisArm64.h"
|
|
|
|
struct Instruction {
|
|
char text[128];
|
|
bool undefined;
|
|
};
|
|
|
|
static void Out(Instruction *instr, const char *fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsnprintf(instr->text, sizeof(instr->text), fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
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
|
|
"nv", // Also always
|
|
};
|
|
|
|
static int SignExtend(uint32_t value, int bits) {
|
|
return (int)(value << (32 - bits)) >> (32 - bits);
|
|
}
|
|
|
|
static int HighestSetBit(int value) {
|
|
int highest = 0;
|
|
for (int i = 0; i < 32; i++) {
|
|
if (value & (1 << i))
|
|
highest = i;
|
|
}
|
|
return highest;
|
|
}
|
|
|
|
static uint64_t Ones(int len) {
|
|
if (len >= 64) {
|
|
return 0xFFFFFFFFFFFFFFFFULL;
|
|
}
|
|
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) {
|
|
if (amount == 0)
|
|
return value & Ones(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 = (int)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);
|
|
uint64_t welem = Ones(S + 1);
|
|
uint64_t telem = Ones(d + 1);
|
|
if (wmask) {
|
|
uint64_t rotated = ROR(welem, R, esize);
|
|
*wmask = Replicate(rotated, esize);
|
|
}
|
|
if (tmask) {
|
|
*tmask = Replicate(telem, esize);
|
|
}
|
|
}
|
|
|
|
// General purpose register names. Register 31 is the zero register, or the stack pointer where the
|
|
// instruction says so.
|
|
static const char *GPR(bool sf, int r, bool sp = false) {
|
|
static const struct Names {
|
|
char n[4][32][5];
|
|
Names() {
|
|
for (int i = 0; i < 31; i++) {
|
|
for (int j = 0; j < 4; j++)
|
|
snprintf(n[j][i], sizeof(n[j][i]), "%c%d", (j & 1) ? 'x' : 'w', i);
|
|
}
|
|
strcpy(n[0][31], "wzr");
|
|
strcpy(n[1][31], "xzr");
|
|
strcpy(n[2][31], "wsp");
|
|
strcpy(n[3][31], "sp");
|
|
}
|
|
} names;
|
|
return names.n[(sp ? 2 : 0) | (sf ? 1 : 0)][r & 31];
|
|
}
|
|
|
|
static const char *X(int r) { return GPR(true, r); }
|
|
static const char *XSP(int r) { return GPR(true, r, true); }
|
|
static const char *W(int r) { return GPR(false, r); }
|
|
|
|
static void FormatTarget(char *buf, size_t size, uint64_t target, SymbolCallback symbolCallback) {
|
|
if (!symbolCallback || !symbolCallback(buf, (int)size, (uint8_t *)(uintptr_t)target)) {
|
|
snprintf(buf, size, "%04x%08x", (uint32_t)(target >> 32), (uint32_t)(target & 0xFFFFFFFF));
|
|
}
|
|
}
|
|
|
|
static void DataProcessingImmediate(uint32_t w, uint64_t addr, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
bool sf = (w >> 31) & 1;
|
|
int op = (w >> 23) & 7;
|
|
int size = sf ? 64 : 32;
|
|
switch (op) {
|
|
case 0: case 1: {
|
|
// Address generation relative to PC
|
|
int imm = SignExtend((((w >> 5) & 0x7FFFF) << 2) | ((w >> 29) & 3), 21);
|
|
uint64_t target;
|
|
if (sf) {
|
|
target = (addr & ~0xFFFULL) + ((int64_t)imm << 12);
|
|
} else {
|
|
target = addr + imm;
|
|
}
|
|
Out(instr, "%s %s, #0x%04x%08x", sf ? "adrp" : "adr", X(Rd), (uint32_t)(target >> 32), (uint32_t)(target & 0xFFFFFFFF));
|
|
break;
|
|
}
|
|
case 2: {
|
|
// Add/subtract immediate value
|
|
bool sub = (w >> 30) & 1;
|
|
bool S = (w >> 29) & 1;
|
|
int imm = (w >> 10) & 0xFFF;
|
|
bool shift = (w >> 22) & 1;
|
|
char immstr[32];
|
|
snprintf(immstr, sizeof(immstr), shift ? "#%d, lsl #12" : "#%d", imm);
|
|
if (S && Rd == 31) {
|
|
Out(instr, "%s %s, %s", sub ? "cmp" : "cmn", GPR(sf, Rn, true), immstr);
|
|
} else if (!sub && !S && !shift && imm == 0 && (Rd == 31 || Rn == 31)) {
|
|
Out(instr, "mov %s, %s", GPR(sf, Rd, true), GPR(sf, Rn, true));
|
|
} else {
|
|
Out(instr, "%s%s %s, %s, %s", sub ? "sub" : "add", S ? "s" : "", GPR(sf, Rd, !S), GPR(sf, Rn, true), immstr);
|
|
}
|
|
break;
|
|
}
|
|
case 4: {
|
|
// Logical immediate
|
|
int opc = (w >> 29) & 3;
|
|
int N = (w >> 22) & 1;
|
|
int immr = (w >> 16) & 0x3f;
|
|
int imms = (w >> 10) & 0x3f;
|
|
if (!sf && N) {
|
|
Out(instr, "(DPI %08x)", w);
|
|
break;
|
|
}
|
|
static const char * const opnames[4] = { "and", "orr", "eor", "ands" };
|
|
uint64_t wmask;
|
|
DecodeBitMasks(N, imms, immr, nullptr, &wmask);
|
|
if (!sf)
|
|
wmask &= 0xFFFFFFFF;
|
|
char immstr[32];
|
|
if (wmask >> 32)
|
|
snprintf(immstr, sizeof(immstr), "#0x%x%08x", (uint32_t)(wmask >> 32), (uint32_t)wmask);
|
|
else
|
|
snprintf(immstr, sizeof(immstr), "#0x%x", (uint32_t)wmask);
|
|
if (opc == 3 && Rd == 31) {
|
|
Out(instr, "tst %s, %s", GPR(sf, Rn), immstr);
|
|
} else {
|
|
Out(instr, "%s %s, %s, %s", opnames[opc], GPR(sf, Rd, opc != 3), GPR(sf, Rn), immstr);
|
|
}
|
|
break;
|
|
}
|
|
case 5: {
|
|
// Move wide
|
|
int imm16 = (w >> 5) & 0xFFFF;
|
|
int opc = (w >> 29) & 3;
|
|
int hw = (w >> 21) & 3;
|
|
static const char * const opnames[4] = { "movn", nullptr, "movz", "movk" };
|
|
if (!opnames[opc] || (!sf && hw >= 2)) {
|
|
Out(instr, "(DPI %08x)", w);
|
|
break;
|
|
}
|
|
Out(instr, "%s %s, #0x%04x << %d", opnames[opc], GPR(sf, Rd), imm16, hw * 16);
|
|
break;
|
|
}
|
|
case 6: {
|
|
// Bitfield
|
|
int opc = (w >> 29) & 3;
|
|
int immr = (w >> 16) & 0x3f;
|
|
int imms = (w >> 10) & 0x3f;
|
|
const char *d = GPR(sf, Rd);
|
|
const char *n = GPR(sf, Rn);
|
|
if (opc == 0) {
|
|
if (imms == size - 1) {
|
|
Out(instr, "asr %s, %s, #%d", d, n, immr);
|
|
} else if (immr == 0 && (imms == 7 || imms == 15 || (sf && imms == 31))) {
|
|
Out(instr, "sxt%c %s, %s", imms == 7 ? 'b' : (imms == 15 ? 'h' : 'w'), d, W(Rn));
|
|
} else if (imms < immr) {
|
|
Out(instr, "sbfiz %s, %s, #%d, #%d", d, n, size - immr, imms + 1);
|
|
} else {
|
|
Out(instr, "sbfx %s, %s, #%d, #%d", d, n, immr, imms - immr + 1);
|
|
}
|
|
} else if (opc == 1) {
|
|
if (imms < immr) {
|
|
Out(instr, "bfi %s, %s, #%d, #%d", d, n, size - immr, imms + 1);
|
|
} else {
|
|
Out(instr, "bfxil %s, %s, #%d, #%d", d, n, immr, imms - immr + 1);
|
|
}
|
|
} else if (opc == 2) {
|
|
if (imms != size - 1 && imms + 1 == immr) {
|
|
Out(instr, "lsl %s, %s, #%d", d, n, size - 1 - imms);
|
|
} else if (imms == size - 1) {
|
|
Out(instr, "lsr %s, %s, #%d", d, n, immr);
|
|
} else if (!sf && immr == 0 && (imms == 7 || imms == 15)) {
|
|
Out(instr, "uxt%c %s, %s", imms == 7 ? 'b' : 'h', d, n);
|
|
} else if (imms < immr) {
|
|
Out(instr, "ubfiz %s, %s, #%d, #%d", d, n, size - immr, imms + 1);
|
|
} else {
|
|
Out(instr, "ubfx %s, %s, #%d, #%d", d, n, immr, imms - immr + 1);
|
|
}
|
|
} else {
|
|
Out(instr, "(DPI %08x)", w);
|
|
}
|
|
break;
|
|
}
|
|
case 7: {
|
|
// Extract
|
|
int Rm = (w >> 16) & 0x1f;
|
|
int imms = (w >> 10) & 0x3f;
|
|
if (Rn == Rm) {
|
|
Out(instr, "ror %s, %s, #%d", GPR(sf, Rd), GPR(sf, Rn), imms);
|
|
} else {
|
|
Out(instr, "extr %s, %s, %s, #%d", GPR(sf, Rd), GPR(sf, Rn), GPR(sf, Rm), imms);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
Out(instr, "(DPI %08x)", w);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const char *GetSystemRegName(int op0, int op1, int CRn, int CRm, int op2) {
|
|
if (op0 == 3 && op1 == 3 && CRn == 4 && CRm == 2 && op2 == 0) {
|
|
return "nzcv";
|
|
} else if (op0 == 3 && op1 == 3 && CRn == 4 && CRm == 4 && op2 == 0) {
|
|
return "fpcr";
|
|
} else if (op0 == 3 && op1 == 3 && CRn == 4 && CRm == 4 && op2 == 1) {
|
|
return "fpsr";
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static void System(uint32_t w, Instruction *instr) {
|
|
int Rt = w & 0x1f;
|
|
bool L = (w >> 21) & 1;
|
|
int op0 = (w >> 19) & 3;
|
|
int op1 = (w >> 16) & 7;
|
|
int CRn = (w >> 12) & 0xF;
|
|
int CRm = (w >> 8) & 0xF;
|
|
int op2 = (w >> 5) & 0x7;
|
|
if (op0 >= 2) {
|
|
const char *sysreg = GetSystemRegName(op0, op1, CRn, CRm, op2);
|
|
char name[32];
|
|
if (sysreg) {
|
|
truncate_cpy(name, sysreg);
|
|
} else {
|
|
snprintf(name, sizeof(name), "s%d_%d_c%d_c%d_%d", op0, op1, CRn, CRm, op2);
|
|
}
|
|
if (L) {
|
|
Out(instr, "mrs %s, %s", X(Rt), name);
|
|
} else {
|
|
Out(instr, "msr %s, %s", name, X(Rt));
|
|
}
|
|
return;
|
|
}
|
|
if (L || op0 != 0) {
|
|
Out(instr, "(sys %08x)", w);
|
|
return;
|
|
}
|
|
if (CRn == 2 && Rt == 31) {
|
|
static const char * const hints[6] = { "nop", "yield", "wfe", "wfi", "sev", "sevl" };
|
|
int imm = (CRm << 3) | op2;
|
|
if (imm < 6) {
|
|
Out(instr, "%s", hints[imm]);
|
|
} else {
|
|
Out(instr, "hint #%d", imm);
|
|
}
|
|
} else if (CRn == 3 && op1 == 3 && Rt == 31) {
|
|
static const char * const barriers[16] = {
|
|
nullptr, "oshld", "oshst", "osh", nullptr, "nshld", "nshst", "nsh",
|
|
nullptr, "ishld", "ishst", "ish", nullptr, "ld", "st", "sy",
|
|
};
|
|
if (op2 == 2) {
|
|
if (CRm == 15)
|
|
Out(instr, "clrex");
|
|
else
|
|
Out(instr, "clrex #%d", CRm);
|
|
} else if ((op2 == 4 || op2 == 5) && barriers[CRm]) {
|
|
Out(instr, "%s %s", op2 == 4 ? "dsb" : "dmb", barriers[CRm]);
|
|
} else if (op2 == 6) {
|
|
if (CRm == 15)
|
|
Out(instr, "isb");
|
|
else
|
|
Out(instr, "isb #%d", CRm);
|
|
} else {
|
|
Out(instr, "(barrier %08x)", w);
|
|
}
|
|
} else if (CRn == 4 && Rt == 31) {
|
|
const char *field = nullptr;
|
|
if (op1 == 0 && op2 == 5)
|
|
field = "spsel";
|
|
else if (op1 == 3 && op2 == 6)
|
|
field = "daifset";
|
|
else if (op1 == 3 && op2 == 7)
|
|
field = "daifclr";
|
|
if (field)
|
|
Out(instr, "msr %s, #%d", field, CRm);
|
|
else
|
|
Out(instr, "(msr pstate %08x)", w);
|
|
} else {
|
|
Out(instr, "(sys %08x)", w);
|
|
}
|
|
}
|
|
|
|
static void BranchExceptionAndSystem(uint32_t w, uint64_t addr, Instruction *instr, SymbolCallback symbolCallback) {
|
|
char target[128];
|
|
int Rt = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
if (((w >> 26) & 0x1F) == 5) {
|
|
// Unconditional branch / branch+link
|
|
FormatTarget(target, sizeof(target), addr + (int64_t)(SignExtend(w & 0x3FFFFFF, 26) << 2), symbolCallback);
|
|
Out(instr, "b%s %s", (w >> 31) ? "l" : "", target);
|
|
} else if (((w >> 25) & 0x3F) == 0x1A) {
|
|
// Compare and branch
|
|
FormatTarget(target, sizeof(target), addr + (int64_t)(SignExtend((w >> 5) & 0x7FFFF, 19) << 2), symbolCallback);
|
|
Out(instr, "%s %s, %s", ((w >> 24) & 1) ? "cbnz" : "cbz", GPR(w >> 31, Rt), target);
|
|
} else if (((w >> 25) & 0x3F) == 0x1B) {
|
|
// Test and branch
|
|
int bit = ((w >> 26) & 0x20) | ((w >> 19) & 0x1F);
|
|
FormatTarget(target, sizeof(target), addr + (int64_t)(SignExtend((w >> 5) & 0x3FFF, 14) << 2), symbolCallback);
|
|
Out(instr, "%s %s, #%d, %s", ((w >> 24) & 1) ? "tbnz" : "tbz", GPR(bit >= 32, Rt), bit, target);
|
|
} else if (((w >> 24) & 0xFF) == 0x54 && ((w >> 4) & 1) == 0) {
|
|
// Conditional branch
|
|
FormatTarget(target, sizeof(target), addr + (int64_t)(SignExtend((w >> 5) & 0x7FFFF, 19) << 2), symbolCallback);
|
|
Out(instr, "b.%s %s", condnames[w & 0xF], target);
|
|
} else if ((w >> 24) == 0xD4) {
|
|
// Exception generation
|
|
int opc = (w >> 21) & 7;
|
|
int imm = (w >> 5) & 0xFFFF;
|
|
int ll = w & 3;
|
|
const char *name = nullptr;
|
|
if (opc == 0 && ll != 0) {
|
|
static const char * const names[4] = { nullptr, "svc", "hvc", "smc" };
|
|
name = names[ll];
|
|
} else if (opc == 1 && ll == 0) {
|
|
name = "brk";
|
|
} else if (opc == 2 && ll == 0) {
|
|
name = "hlt";
|
|
} else if (opc == 5 && ll != 0) {
|
|
static const char * const names[4] = { nullptr, "dcps1", "dcps2", "dcps3" };
|
|
name = names[ll];
|
|
if (imm == 0) {
|
|
Out(instr, "%s", name);
|
|
return;
|
|
}
|
|
}
|
|
if (name && ((w >> 2) & 7) == 0)
|
|
Out(instr, "%s #%d", name, imm);
|
|
else
|
|
Out(instr, "(exception-gen %08x)", w);
|
|
} else if (((w >> 22) & 0x3FF) == 0x354) {
|
|
System(w, instr);
|
|
} else if (((w >> 25) & 0x7F) == 0x6B) {
|
|
// Unconditional branch (register)
|
|
int opc = (w >> 21) & 0xF;
|
|
if (((w >> 10) & 0x7FF) != 0x7C0 || Rt != 0) {
|
|
Out(instr, "(BRX ?? %08x)", w);
|
|
} else if (opc == 0) {
|
|
Out(instr, "br %s", X(Rn));
|
|
} else if (opc == 1) {
|
|
Out(instr, "blr %s", X(Rn));
|
|
} else if (opc == 2) {
|
|
if (Rn == 30)
|
|
Out(instr, "ret");
|
|
else
|
|
Out(instr, "ret %s", X(Rn));
|
|
} else if (opc == 4 && Rn == 31) {
|
|
Out(instr, "eret");
|
|
} else if (opc == 5 && Rn == 31) {
|
|
Out(instr, "drps");
|
|
} else {
|
|
Out(instr, "(BRX ?? %08x)", w);
|
|
}
|
|
} else {
|
|
Out(instr, "(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 PrefetchName(char *buf, size_t size, int Rt) {
|
|
static const char * const types[4] = { "pld", "pli", "pst", nullptr };
|
|
if (types[Rt >> 3] && ((Rt >> 1) & 3) != 3) {
|
|
snprintf(buf, size, "%sl%d%s", types[Rt >> 3], ((Rt >> 1) & 3) + 1, (Rt & 1) ? "strm" : "keep");
|
|
} else {
|
|
snprintf(buf, size, "#%d", Rt);
|
|
}
|
|
}
|
|
|
|
// Loads and stores of a single register, both integer and FP/SIMD.
|
|
static void LoadStoreRegister(uint32_t w, uint64_t addr, Instruction *instr) {
|
|
int size = w >> 30;
|
|
bool V = (w >> 26) & 1;
|
|
int opc = (w >> 22) & 3;
|
|
int Rt = w & 0x1F;
|
|
int Rn = (w >> 5) & 0x1F;
|
|
int Rm = (w >> 16) & 0x1F;
|
|
bool unsignedOffset = (w >> 24) & 1;
|
|
int type = (w >> 10) & 3;
|
|
bool regOffset = !unsignedOffset && ((w >> 21) & 1);
|
|
|
|
if (regOffset && type != 2) {
|
|
Out(instr, "(LS atomic %08x)", w);
|
|
return;
|
|
}
|
|
if (!unsignedOffset && !regOffset && type == 2) {
|
|
Out(instr, "(LS unprivileged %08x)", w);
|
|
return;
|
|
}
|
|
|
|
// Build the mnemonic and the register.
|
|
char name[16];
|
|
char reg[16];
|
|
int scale = size;
|
|
bool unscaled = !unsignedOffset && !regOffset && type == 0;
|
|
if (V) {
|
|
if (opc & 2) {
|
|
if (size != 0) {
|
|
Out(instr, "(LS %08x)", w);
|
|
return;
|
|
}
|
|
scale = 4;
|
|
}
|
|
snprintf(name, sizeof(name), "%s%s", (opc & 1) ? "ld" : "st", unscaled ? "ur" : "r");
|
|
snprintf(reg, sizeof(reg), "%c%d", "bhsdq"[scale], Rt);
|
|
} else {
|
|
static const char * const sizeSuffix[4] = { "b", "h", "", "" };
|
|
const char *r = unscaled ? "ur" : "r";
|
|
if (opc == 2 && size == 3) {
|
|
snprintf(name, sizeof(name), "prf%s", unscaled ? "um" : "m");
|
|
PrefetchName(reg, sizeof(reg), Rt);
|
|
} else if (opc >= 2) {
|
|
if (size == 3 || (size == 2 && opc == 3)) {
|
|
Out(instr, "(LS %08x)", w);
|
|
return;
|
|
}
|
|
snprintf(name, sizeof(name), "ld%ss%s", r, size == 2 ? "w" : sizeSuffix[size]);
|
|
truncate_cpy(reg, GPR(opc == 2, Rt));
|
|
} else {
|
|
snprintf(name, sizeof(name), "%s%s%s", opc ? "ld" : "st", r, sizeSuffix[size]);
|
|
truncate_cpy(reg, GPR(size == 3, Rt));
|
|
}
|
|
}
|
|
|
|
if (unsignedOffset) {
|
|
int imm = ((w >> 10) & 0xFFF) << scale;
|
|
if (imm)
|
|
Out(instr, "%s %s, [%s, #%d]", name, reg, XSP(Rn), imm);
|
|
else
|
|
Out(instr, "%s %s, [%s]", name, reg, XSP(Rn));
|
|
} else if (regOffset) {
|
|
int option = (w >> 13) & 7;
|
|
bool S = (w >> 12) & 1;
|
|
if (!(option & 2)) {
|
|
Out(instr, "(LS %08x)", w);
|
|
return;
|
|
}
|
|
const char *index = GPR(option & 1, Rm);
|
|
if (option == 3) {
|
|
if (S)
|
|
Out(instr, "%s %s, [%s, %s, lsl #%d]", name, reg, XSP(Rn), index, scale);
|
|
else
|
|
Out(instr, "%s %s, [%s, %s]", name, reg, XSP(Rn), index);
|
|
} else {
|
|
if (S)
|
|
Out(instr, "%s %s, [%s, %s, %s #%d]", name, reg, XSP(Rn), index, extendnames[option], scale);
|
|
else
|
|
Out(instr, "%s %s, [%s, %s, %s]", name, reg, XSP(Rn), index, extendnames[option]);
|
|
}
|
|
} else {
|
|
int imm9 = SignExtend((w >> 12) & 0x1FF, 9);
|
|
if (type == 1) {
|
|
Out(instr, "%s %s, [%s], #%d", name, reg, XSP(Rn), imm9);
|
|
} else if (type == 3) {
|
|
Out(instr, "%s %s, [%s, #%d]!", name, reg, XSP(Rn), imm9);
|
|
} else if (imm9) {
|
|
Out(instr, "%s %s, [%s, #%d]", name, reg, XSP(Rn), imm9);
|
|
} else {
|
|
Out(instr, "%s %s, [%s]", name, reg, XSP(Rn));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LoadStorePair(uint32_t w, Instruction *instr) {
|
|
int opc = w >> 30;
|
|
bool V = (w >> 26) & 1;
|
|
int indexType = (w >> 23) & 3;
|
|
bool L = (w >> 22) & 1;
|
|
int Rt = w & 0x1F;
|
|
int Rn = (w >> 5) & 0x1F;
|
|
int Rt2 = (w >> 10) & 0x1F;
|
|
int imm7 = SignExtend((w >> 15) & 0x7F, 7);
|
|
|
|
const char *name = L ? "ldp" : "stp";
|
|
char r;
|
|
int scale;
|
|
if (V) {
|
|
if (opc == 3) {
|
|
Out(instr, "(LS pair %08x)", w);
|
|
return;
|
|
}
|
|
r = "sdq"[opc];
|
|
scale = 2 + opc;
|
|
} else if (opc == 0) {
|
|
r = 'w';
|
|
scale = 2;
|
|
} else if (opc == 1 && L && indexType != 0) {
|
|
name = "ldpsw";
|
|
r = 'x';
|
|
scale = 2;
|
|
} else if (opc == 2) {
|
|
r = 'x';
|
|
scale = 3;
|
|
} else {
|
|
Out(instr, "(LS pair %08x)", w);
|
|
return;
|
|
}
|
|
if (indexType == 0)
|
|
name = L ? "ldnp" : "stnp";
|
|
|
|
char t1[8], t2[8];
|
|
if (r == 'w' || r == 'x') {
|
|
truncate_cpy(t1, GPR(r == 'x', Rt));
|
|
truncate_cpy(t2, GPR(r == 'x', Rt2));
|
|
} else {
|
|
snprintf(t1, sizeof(t1), "%c%d", r, Rt);
|
|
snprintf(t2, sizeof(t2), "%c%d", r, Rt2);
|
|
}
|
|
int offset = imm7 << scale;
|
|
if (indexType == 1) {
|
|
Out(instr, "%s %s, %s, [%s], #%d", name, t1, t2, XSP(Rn), offset);
|
|
} else if (indexType == 3) {
|
|
Out(instr, "%s %s, %s, [%s, #%d]!", name, t1, t2, XSP(Rn), offset);
|
|
} else if (offset) {
|
|
Out(instr, "%s %s, %s, [%s, #%d]", name, t1, t2, XSP(Rn), offset);
|
|
} else {
|
|
Out(instr, "%s %s, %s, [%s]", name, t1, t2, XSP(Rn));
|
|
}
|
|
}
|
|
|
|
static void LoadStoreExclusive(uint32_t w, Instruction *instr) {
|
|
int size = w >> 30;
|
|
bool o2 = (w >> 23) & 1;
|
|
bool L = (w >> 22) & 1;
|
|
bool o1 = (w >> 21) & 1;
|
|
int Rs = (w >> 16) & 0x1F;
|
|
bool o0 = (w >> 15) & 1;
|
|
int Rt2 = (w >> 10) & 0x1F;
|
|
int Rn = (w >> 5) & 0x1F;
|
|
int Rt = w & 0x1F;
|
|
|
|
static const char * const suffix[4] = { "b", "h", "", "" };
|
|
bool sf = size == 3;
|
|
if (!o2 && !o1) {
|
|
if (L)
|
|
Out(instr, "ld%sxr%s %s, [%s]", o0 ? "a" : "", suffix[size], GPR(sf, Rt), XSP(Rn));
|
|
else
|
|
Out(instr, "st%sxr%s %s, %s, [%s]", o0 ? "l" : "", suffix[size], W(Rs), GPR(sf, Rt), XSP(Rn));
|
|
} else if (!o2 && o1 && size >= 2) {
|
|
if (L)
|
|
Out(instr, "ld%sxp %s, %s, [%s]", o0 ? "a" : "", GPR(sf, Rt), GPR(sf, Rt2), XSP(Rn));
|
|
else
|
|
Out(instr, "st%sxp %s, %s, %s, [%s]", o0 ? "l" : "", W(Rs), GPR(sf, Rt), GPR(sf, Rt2), XSP(Rn));
|
|
} else if (o2 && !o1) {
|
|
if (L)
|
|
Out(instr, "%sr%s %s, [%s]", o0 ? "lda" : "ldla", suffix[size], GPR(sf, Rt), XSP(Rn));
|
|
else
|
|
Out(instr, "%sr%s %s, [%s]", o0 ? "stl" : "stll", suffix[size], GPR(sf, Rt), XSP(Rn));
|
|
} else {
|
|
Out(instr, "(LS exclusive %08x)", w);
|
|
}
|
|
}
|
|
|
|
static void LoadLiteral(uint32_t w, uint64_t addr, Instruction *instr) {
|
|
int opc = w >> 30;
|
|
bool V = (w >> 26) & 1;
|
|
int Rt = w & 0x1F;
|
|
char target[32];
|
|
FormatTarget(target, sizeof(target), addr + (int64_t)(SignExtend((w >> 5) & 0x7FFFF, 19) << 2), nullptr);
|
|
if (V) {
|
|
if (opc == 3)
|
|
Out(instr, "(LS literal %08x)", w);
|
|
else
|
|
Out(instr, "ldr %c%d, %s", "sdq"[opc], Rt, target);
|
|
} else if (opc == 3) {
|
|
char prfop[16];
|
|
PrefetchName(prfop, sizeof(prfop), Rt);
|
|
Out(instr, "prfm %s, %s", prfop, target);
|
|
} else {
|
|
Out(instr, "%s %s, %s", opc == 2 ? "ldrsw" : "ldr", GPR(opc != 0, Rt), target);
|
|
}
|
|
}
|
|
|
|
// Appends a list of count consecutive vector registers, like "{q1, q2}".
|
|
static void RegList(char *buf, size_t size, char r, int Rt, int count) {
|
|
int pos = snprintf(buf, size, "{");
|
|
for (int i = 0; i < count; i++) {
|
|
pos += snprintf(buf + pos, size - pos, "%s%c%d", i ? ", " : "", r, (Rt + i) & 31);
|
|
}
|
|
snprintf(buf + pos, size - pos, "}");
|
|
}
|
|
|
|
static void SIMDLoadStoreStructure(uint32_t w, Instruction *instr) {
|
|
bool Q = (w >> 30) & 1;
|
|
bool single = (w >> 24) & 1;
|
|
bool post = (w >> 23) & 1;
|
|
bool L = (w >> 22) & 1;
|
|
int Rm = (w >> 16) & 0x1F;
|
|
int opcode = (w >> 12) & 0xF;
|
|
int size = (w >> 10) & 3;
|
|
int Rn = (w >> 5) & 0x1F;
|
|
int Rt = w & 0x1F;
|
|
char r = Q ? 'q' : 'd';
|
|
char regs[48];
|
|
char addrstr[32];
|
|
|
|
if (!post && Rm != 0) {
|
|
Out(instr, "(LS simd %08x)", w);
|
|
return;
|
|
}
|
|
|
|
int bytes;
|
|
if (!single) {
|
|
static const int regCount[16] = { 4, 0, 4, 0, 3, 0, 3, 1, 2, 0, 2, 0, 0, 0, 0, 0 };
|
|
static const int selem[16] = { 4, 0, 1, 0, 3, 0, 1, 1, 2, 0, 1, 0, 0, 0, 0, 0 };
|
|
int count = regCount[opcode];
|
|
if (count == 0 || ((w >> 21) & 1)) {
|
|
Out(instr, "(LS simd %08x)", w);
|
|
return;
|
|
}
|
|
RegList(regs, sizeof(regs), r, Rt, count);
|
|
bytes = count * (Q ? 16 : 8);
|
|
if (post) {
|
|
if (Rm == 31)
|
|
snprintf(addrstr, sizeof(addrstr), "[%s], #%d", XSP(Rn), bytes);
|
|
else
|
|
snprintf(addrstr, sizeof(addrstr), "[%s], %s", XSP(Rn), X(Rm));
|
|
} else {
|
|
snprintf(addrstr, sizeof(addrstr), "[%s]", XSP(Rn));
|
|
}
|
|
Out(instr, "%s%d.%d %s, %s", L ? "ld" : "st", selem[opcode], 8 << size, regs, addrstr);
|
|
return;
|
|
}
|
|
|
|
bool R = (w >> 21) & 1;
|
|
bool S = (w >> 12) & 1;
|
|
int op = (w >> 13) & 7;
|
|
int count = (((op & 1) << 1) | R) + 1;
|
|
if (op >= 6) {
|
|
// Load and replicate.
|
|
if (!L || S) {
|
|
Out(instr, "(LS simd %08x)", w);
|
|
return;
|
|
}
|
|
RegList(regs, sizeof(regs), r, Rt, count);
|
|
bytes = count << size;
|
|
if (post) {
|
|
if (Rm == 31)
|
|
snprintf(addrstr, sizeof(addrstr), "[%s], #%d", XSP(Rn), bytes);
|
|
else
|
|
snprintf(addrstr, sizeof(addrstr), "[%s], %s", XSP(Rn), X(Rm));
|
|
} else {
|
|
snprintf(addrstr, sizeof(addrstr), "[%s]", XSP(Rn));
|
|
}
|
|
Out(instr, "ld%dr.%d %s, %s", count, 8 << size, regs, addrstr);
|
|
return;
|
|
}
|
|
|
|
// Single lane.
|
|
int index;
|
|
int esize;
|
|
switch (op >> 1) {
|
|
case 0:
|
|
esize = 0;
|
|
index = (Q << 3) | (S << 2) | size;
|
|
break;
|
|
case 1:
|
|
if (size & 1) {
|
|
Out(instr, "(LS simd %08x)", w);
|
|
return;
|
|
}
|
|
esize = 1;
|
|
index = (Q << 2) | (S << 1) | (size >> 1);
|
|
break;
|
|
default:
|
|
if (size == 0) {
|
|
esize = 2;
|
|
index = (Q << 1) | S;
|
|
} else if (size == 1 && !S) {
|
|
esize = 3;
|
|
index = Q;
|
|
} else {
|
|
Out(instr, "(LS simd %08x)", w);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
bytes = count << esize;
|
|
if (count == 1) {
|
|
snprintf(regs, sizeof(regs), "q%d.%c[%d]", Rt, "bhsd"[esize], index);
|
|
} else {
|
|
char list[40];
|
|
RegList(list, sizeof(list), 'q', Rt, count);
|
|
snprintf(regs, sizeof(regs), "%s.%c[%d]", list, "bhsd"[esize], index);
|
|
}
|
|
if (post) {
|
|
if (Rm == 31)
|
|
snprintf(addrstr, sizeof(addrstr), "[%s], #%d", XSP(Rn), bytes);
|
|
else
|
|
snprintf(addrstr, sizeof(addrstr), "[%s], %s", XSP(Rn), X(Rm));
|
|
} else {
|
|
snprintf(addrstr, sizeof(addrstr), "[%s]", XSP(Rn));
|
|
}
|
|
Out(instr, "%s%d %s, %s", L ? "ld" : "st", count, regs, addrstr);
|
|
}
|
|
|
|
static void LoadStore(uint32_t w, uint64_t addr, Instruction *instr) {
|
|
if ((w & 0xBE000000) == 0x0C000000) {
|
|
// AdvSIMD load/store multiple / single structure
|
|
SIMDLoadStoreStructure(w, instr);
|
|
} else if (((w >> 24) & 0x3F) == 0x08) {
|
|
LoadStoreExclusive(w, instr);
|
|
} else if (((w >> 27) & 7) == 3 && ((w >> 24) & 3) == 0) {
|
|
LoadLiteral(w, addr, instr);
|
|
} else if (((w >> 27) & 7) == 5) {
|
|
LoadStorePair(w, instr);
|
|
} else if (((w >> 27) & 7) == 7) {
|
|
LoadStoreRegister(w, addr, instr);
|
|
} else {
|
|
Out(instr, "(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;
|
|
bool sf = (w >> 31) & 1;
|
|
const char *d = GPR(sf, Rd);
|
|
const char *n = GPR(sf, Rn);
|
|
const char *m = GPR(sf, Rm);
|
|
|
|
if (((w >> 28) & 1) == 0) {
|
|
int shift = (w >> 22) & 0x3;
|
|
int imm6 = (w >> 10) & 0x3f;
|
|
char shiftstr[16] = "";
|
|
if (shift || imm6)
|
|
snprintf(shiftstr, sizeof(shiftstr), ", %s #%d", shiftnames[shift], imm6);
|
|
|
|
if (((w >> 24) & 1) == 0) {
|
|
// Logical (shifted register)
|
|
int opc = (((w >> 29) & 3) << 1) | ((w >> 21) & 1);
|
|
static const char * const opnames[8] = { "and", "bic", "orr", "orn", "eor", "eon", "ands", "bics" };
|
|
if (opc == 2 && Rn == 31) {
|
|
Out(instr, "mov %s, %s%s", d, m, shiftstr);
|
|
} else if (opc == 3 && Rn == 31) {
|
|
Out(instr, "mvn %s, %s%s", d, m, shiftstr);
|
|
} else if (opc == 6 && Rd == 31) {
|
|
Out(instr, "tst %s, %s%s", n, m, shiftstr);
|
|
} else {
|
|
Out(instr, "%s %s, %s, %s%s", opnames[opc], d, n, m, shiftstr);
|
|
}
|
|
} else if (((w >> 21) & 1) == 0) {
|
|
// Add/subtract (shifted register)
|
|
bool sub = (w >> 30) & 1;
|
|
bool S = (w >> 29) & 1;
|
|
if (shift == 3) {
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else if (S && Rd == 31) {
|
|
Out(instr, "%s %s, %s%s", sub ? "cmp" : "cmn", n, m, shiftstr);
|
|
} else if (sub && Rn == 31) {
|
|
Out(instr, "neg%s %s, %s%s", S ? "s" : "", d, m, shiftstr);
|
|
} else {
|
|
Out(instr, "%s%s %s, %s, %s%s", sub ? "sub" : "add", S ? "s" : "", d, n, m, shiftstr);
|
|
}
|
|
} else {
|
|
// Add/subtract (extended register)
|
|
bool sub = (w >> 30) & 1;
|
|
bool S = (w >> 29) & 1;
|
|
int option = (w >> 13) & 0x7;
|
|
int imm3 = (w >> 10) & 0x7;
|
|
const char *rm = GPR((option & 3) == 3, Rm);
|
|
char ext[16];
|
|
if ((Rd == 31 || Rn == 31) && option == (sf ? 3 : 2)) {
|
|
if (imm3)
|
|
snprintf(ext, sizeof(ext), ", lsl #%d", imm3);
|
|
else
|
|
ext[0] = '\0';
|
|
} else if (imm3) {
|
|
snprintf(ext, sizeof(ext), ", %s #%d", extendnames[option], imm3);
|
|
} else {
|
|
snprintf(ext, sizeof(ext), ", %s", extendnames[option]);
|
|
}
|
|
if (S && Rd == 31) {
|
|
Out(instr, "%s %s, %s%s", sub ? "cmp" : "cmn", GPR(sf, Rn, true), rm, ext);
|
|
} else {
|
|
Out(instr, "%s%s %s, %s, %s%s", sub ? "sub" : "add", S ? "s" : "", GPR(sf, Rd, !S), GPR(sf, Rn, true), rm, ext);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch ((w >> 21) & 0xF) {
|
|
case 0x0: {
|
|
// Add/subtract with carry
|
|
bool sub = (w >> 30) & 1;
|
|
bool S = (w >> 29) & 1;
|
|
if (((w >> 10) & 0x3F) != 0) {
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else if (sub && Rn == 31) {
|
|
Out(instr, "ngc%s %s, %s", S ? "s" : "", d, m);
|
|
} else {
|
|
Out(instr, "%s%s %s, %s, %s", sub ? "sbc" : "adc", S ? "s" : "", d, n, m);
|
|
}
|
|
break;
|
|
}
|
|
case 0x2: {
|
|
// Conditional compare
|
|
const char *name = ((w >> 30) & 1) ? "ccmp" : "ccmn";
|
|
int cond = (w >> 12) & 0xF;
|
|
int nzcv = w & 0xF;
|
|
if (((w >> 29) & 1) == 0 || ((w >> 10) & 1) || ((w >> 4) & 1)) {
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else if ((w >> 11) & 1) {
|
|
Out(instr, "%s %s, #%d, #%d, %s", name, n, Rm, nzcv, condnames[cond]);
|
|
} else {
|
|
Out(instr, "%s %s, %s, #%d, %s", name, n, m, nzcv, condnames[cond]);
|
|
}
|
|
break;
|
|
}
|
|
case 0x4: {
|
|
// Conditional select
|
|
int op = (((w >> 30) & 1) << 1) | ((w >> 10) & 1);
|
|
int cond = (w >> 12) & 0xF;
|
|
static const char * const opnames[4] = { "csel", "csinc", "csinv", "csneg" };
|
|
if (((w >> 29) & 1) || ((w >> 11) & 1)) {
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else if ((op == 1 || op == 2) && Rn == 31 && Rm == 31 && cond < 14) {
|
|
Out(instr, "%s %s, %s", op == 1 ? "cset" : "csetm", d, condnames[cond ^ 1]);
|
|
} else if (op != 0 && Rn == Rm && Rn != 31 && cond < 14) {
|
|
static const char * const aliases[4] = { nullptr, "cinc", "cinv", "cneg" };
|
|
Out(instr, "%s %s, %s, %s", aliases[op], d, n, condnames[cond ^ 1]);
|
|
} else {
|
|
Out(instr, "%s %s, %s, %s, %s", opnames[op], d, n, m, condnames[cond]);
|
|
}
|
|
break;
|
|
}
|
|
case 0x6: {
|
|
int opcode = (w >> 10) & 0x3F;
|
|
if ((w >> 30) & 1) {
|
|
// Data-processing (1 source)
|
|
static const char * const opnames[6] = { "rbit", "rev16", "rev32", "rev", "clz", "cls" };
|
|
const char *name = opcode < 6 ? opnames[opcode] : nullptr;
|
|
if (opcode == 2 && !sf)
|
|
name = "rev";
|
|
else if (opcode == 3 && !sf)
|
|
name = nullptr;
|
|
if (name && Rm == 0 && ((w >> 29) & 1) == 0)
|
|
Out(instr, "%s %s, %s", name, d, n);
|
|
else
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else {
|
|
// Data-processing (2 source)
|
|
static const char * const opnames[16] = {
|
|
nullptr, nullptr, "udiv", "sdiv", nullptr, nullptr, nullptr, nullptr,
|
|
"lslv", "lsrv", "asrv", "rorv", nullptr, nullptr, nullptr, nullptr,
|
|
};
|
|
if ((opcode & 0x38) == 0x10) {
|
|
// CRC32
|
|
int sz = opcode & 3;
|
|
bool C = (opcode >> 2) & 1;
|
|
if (sf != (sz == 3)) {
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else {
|
|
Out(instr, "crc32%s%c %s, %s, %s", C ? "c" : "", "bhwx"[sz], W(Rd), W(Rn), GPR(sz == 3, Rm));
|
|
}
|
|
} else if (opcode < 16 && opnames[opcode]) {
|
|
Out(instr, "%s %s, %s, %s", opnames[opcode], d, n, m);
|
|
} else {
|
|
Out(instr, "(DPR %08x)", w);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
if ((w >> 24) & 1) {
|
|
// Data processing - 3 source
|
|
int op31 = (w >> 21) & 0x7;
|
|
bool o0 = (w >> 15) & 1;
|
|
int Ra = (w >> 10) & 0x1f;
|
|
if (((w >> 29) & 3) != 0) {
|
|
Out(instr, "(DPR %08x)", w);
|
|
} else if (op31 == 0) {
|
|
if (Ra == 31)
|
|
Out(instr, "%s %s, %s, %s", o0 ? "mneg" : "mul", d, n, m);
|
|
else
|
|
Out(instr, "%s %s, %s, %s, %s", o0 ? "msub" : "madd", d, n, m, GPR(sf, Ra));
|
|
} else if (sf && (op31 == 1 || op31 == 5)) {
|
|
char sign = op31 == 5 ? 'u' : 's';
|
|
if (Ra == 31)
|
|
Out(instr, "%c%s %s, %s, %s", sign, o0 ? "mnegl" : "mull", d, W(Rn), W(Rm));
|
|
else
|
|
Out(instr, "%c%s %s, %s, %s, %s", sign, o0 ? "msubl" : "maddl", d, W(Rn), W(Rm), X(Ra));
|
|
} else if (sf && (op31 == 2 || op31 == 6) && !o0) {
|
|
Out(instr, "%cmulh %s, %s, %s", op31 == 6 ? 'u' : 's', d, n, m);
|
|
} else {
|
|
Out(instr, "(DPR %08x)", w);
|
|
}
|
|
} else {
|
|
Out(instr, "(DPR %08x)", w);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline bool GetQ(uint32_t w) { return (w >> 30) & 1; }
|
|
inline bool GetU(uint32_t w) { return (w >> 29) & 1; }
|
|
static 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";
|
|
}
|
|
|
|
static void SIMDThreeSame(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
int Rm = (w >> 16) & 0x1f;
|
|
int opcode = (w >> 11) & 0x1f;
|
|
int size = (w >> 22) & 3;
|
|
bool U = GetU(w);
|
|
char r = GetQ(w) ? 'q' : 'd';
|
|
|
|
if (opcode == 3) {
|
|
static const char * const logical[2][4] = {
|
|
{ "and", "bic", "orr", "orn" },
|
|
{ "eor", "bsl", "bit", "bif" },
|
|
};
|
|
if (!U && size == 2 && Rn == Rm) {
|
|
Out(instr, "mov %c%d, %c%d", r, Rd, r, Rn);
|
|
} else {
|
|
Out(instr, "%s %c%d, %c%d, %c%d", logical[U][size], r, Rd, r, Rn, r, Rm);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const char *opname = nullptr;
|
|
int esize;
|
|
if (opcode < 0x18) {
|
|
static const char * const opnames[2][24] = {
|
|
{
|
|
"shadd", "sqadd", "srhadd", nullptr, "shsub", "sqsub", "cmgt", "cmge",
|
|
"sshl", "sqshl", "srshl", "sqrshl", "smax", "smin", "sabd", "saba",
|
|
"add", "cmtst", "mla", "mul", "smaxp", "sminp", "sqdmulh", "addp",
|
|
},
|
|
{
|
|
"uhadd", "uqadd", "urhadd", nullptr, "uhsub", "uqsub", "cmhi", "cmhs",
|
|
"ushl", "uqshl", "urshl", "uqrshl", "umax", "umin", "uabd", "uaba",
|
|
"sub", "cmeq", "mls", "pmul", "umaxp", "uminp", "sqrdmulh", nullptr,
|
|
},
|
|
};
|
|
opname = opnames[U][opcode];
|
|
esize = 8 << size;
|
|
} else {
|
|
// [U][size >> 1][opcode - 0x18]
|
|
static const char * const fpnames[2][2][8] = {
|
|
{
|
|
{ "fmaxnm", "fmla", "fadd", "fmulx", "fcmeq", nullptr, "fmax", "frecps" },
|
|
{ "fminnm", "fmls", "fsub", nullptr, nullptr, nullptr, "fmin", "frsqrts" },
|
|
},
|
|
{
|
|
{ "fmaxnmp", nullptr, "faddp", "fmul", "fcmge", "facge", "fmaxp", "fdiv" },
|
|
{ "fminnmp", nullptr, "fabd", nullptr, "fcmgt", "facgt", "fminp", nullptr },
|
|
},
|
|
};
|
|
opname = fpnames[U][size >> 1][opcode - 0x18];
|
|
esize = (size & 1) ? 64 : 32;
|
|
}
|
|
if (opname) {
|
|
Out(instr, "%s.%d %c%d, %c%d, %c%d", opname, esize, r, Rd, r, Rn, r, Rm);
|
|
} else {
|
|
Out(instr, "(asimd three-same %08x)", w);
|
|
}
|
|
}
|
|
|
|
static void SIMDTwoRegMisc(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
int opcode = (w >> 12) & 0x1F;
|
|
int size = (w >> 22) & 3;
|
|
bool Q = GetQ(w);
|
|
bool U = GetU(w);
|
|
char r = Q ? 'q' : 'd';
|
|
const char *two = Q ? "2" : "";
|
|
int esize = 8 << size;
|
|
int fsize = (size & 1) ? 64 : 32;
|
|
|
|
// Narrowing and widening ops.
|
|
switch (opcode) {
|
|
case 0x12: case 0x14: {
|
|
static const char * const names[2][2] = { { "xtn", "sqxtn" }, { "sqxtun", "uqxtn" } };
|
|
Out(instr, "%s%s.%d.%d %c%d, q%d", names[U][opcode == 0x14], two, esize, esize * 2, r, Rd, Rn);
|
|
return;
|
|
}
|
|
case 0x13:
|
|
if (U) {
|
|
Out(instr, "shll%s.%d.%d q%d, %c%d, #%d", two, esize * 2, esize, Rd, r, Rn, esize);
|
|
return;
|
|
}
|
|
break;
|
|
case 0x16:
|
|
Out(instr, "%s%s.%d.%d %c%d, q%d", U ? "fcvtxn" : "fcvtn", two, fsize / 2, fsize, r, Rd, Rn);
|
|
return;
|
|
case 0x17:
|
|
if (!U) {
|
|
Out(instr, "fcvtl%s.%d.%d q%d, %c%d", two, fsize, fsize / 2, Rd, r, Rn);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
const char *opname = nullptr;
|
|
const char *zero = nullptr;
|
|
bool fp = false;
|
|
bool nosize = false;
|
|
if (opcode < 0xC) {
|
|
static const char * const names[2][12] = {
|
|
{ "rev64", "rev16", "saddlp", "suqadd", "cls", "cnt", "sadalp", "sqabs", "cmgt", "cmeq", "cmlt", "abs" },
|
|
{ "rev32", nullptr, "uaddlp", "usqadd", "clz", nullptr, "uadalp", "sqneg", "cmge", "cmle", nullptr, "neg" },
|
|
};
|
|
opname = names[U][opcode];
|
|
if (opcode >= 8 && opcode <= 10)
|
|
zero = "#0";
|
|
if (U && opcode == 5) {
|
|
opname = size == 0 ? "not" : "rbit";
|
|
nosize = size == 0;
|
|
}
|
|
} else if (size & 2) {
|
|
// [U][opcode - 0xC]
|
|
static const char * const names[2][20] = {
|
|
{
|
|
"fcmgt", "fcmeq", "fcmlt", "fabs", nullptr, nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, "frintp", "frintz", "fcvtps", "fcvtzs",
|
|
"urecpe", "frecpe", nullptr, nullptr,
|
|
},
|
|
{
|
|
"fcmge", "fcmle", nullptr, "fneg", nullptr, nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, nullptr, "frinti", "fcvtpu", "fcvtzu",
|
|
"ursqrte", "frsqrte", nullptr, "fsqrt",
|
|
},
|
|
};
|
|
opname = names[U][opcode - 0xC];
|
|
fp = opcode != 0x1C;
|
|
if (opcode >= 0xC && opcode <= 0xE)
|
|
zero = "#0.0";
|
|
} else if (opcode >= 0x18) {
|
|
static const char * const names[2][8] = {
|
|
{ "frintn", "frintm", "fcvtns", "fcvtms", "fcvtas", "scvtf", nullptr, nullptr },
|
|
{ "frinta", "frintx", "fcvtnu", "fcvtmu", "fcvtau", "ucvtf", nullptr, nullptr },
|
|
};
|
|
opname = names[U][opcode - 0x18];
|
|
fp = true;
|
|
}
|
|
|
|
if (!opname) {
|
|
Out(instr, "(asimd vector two-reg misc %08x)", w);
|
|
} else if (nosize) {
|
|
Out(instr, "%s %c%d, %c%d", opname, r, Rd, r, Rn);
|
|
} else if (zero) {
|
|
Out(instr, "%s.%d %c%d, %c%d, %s", opname, fp ? fsize : esize, r, Rd, r, Rn, zero);
|
|
} else {
|
|
Out(instr, "%s.%d %c%d, %c%d", opname, fp ? fsize : esize, r, Rd, r, Rn);
|
|
}
|
|
}
|
|
|
|
static void SIMDCopy(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
bool Q = GetQ(w);
|
|
bool op = GetU(w);
|
|
int imm5 = (w >> 16) & 0x1F;
|
|
int imm4 = (w >> 11) & 0xF;
|
|
int size = 0;
|
|
while (size < 4 && !(imm5 & (1 << size)))
|
|
size++;
|
|
if (size == 4) {
|
|
Out(instr, "(asimd copy %08x)", w);
|
|
return;
|
|
}
|
|
char s = "bhsd"[size];
|
|
int index = imm5 >> (size + 1);
|
|
char r = Q ? 'q' : 'd';
|
|
|
|
if (op) {
|
|
if (!Q) {
|
|
Out(instr, "(asimd copy %08x)", w);
|
|
return;
|
|
}
|
|
Out(instr, "ins q%d.%c[%d], q%d.%c[%d]", Rd, s, index, Rn, s, imm4 >> size);
|
|
return;
|
|
}
|
|
switch (imm4) {
|
|
case 0:
|
|
Out(instr, "dup %c%d, q%d.%c[%d]", r, Rd, Rn, s, index);
|
|
break;
|
|
case 1:
|
|
Out(instr, "dup.%d %c%d, %s", 8 << size, r, Rd, GPR(size == 3, Rn));
|
|
break;
|
|
case 3:
|
|
Out(instr, "ins q%d.%c[%d], %s", Rd, s, index, GPR(size == 3, Rn));
|
|
break;
|
|
case 5:
|
|
Out(instr, "smov %s, q%d.%c[%d]", GPR(Q, Rd), Rn, s, index);
|
|
break;
|
|
case 7:
|
|
Out(instr, "umov %s, q%d.%c[%d]", GPR(Q, Rd), Rn, s, index);
|
|
break;
|
|
default:
|
|
Out(instr, "(asimd copy %08x)", w);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void SIMDModifiedImmediate(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
bool Q = GetQ(w);
|
|
bool op = GetU(w);
|
|
int cmode = (w >> 12) & 0xF;
|
|
int imm8 = (((w >> 16) & 7) << 5) | ((w >> 5) & 0x1F);
|
|
char r = Q ? 'q' : 'd';
|
|
|
|
if (((w >> 11) & 1) != 0) {
|
|
Out(instr, "(asimd modified immediate %08x)", w);
|
|
} else if (cmode < 8) {
|
|
// 32-bit shifted immediate.
|
|
const char *name = (cmode & 1) ? (op ? "bic" : "orr") : (op ? "mvni" : "movi");
|
|
int shift = (cmode >> 1) * 8;
|
|
if (shift)
|
|
Out(instr, "%s.32 %c%d, #0x%02x, lsl #%d", name, r, Rd, imm8, shift);
|
|
else
|
|
Out(instr, "%s.32 %c%d, #0x%02x", name, r, Rd, imm8);
|
|
} else if (cmode < 12) {
|
|
// 16-bit shifted immediate.
|
|
const char *name = (cmode & 1) ? (op ? "bic" : "orr") : (op ? "mvni" : "movi");
|
|
int shift = ((cmode >> 1) & 1) * 8;
|
|
if (shift)
|
|
Out(instr, "%s.16 %c%d, #0x%02x, lsl #%d", name, r, Rd, imm8, shift);
|
|
else
|
|
Out(instr, "%s.16 %c%d, #0x%02x", name, r, Rd, imm8);
|
|
} else if (cmode < 14) {
|
|
Out(instr, "%s.32 %c%d, #0x%02x, msl #%d", op ? "mvni" : "movi", r, Rd, imm8, (cmode & 1) ? 16 : 8);
|
|
} else if (cmode == 14) {
|
|
if (!op) {
|
|
Out(instr, "movi.8 %c%d, #0x%02x", r, Rd, imm8);
|
|
} else {
|
|
uint64_t imm = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
if (imm8 & (1 << i))
|
|
imm |= 0xFFULL << (i * 8);
|
|
}
|
|
// With Q clear this is the scalar form, which writes the whole d register.
|
|
Out(instr, Q ? "movi.64 q%d, #0x%08x%08x" : "movi d%d, #0x%08x%08x", Rd, (uint32_t)(imm >> 32), (uint32_t)imm);
|
|
}
|
|
} else {
|
|
if (op && !Q) {
|
|
Out(instr, "(asimd modified immediate %08x)", w);
|
|
} else {
|
|
Out(instr, "fmov.%d %c%d, #%f", op ? 64 : 32, r, Rd, Arm64Gen::FPImm8ToFloat(imm8));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SIMDShiftByImmediate(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
bool Q = GetQ(w);
|
|
bool U = GetU(w);
|
|
int immh = (w >> 19) & 0xf;
|
|
int immhb = (w >> 16) & 0x7f;
|
|
int opcode = (w >> 11) & 0x1f;
|
|
int esize = 8 << HighestSetBit(immh);
|
|
int rshift = esize * 2 - immhb;
|
|
int lshift = immhb - esize;
|
|
char r = Q ? 'q' : 'd';
|
|
const char *two = Q ? "2" : "";
|
|
|
|
static const char * const opnamesU0[32] = {
|
|
"sshr", nullptr, "ssra", nullptr, "srshr", nullptr, "srsra", nullptr,
|
|
nullptr, nullptr, "shl", nullptr, nullptr, nullptr, "sqshl", nullptr,
|
|
"shrn", "rshrn", "sqshrn", "sqrshrn", "sshll", nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, "scvtf", nullptr, nullptr, "fcvtzs",
|
|
};
|
|
static const char * const opnamesU1[32] = {
|
|
"ushr", nullptr, "usra", nullptr, "urshr", nullptr, "ursra", nullptr,
|
|
"sri", nullptr, "sli", nullptr, "sqshlu", nullptr, "uqshl", nullptr,
|
|
"sqshrun", "sqrshrun", "uqshrn", "uqrshrn", "ushll", nullptr, nullptr, nullptr,
|
|
nullptr, nullptr, nullptr, nullptr, "ucvtf", nullptr, nullptr, "fcvtzu",
|
|
};
|
|
const char *opname = U ? opnamesU1[opcode] : opnamesU0[opcode];
|
|
if (!opname || (immh & 8 && !Q && opcode != 0x10 && opcode != 0x11 && opcode != 0x12 && opcode != 0x13 && opcode != 0x14)) {
|
|
Out(instr, "(asimd shift-by-immediate %08x)", w);
|
|
} else if (opcode == 0x1C || opcode == 0x1F) {
|
|
// Fixed point conversions.
|
|
Out(instr, "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, rshift);
|
|
} else if (opcode >= 0x10 && opcode <= 0x13) {
|
|
// Narrowing right shifts.
|
|
if (immh & 8)
|
|
Out(instr, "(asimd shift-by-immediate %08x)", w);
|
|
else
|
|
Out(instr, "%s%s.%d.%d %c%d, q%d, #%d", opname, two, esize, esize * 2, r, Rd, Rn, rshift);
|
|
} else if (opcode == 0x14) {
|
|
// Widening left shifts.
|
|
if (immh & 8) {
|
|
Out(instr, "(asimd shift-by-immediate %08x)", w);
|
|
} else if (lshift == 0) {
|
|
Out(instr, "%cxtl%s.%d.%d q%d, %c%d", U ? 'u' : 's', two, esize * 2, esize, Rd, r, Rn);
|
|
} else {
|
|
Out(instr, "%s%s.%d.%d q%d, %c%d, #%d", opname, two, esize * 2, esize, Rd, r, Rn, lshift);
|
|
}
|
|
} else if (opcode >= 0xA) {
|
|
Out(instr, "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, lshift);
|
|
} else {
|
|
Out(instr, "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, rshift);
|
|
}
|
|
}
|
|
|
|
static void SIMDVectorIndexedElement(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
int Rm = (w >> 16) & 0x1f;
|
|
bool Q = GetQ(w);
|
|
bool U = GetU(w);
|
|
int size = (w >> 22) & 3;
|
|
bool L = (w >> 21) & 1;
|
|
bool H = (w >> 11) & 1;
|
|
int opcode = (w >> 12) & 0xf;
|
|
const char *opname = nullptr;
|
|
if (size & 2) {
|
|
if (!U) {
|
|
switch (opcode) {
|
|
case 1: opname = "fmla"; break;
|
|
case 5: opname = "fmls"; break;
|
|
case 9: opname = "fmul"; break;
|
|
}
|
|
} else if (opcode == 9) {
|
|
opname = "fmulx";
|
|
}
|
|
}
|
|
if (!opname || ((size & 1) && (L || !Q))) {
|
|
Out(instr, "(asimd vector x indexed elem %08x)", w);
|
|
return;
|
|
}
|
|
int index = (size & 1) ? H : ((H << 1) | (int)L);
|
|
char r = Q ? 'q' : 'd';
|
|
Out(instr, "%s %c%d, %c%d, %c%d.%s[%d]", opname, r, Rd, r, Rn, r, Rm, GetArrangement(Q, size & 1), index);
|
|
}
|
|
|
|
// (w >> 25) & 0xF == 7: AdvSIMD vector instructions.
|
|
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;
|
|
char r = GetQ(w) ? 'q' : 'd';
|
|
if ((w >> 31) & 1) {
|
|
Out(instr, "(FP1 %08x)", w);
|
|
} else if (((w >> 24) & 1) == 0) {
|
|
if ((w >> 21) & 1) {
|
|
if ((w >> 10) & 1) {
|
|
SIMDThreeSame(w, instr);
|
|
} else if (((w >> 10) & 3) == 2 && ((w >> 17) & 0xF) == 0) {
|
|
SIMDTwoRegMisc(w, instr);
|
|
} else if (((w >> 10) & 3) == 2 && ((w >> 17) & 0xF) == 8) {
|
|
Out(instr, "(asimd across lanes %08x)", w);
|
|
} else {
|
|
Out(instr, "(asimd three-different %08x)", w);
|
|
}
|
|
} else if ((w & 0x9FE08400) == 0x0E000400) {
|
|
SIMDCopy(w, instr);
|
|
} else if ((w & 0xBF208C00) == 0x0E000800 && ((w >> 12) & 3) != 0) {
|
|
static const char * const opnames[8] = { nullptr, "uzp1", "trn1", "zip1", nullptr, "uzp2", "trn2", "zip2" };
|
|
int size = (w >> 22) & 3;
|
|
Out(instr, "%s.%d %c%d, %c%d, %c%d", opnames[(w >> 12) & 7], 8 << size, r, Rd, r, Rn, r, Rm);
|
|
} else if ((w & 0xBFE08400) == 0x2E000000) {
|
|
Out(instr, "ext %c%d, %c%d, %c%d, #%d", r, Rd, r, Rn, r, Rm, (w >> 11) & 0xF);
|
|
} else {
|
|
Out(instr, "(FP1 %08x)", w);
|
|
}
|
|
} else if (((w >> 10) & 1) == 1) {
|
|
if (((w >> 19) & 0xf) == 0) {
|
|
SIMDModifiedImmediate(w, instr);
|
|
} else {
|
|
SIMDShiftByImmediate(w, instr);
|
|
}
|
|
} else {
|
|
SIMDVectorIndexedElement(w, instr);
|
|
}
|
|
}
|
|
|
|
static char FPRegChar(int type) {
|
|
switch (type) {
|
|
case 0: return 's';
|
|
case 1: return 'd';
|
|
case 3: return 'h';
|
|
default: return '?';
|
|
}
|
|
}
|
|
|
|
static void FPDataProcessing1(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
int type = (w >> 22) & 3;
|
|
int opcode = (w >> 15) & 0x3F;
|
|
char fr = FPRegChar(type);
|
|
if (opcode < 4) {
|
|
static const char * const opnames[4] = { "fmov", "fabs", "fneg", "fsqrt" };
|
|
Out(instr, "%s %c%d, %c%d", opnames[opcode], fr, Rd, fr, Rn);
|
|
} else if (opcode < 8 && (opcode & 3) != 2 && (opcode & 3) != type) {
|
|
Out(instr, "fcvt %c%d, %c%d", FPRegChar(opcode & 3), Rd, fr, Rn);
|
|
} else if (opcode >= 8 && opcode < 16 && opcode != 13) {
|
|
static const char * const opnames[8] = { "frintn", "frintp", "frintm", "frintz", "frinta", nullptr, "frintx", "frinti" };
|
|
Out(instr, "%s %c%d, %c%d", opnames[opcode - 8], fr, Rd, fr, Rn);
|
|
} else {
|
|
Out(instr, "(fp 1-source %08x)", w);
|
|
}
|
|
}
|
|
|
|
static void FPIntConversion(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
bool sf = (w >> 31) & 1;
|
|
int type = (w >> 22) & 3;
|
|
int rmode = (w >> 19) & 3;
|
|
int opcode = (w >> 16) & 7;
|
|
char fr = FPRegChar(type);
|
|
if (opcode < 2 || opcode == 4 || opcode == 5) {
|
|
// FP to int
|
|
char round = (opcode >= 4) ? 'a' : "npmz"[rmode];
|
|
if (opcode >= 4 && rmode != 0) {
|
|
Out(instr, "(fp<->int %08x)", w);
|
|
return;
|
|
}
|
|
Out(instr, "fcvt%c%c %s, %c%d", round, (opcode & 1) ? 'u' : 's', GPR(sf, Rd), fr, Rn);
|
|
} else if ((opcode == 2 || opcode == 3) && rmode == 0) {
|
|
Out(instr, "%ccvtf %c%d, %s", (opcode & 1) ? 'u' : 's', fr, Rd, GPR(sf, Rn));
|
|
} else if (opcode == 6 && rmode == 0) {
|
|
Out(instr, "fmov %s, %c%d", GPR(sf, Rd), fr, Rn);
|
|
} else if (opcode == 7 && rmode == 0) {
|
|
Out(instr, "fmov %c%d, %s", fr, Rd, GPR(sf, Rn));
|
|
} else if (opcode == 6 && rmode == 1 && sf && type == 2) {
|
|
Out(instr, "fmov %s, q%d.d[1]", X(Rd), Rn);
|
|
} else if (opcode == 7 && rmode == 1 && sf && type == 2) {
|
|
Out(instr, "fmov q%d.d[1], %s", Rd, X(Rn));
|
|
} else {
|
|
Out(instr, "(fp<->int %08x)", w);
|
|
}
|
|
}
|
|
|
|
static void SIMDScalar(uint32_t w, Instruction *instr) {
|
|
int Rd = w & 0x1f;
|
|
int Rn = (w >> 5) & 0x1f;
|
|
bool U = GetU(w);
|
|
int size = (w >> 22) & 3;
|
|
int opcode = (w >> 12) & 0x1f;
|
|
|
|
if ((w >> 24) & 1) {
|
|
if (((w >> 10) & 1) == 0) {
|
|
Out(instr, "(asimd scalar x indexed elem %08x)", w);
|
|
return;
|
|
}
|
|
// Scalar shift by immediate.
|
|
int immh = (w >> 19) & 0xf;
|
|
int immhb = (w >> 16) & 0x7f;
|
|
int sopcode = (w >> 11) & 0x1f;
|
|
if (immh == 0) {
|
|
Out(instr, "(asimd scalar shift-by-immediate %08x)", w);
|
|
return;
|
|
}
|
|
int esize = 8 << HighestSetBit(immh);
|
|
char r = "bhsd"[HighestSetBit(immh)];
|
|
if (sopcode == 0x1C || sopcode == 0x1F) {
|
|
const char *name = sopcode == 0x1C ? (U ? "ucvtf" : "scvtf") : (U ? "fcvtzu" : "fcvtzs");
|
|
if (esize < 16)
|
|
Out(instr, "(asimd scalar shift-by-immediate %08x)", w);
|
|
else
|
|
Out(instr, "%s %c%d, %c%d, #%d", name, r, Rd, r, Rn, esize * 2 - immhb);
|
|
} else if (esize == 64 && (sopcode == 0 || sopcode == 2 || sopcode == 4 || sopcode == 6 || (U && sopcode == 8))) {
|
|
static const char * const names[2][5] = { { "sshr", "ssra", "srshr", "srsra", nullptr }, { "ushr", "usra", "urshr", "ursra", "sri" } };
|
|
Out(instr, "%s d%d, d%d, #%d", names[U][sopcode >> 1], Rd, Rn, 128 - immhb);
|
|
} else if (esize == 64 && sopcode == 0xA) {
|
|
Out(instr, "%s d%d, d%d, #%d", U ? "sli" : "shl", Rd, Rn, immhb - 64);
|
|
} else {
|
|
Out(instr, "(asimd scalar shift-by-immediate %08x)", w);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (((w >> 21) & 1) == 0) {
|
|
Out(instr, "(asimd scalar copy %08x)", w);
|
|
return;
|
|
}
|
|
if ((w >> 10) & 1) {
|
|
Out(instr, "(asimd scalar three same %08x)", w);
|
|
return;
|
|
}
|
|
if (((w >> 10) & 3) != 2) {
|
|
Out(instr, "(asimd scalar three different %08x)", w);
|
|
return;
|
|
}
|
|
|
|
if (((w >> 17) & 0xf) == 8) {
|
|
// Scalar pairwise.
|
|
const char *opname = nullptr;
|
|
if (U) {
|
|
switch (opcode) {
|
|
case 0xC: opname = (size & 2) ? "fminnmp" : "fmaxnmp"; break;
|
|
case 0xD: opname = (size & 2) ? nullptr : "faddp"; break;
|
|
case 0xF: opname = (size & 2) ? "fminp" : "fmaxp"; break;
|
|
}
|
|
}
|
|
if (opname) {
|
|
bool sz = size & 1;
|
|
Out(instr, "%s %c%d, %c%d", opname, sz ? 'd' : 's', Rd, sz ? 'q' : 'd', Rn);
|
|
} else if (!U && opcode == 0x1B && size == 3) {
|
|
Out(instr, "addp d%d, q%d", Rd, Rn);
|
|
} else {
|
|
Out(instr, "(asimd scalar pair-wise %08x)", w);
|
|
}
|
|
return;
|
|
}
|
|
if (((w >> 17) & 0xf) != 0) {
|
|
Out(instr, "(asimd scalar stuff %08x)", w);
|
|
return;
|
|
}
|
|
|
|
// Scalar two-reg misc.
|
|
const char *opname = nullptr;
|
|
const char *zero = nullptr;
|
|
bool fp = false;
|
|
if (opcode >= 0x1A && opcode <= 0x1D) {
|
|
static const char * const names[2][2][4] = {
|
|
{ { "fcvtns", "fcvtms", "fcvtas", "scvtf" }, { "fcvtps", "fcvtzs", nullptr, "frecpe" } },
|
|
{ { "fcvtnu", "fcvtmu", "fcvtau", "ucvtf" }, { "fcvtpu", "fcvtzu", nullptr, "frsqrte" } },
|
|
};
|
|
opname = names[U][size >> 1][opcode - 0x1A];
|
|
fp = true;
|
|
} else if ((size & 2) && opcode >= 0xC && opcode <= 0xE) {
|
|
static const char * const names[2][3] = { { "fcmgt", "fcmeq", "fcmlt" }, { "fcmge", "fcmle", nullptr } };
|
|
opname = names[U][opcode - 0xC];
|
|
zero = "#0.0";
|
|
fp = true;
|
|
} else if (size == 3 && opcode >= 8 && opcode <= 0xB) {
|
|
static const char * const names[2][4] = { { "cmgt", "cmeq", "cmlt", "abs" }, { "cmge", "cmle", nullptr, "neg" } };
|
|
opname = names[U][opcode - 8];
|
|
if (opcode != 0xB)
|
|
zero = "#0";
|
|
}
|
|
if (!opname) {
|
|
Out(instr, "(asimd scalar two-reg misc %08x)", w);
|
|
return;
|
|
}
|
|
char r = fp ? ((size & 1) ? 'd' : 's') : 'd';
|
|
if (zero)
|
|
Out(instr, "%s %c%d, %c%d, %s", opname, r, Rd, r, Rn, zero);
|
|
else
|
|
Out(instr, "%s %c%d, %c%d", opname, r, Rd, r, Rn);
|
|
}
|
|
|
|
// (w >> 25) & 0xF == f: scalar floating point, and AdvSIMD scalar.
|
|
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;
|
|
bool sf = (w >> 31) & 1;
|
|
char fr = FPRegChar(type);
|
|
|
|
if (GetQ(w)) {
|
|
if (sf)
|
|
Out(instr, "(FP2 %08x)", w);
|
|
else
|
|
SIMDScalar(w, instr);
|
|
return;
|
|
}
|
|
if (GetU(w) || type == 2) {
|
|
// The S bit, and the type used by fmov to the top half of a vector.
|
|
if (((w >> 21) & 1) && ((w >> 10) & 0x3F) == 0 && type == 2 && !GetU(w))
|
|
FPIntConversion(w, instr);
|
|
else
|
|
Out(instr, "(FP2 %08x)", w);
|
|
return;
|
|
}
|
|
|
|
if ((w >> 24) & 1) {
|
|
// Data-processing (3 source)
|
|
int opcode = (((w >> 21) & 1) << 1) | ((w >> 15) & 1);
|
|
static const char * const opnames[4] = { "fmadd", "fmsub", "fnmadd", "fnmsub" };
|
|
int Ra = (w >> 10) & 0x1f;
|
|
Out(instr, "%s %c%d, %c%d, %c%d, %c%d", opnames[opcode], fr, Rd, fr, Rn, fr, Rm, fr, Ra);
|
|
} else if (((w >> 21) & 1) == 0) {
|
|
// Conversion between floating point and fixed point.
|
|
int rmode = (w >> 19) & 3;
|
|
int opcode = (w >> 16) & 7;
|
|
int scale = (w >> 10) & 0x3f;
|
|
int fbits = 64 - scale;
|
|
char sign = (opcode & 1) ? 'u' : 's';
|
|
if (!sf && scale < 32) {
|
|
Out(instr, "(float<->fixed %08x)", w);
|
|
} else if (rmode == 0 && (opcode & 6) == 2) {
|
|
Out(instr, "%ccvtf %c%d, %s, #%d", sign, fr, Rd, GPR(sf, Rn), fbits);
|
|
} else if (rmode == 3 && (opcode & 6) == 0) {
|
|
Out(instr, "fcvtz%c %s, %c%d, #%d", sign, GPR(sf, Rd), fr, Rn, fbits);
|
|
} else {
|
|
Out(instr, "(float<->fixed %08x)", w);
|
|
}
|
|
} else {
|
|
switch ((w >> 10) & 3) {
|
|
case 0:
|
|
if (((w >> 10) & 7) == 4) {
|
|
if (((w >> 5) & 0x1F) != 0) {
|
|
Out(instr, "(fp imm %08x)", w);
|
|
} else {
|
|
uint8_t uimm8 = (w >> 13) & 0xff;
|
|
Out(instr, "fmov %c%d, #%f", fr, Rd, Arm64Gen::FPImm8ToFloat(uimm8));
|
|
}
|
|
} else if (((w >> 10) & 0xf) == 8) {
|
|
int opcode2 = w & 0x1f;
|
|
bool e = (opcode2 >> 4) & 1;
|
|
bool z = (opcode2 >> 3) & 1;
|
|
if ((opcode2 & 7) != 0 || ((w >> 14) & 3) != 0) {
|
|
Out(instr, "(fp compare %08x)", w);
|
|
} else if (z) {
|
|
Out(instr, "fcmp%s %c%d, #0.0", e ? "e" : "", fr, Rn);
|
|
} else {
|
|
Out(instr, "fcmp%s %c%d, %c%d", e ? "e" : "", fr, Rn, fr, Rm);
|
|
}
|
|
} else if (((w >> 10) & 0x1f) == 0x10) {
|
|
FPDataProcessing1(w, instr);
|
|
} else if (((w >> 10) & 0x3f) == 0) {
|
|
FPIntConversion(w, instr);
|
|
} else {
|
|
Out(instr, "(FP2 %08x)", w);
|
|
}
|
|
break;
|
|
case 1: {
|
|
int cond = (w >> 12) & 0xf;
|
|
bool e = (w >> 4) & 1;
|
|
Out(instr, "fccmp%s %c%d, %c%d, #%d, %s", e ? "e" : "", fr, Rn, fr, Rm, w & 0xF, condnames[cond]);
|
|
break;
|
|
}
|
|
case 2: {
|
|
int opc = (w >> 12) & 0xf;
|
|
static const char * const opnames[16] = { "fmul", "fdiv", "fadd", "fsub", "fmax", "fmin", "fmaxnm", "fminnm", "fnmul" };
|
|
if (opnames[opc])
|
|
Out(instr, "%s %c%d, %c%d, %c%d", opnames[opc], fr, Rd, fr, Rn, fr, Rm);
|
|
else
|
|
Out(instr, "(fp 2-source %08x)", w);
|
|
break;
|
|
}
|
|
case 3: {
|
|
int cond = (w >> 12) & 0xf;
|
|
Out(instr, "fcsel %c%d, %c%d, %c%d, %s", fr, Rd, fr, Rn, fr, Rm, condnames[cond]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (instr.undefined) {
|
|
snprintf(output, bufsize, "%08x\t[undefined instr]", w);
|
|
} else if (includeWord) {
|
|
snprintf(output, bufsize, "%08x\t%s", w, instr.text);
|
|
} else {
|
|
snprintf(output, bufsize, "%s", instr.text);
|
|
}
|
|
// zap tabs
|
|
while (*output) {
|
|
if (*output == '\t')
|
|
*output = ' ';
|
|
output++;
|
|
}
|
|
}
|