Files
ppsspp/Core/Util/DisArm64.cpp
T
Henrik RydgårdandClaude Opus 5.5 4163737bbc ARM64 disassembler: Cover everything the emitter can emit, add a test
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>
2026-10-13 20:59:51 +02:00

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++;
}
}