// Copyright (c) 2015- PPSSPP Project.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

// Basic ARM64 disassembler.

// No promises of accuracy, mostly just made to debug JIT code.
// Does enough to understand what's going on without having to resort to an
// external disassembler all the time...

#include <cstdlib>
#include <cstring>
#include <array>

#include "Common/Arm64Emitter.h"
#include "Common/StringUtils.h"
#include "Core/Util/DisArm64.h"

struct Instruction {
	char text[128];
	bool undefined;
	bool badbits;
	bool oddbits;
};

static const char * const shiftnames[4] = { "lsl", "lsr", "asr", "ror" };
static const char * const extendnames[8] = { "uxtb", "uxth", "uxtw", "uxtx", "sxtb", "sxth", "sxtw", "sxtx" };
static const char * const condnames[16] = {
	"eq", // Equal
	"ne", // Not equal
	"cs", // Carry Set    "HS"
	"cc", // Carry Clear	"LO"
	"mi", // Minus (Negative)
	"pl", // Plus
	"vs", // Overflow
	"vc", // No Overflow
	"hi", // Unsigned higher
	"ls", // Unsigned lower or same
	"ge", // Signed greater than or equal
	"lt", // Signed less than
	"gt", // Signed greater than
	"le", // Signed less than or equal
	"al", // Always (unconditional) 14
};

int SignExtend26(int x) {
	return (x & 0x02000000) ? (0xFC000000 | x) : (x & 0x3FFFFFF);
}

int SignExtend19(int x) {
	return (x & 0x00040000) ? (0xFFF80000 | x) : (x & 0x7FFFF);
}

int SignExtend9(int x) {
	return (x & 0x00000100) ? (0xFFFFFE00 | x) : (x & 0x1FF);
}

int SignExtend7(int x) {
	return (x & 0x00000040) ? (0xFFFFFF80 | x) : (x & 0x7F);
}

int SignExtend12(int x) {
	return (x & 0x00000800) ? (0xFFFFF000 | x) : (x & 0xFFF);
}

int HighestSetBit(int value) {
	int highest = 0;
	for (int i = 0; i < 32; i++) {
		if (value & (1 << i))
			highest = i;
	}
	return highest;
}

int LowestSetBit(int value, int maximum = 32) {
	for (int i = 0; i < maximum; i++) {
		if (value & (1 << i))
			return i;
	}
	return maximum;
}

static uint64_t Ones(int len) {
	if (len == 0x40) {
		return 0xFFFFFFFFFFFFFFFF;
	}
	return (1ULL << len) - 1;
}

static uint64_t Replicate(uint64_t value, int esize) {
	uint64_t out = 0;
	value &= Ones(esize);
	for (int i = 0; i < 64; i += esize) {
		out |= value << i;
	}
	return out;
}

static uint64_t ROR(uint64_t value, int amount, int esize) {
	uint64_t rotated = (value >> amount) | (value << (esize - amount));
	return rotated & Ones(esize);
}

void DecodeBitMasks(int immN, int imms, int immr, uint64_t *tmask, uint64_t *wmask) {
	// Compute log2 of element size
	// 2^len must be in range [2, M]
	int len = HighestSetBit((immN << 6) | ((~imms) & 0x3f));
	// if len < 1 then ReservedValue();
	// assert M >= (1 << len);
	// Determine S, R and S - R parameters
	int levels = (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);
	uint32_t welem = (uint32_t)Ones(S + 1);
	uint32_t telem = (uint32_t)Ones(d + 1);
	if (wmask) {
		uint64_t rotated = ROR(welem, R, esize);
		*wmask = Replicate(rotated, esize);
	}
	if (tmask) {
		*tmask = Replicate(telem, esize);
	}
}

static void DataProcessingImmediate(uint32_t w, uint64_t addr, Instruction *instr) {
	int Rd = w & 0x1f;
	int Rn = (w >> 5) & 0x1f;
	char r = ((w >> 31) & 1) ? 'x' : 'w';
	if (((w >> 23) & 0x3f) == 0x25) {
		// Constant initialization.
		int imm16 = (w >> 5) & 0xFFFF;
		int opc = (w >> 29) & 3;
		int shift = ((w >> 21) & 0x3) * 16;
		const char *opnames[4] = { "movn", "(undef)", "movz", "movk" };
		snprintf(instr->text, sizeof(instr->text), "%s %c%d, #0x%04x << %d", opnames[opc], r, Rd, imm16, shift);
	} else if (((w >> 24) & 0x1F) == 0x10) {
		// Address generation relative to PC
		int op = w >> 31;
		int imm = (SignExtend19(w >> 5) << 2) | ((w >> 29) & 3);
		if (op & 1) imm <<= 12;
		u64 daddr = addr + imm;
		snprintf(instr->text, sizeof(instr->text), "%s x%d, #0x%04x%08x", op ? "adrp" : "adr", Rd, (u32)(daddr >> 32), (u32)(daddr & 0xFFFFFFFF));
	} else if (((w >> 24) & 0x1F) == 0x11) {
		// Add/subtract immediate value
		int op = (w >> 30) & 1;
		int imm = ((w >> 10) & 0xFFF);
		int shift = ((w >> 22) & 0x1) * 12;
		int s = ((w >> 29) & 1);
		imm <<= shift;
		if (s && Rd == 31) {
			snprintf(instr->text, sizeof(instr->text), "cmp %c%d, #%d", r, Rn, imm);
		} else if (!shift && Rn == 31 && imm == 0) {
			snprintf(instr->text, sizeof(instr->text), "mov %c%d, sp", r, Rd);
		} else if (!shift && Rd == 31 && imm == 0) {
			snprintf(instr->text, sizeof(instr->text), "mov sp, %c%d", r, Rn);
		} else {
			snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, %c%d, #%d", op == 0 ? "add" : "sub", s ? "s" : "", r, Rd, r, Rn, imm);
		}
	} else if (((w >> 23) & 0x3f) == 0x24) {
		int immr = (w >> 16) & 0x3f;
		int imms = (w >> 10) & 0x3f;
		int N = (w >> 22) & 1;
		int opc = (w >> 29) & 3;
		const char *opname[4] = { "and", "orr", "eor", "ands" };
		uint64_t wmask;
		DecodeBitMasks(N, imms, immr, NULL, &wmask);
		if (((w >> 31) & 1) && wmask & 0xFFFFFFFF00000000ULL)
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, #0x%x%08x", opname[opc], r, Rd, r, Rn, (uint32_t)(wmask >> 32), (uint32_t)(wmask & 0xFFFFFFFF));
		else
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, #0x%x", opname[opc], r, Rd, r, Rn, (uint32_t)wmask);
	} else if (((w >> 23) & 0x3f) == 0x26) {
		int N = (w >> 22) & 1;
		int opc = (w >> 29) & 3;
		int immr = (w >> 16) & 0x3f;
		int imms = (w >> 10) & 0x3f;
		const char *opname[4] = { "sbfm", "bfm", "ubfm" };
		snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, #%d, #%d", opname[opc], r, Rd, r, Rn, immr, imms);
	} else {
		snprintf(instr->text, sizeof(instr->text), "(DPI %08x)", w);
	}
}

static const char *GetSystemRegName(int o0, int op1, int CRn, int CRm, int op2) {
	if (o0 == 3 && op1 == 3 && CRn == 4 && CRm == 2 && op2 == 0) {
		return "nzcv";
	} else if (o0 == 3 && op1 == 3 && CRn == 4 && CRm == 4 && op2 == 0) {
		return "fpsr";
	} else {
		return "(unknown)";
	}
}

static void BranchExceptionAndSystem(uint32_t w, uint64_t addr, Instruction *instr, SymbolCallback symbolCallback) {
	char buffer[125];
	int Rt = w & 0x1f;
	int Rn = (w >> 5) & 0x1f;
	if (((w >> 26) & 0x1F) == 5) {
		// Unconditional branch / branch+link
		int offset = SignExtend26(w) << 2;
		uint64_t target = addr + offset;
		if (symbolCallback && symbolCallback(buffer, sizeof(buffer), (uint8_t *)(uintptr_t)target)) {
			snprintf(instr->text, sizeof(instr->text), "b%s %s", (w >> 31) ? "l" : "", buffer);
		} else {
			snprintf(instr->text, sizeof(instr->text), "b%s %04x%08x", (w >> 31) ? "l" : "", (uint32_t)(target >> 32), (uint32_t)(target & 0xFFFFFFFF));
		}
	} else if (((w >> 25) & 0x3F) == 0x1A) {
		// Compare and branch
		int op = (w >> 24) & 1;
		const char *opname[2] = { "cbz", "cbnz" };
		char r = ((w >> 31) & 1) ? 'x' : 'w';
		int offset = SignExtend19(w >> 5);
		snprintf(instr->text, sizeof(instr->text), "%s %c%d", opname[op], r, Rt);
	} else if (((w >> 25) & 0x3F) == 0x1B) {
		// Test and branch
		snprintf(instr->text, sizeof(instr->text), "(test & branch %08x)", w);
	} else if (((w >> 25) & 0x7F) == 0x2A) {
		// Conditional branch
		int offset = SignExtend19(w >> 5) << 2;
		uint64_t target = addr + offset;
		int cond = w & 0xF;
		snprintf(instr->text, sizeof(instr->text), "b.%s %04x%08x", condnames[cond], (uint32_t)(target >> 32), (uint32_t)(target & 0xFFFFFFFF));
	} else if ((w >> 24) == 0xD4) {
		if (((w >> 21) & 0x7) == 1 && Rt == 0) {
			int imm = (w >> 5) & 0xFFFF;
			snprintf(instr->text, sizeof(instr->text), "brk #%d", imm);
		} else {
			snprintf(instr->text, sizeof(instr->text), "(exception-gen %08x)", w);
		}
	} else if (((w >> 20) & 0xFFC) == 0xD50) {
		bool L = (w >> 21) & 1;  // read
		// Could check them all at once, but feels better to do it like the manual says.
		int o0 = (w >> 19) & 3;
		int op1 = (w >> 16) & 7;
		int CRn = (w >> 12) & 0xF;
		int CRm = (w >> 8) & 0xf;
		int op2 = (w >> 5) & 0x7;
		const char *sysreg = GetSystemRegName(o0, op1, CRn, CRm, op2);
		if (L) {
			snprintf(instr->text, sizeof(instr->text), "mrs x%d, %s", Rt, sysreg);
		} else {
			snprintf(instr->text, sizeof(instr->text), "msr %s, x%d", sysreg, Rt);
		}

	} else if (((w >> 25) & 0x7F) == 0x6B) {
		int op = (w >> 21) & 3;
		const char *opname[4] = { "b", "bl", "ret", "(unk)" };
		snprintf(instr->text, sizeof(instr->text), "%s x%d", opname[op], Rn);
	} else {
		snprintf(instr->text, sizeof(instr->text), "(BRX ?? %08x)", w);
	}
}

bool Arm64AnalyzeLoadStore(uint64_t addr, uint32_t w, Arm64LSInstructionInfo *info) {
	*info = {};
	info->instructionSize = 4;
	int id = (w >> 25) & 0xF;
	switch (id) {
	case 4: case 6: case 0xC: case 0xE:
		break;
	default:
		return false;  // not the expected instruction
	}

	info->size = w >> 30;
	info->Rt = (w & 0x1F);
	info->Rn = ((w >> 5) & 0x1F);
	info->Rm = ((w >> 16) & 0x1F);
	int opc = (w >> 22) & 0x3;
	if (opc == 0 || opc == 2) {
		info->isMemoryWrite = true;
	}

	if (((w >> 27) & 7) == 7) {
		int V = (w >> 26) & 1;
		if (V == 0) {
			info->isIntegerLoadStore = true;
		} else {
			info->isFPLoadStore = true;
		}
	} else {
		info->isPairLoadStore = true;
		// TODO
	}
	return true;
}

static void LoadStore(uint32_t w, uint64_t addr, Instruction *instr) {
	int size = w >> 30;
	int imm9 = SignExtend9((w >> 12) & 0x1FF);
	int Rt = (w & 0x1F);
	int Rn = ((w >> 5) & 0x1F);
	int Rm = ((w >> 16) & 0x1F);
	int option = (w >> 13) & 0x7;
	int opc = (w >> 22) & 0x3;
	char r = size == 3 ? 'x' : 'w';
	const char *opname[4] = { "str", "ldr", "str", "ldr" };
	const char *sizeSuffix[4] = { "b", "h", "", "" };

	if (((w >> 27) & 7) == 7) {
		int V = (w >> 26) & 1;
		bool index_unsigned = ((w >> 24) & 3) == 1;
		bool index_post = !index_unsigned && ((w >> 10) & 3) == 1;
		bool index_pre = !index_unsigned && ((w >> 10) & 3) == 3;
		if (V == 0) {
			const char *signExt = ((opc & 0x2) && size < 3) ? "s" : "";
			int imm12 = SignExtend12((w >> 10) & 0xFFF) << size;
			// Integer type
			if (index_unsigned) {
				snprintf(instr->text, sizeof(instr->text), "%s%s%s %c%d, [x%d, #%d]", opname[opc], signExt, sizeSuffix[size], r, Rt, Rn, imm12);
				return;
			} else if (index_post) {
				snprintf(instr->text, sizeof(instr->text), "%s%s%s %c%d, [x%d], #%d", opname[opc], signExt, sizeSuffix[size], r, Rt, Rn, SignExtend9(imm9));
				return;
			} else if (index_pre) {
				snprintf(instr->text, sizeof(instr->text), "%s%s%s %c%d, [x%d, #%d]!", opname[opc], signExt, sizeSuffix[size], r, Rt, Rn, SignExtend9(imm9));
				return;
			} else {
				// register offset
				int S = (w >> 12) & 1;
				char index_w = (option & 3) == 2 ? 'w' : 'x';
				// TODO: Needs index support
				snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, [x%d + %c%d]", opname[opc], sizeSuffix[size], r, Rt, Rn, index_w, Rm);
				return;
			}
		} else {
			// FP/Vector type
			if ((opc & 2) && size == 0) {
				size = 4;
			}
			char vr = "bhsdq"[size];
			int imm12 = SignExtend12((w >> 10) & 0xFFF) << size;
			if (index_unsigned) {
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, [x%d, #%d]", opname[opc], vr, Rt, Rn, imm12);
				return;
			} else if (index_post) {
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, [x%d], #%d", opname[opc], vr, Rt, Rn, SignExtend9(imm9));
				return;
			} else if (index_pre) {
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, [x%d, #%d]!", opname[opc], vr, Rt, Rn, SignExtend9(imm9));
				return;
			} else {
				snprintf(instr->text, sizeof(instr->text), "(loadstore-fp-vector %08x)", w);
				return;
			}
		}
	} else if (((w >> 25) & 0x1D) == 0x14) {
		// load/store pair
		int Rt2 = (w >> 10) & 0x1f;
		bool load = (w >> 22) & 1;
		int index_type = ((w >> 23) & 3);
		bool sf = (w >> 31) != 0;
		bool V = (w >> 26) & 1;
		int op = (w >> 30);

		int offset = SignExtend7((w >> 15) & 0x7f);
		if (V) {
			offset <<= 2;
			switch (op) {
			case 0:
				r = 's';
				break;
			case 1:
				r = 'd';
				if (index_type == 2)
					offset <<= 1;
				break;
			case 2:
				r = 'q';
				if (index_type == 2)
					offset <<= 2;
			}
		} else {
			r = sf ? 'x' : 'w';
			offset <<= (sf ? 3 : 2);
		}
		if (index_type == 2) {
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d, #%d]", load ? "ldp" : "stp", r, Rt, r, Rt2, Rn, offset);
			return;
		} else if (index_type == 1) {
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d], #%d", load ? "ldp" : "stp", r, Rt, r, Rt2, Rn, offset);
			return;
		} else if (index_type == 3) {
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d, #%d]!", load ? "ldp" : "stp", r, Rt, r, Rt2, Rn, offset);
			return;
		} else if (index_type == 0) {
			// LDNP/STNP (ldp/stp with non-temporal hint). Automatically signed offset.
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, [x%d, #%d]", load ? "ldnp" : "stnp", r, Rt, r, Rt2, Rn, offset);
			return;
		}
	}
	snprintf(instr->text, sizeof(instr->text), "(LS %08x)", w);
}

static void DataProcessingRegister(uint32_t w, uint64_t addr, Instruction *instr) {
	int Rd = w & 0x1F;
	int Rn = (w >> 5) & 0x1F;
	int Rm = (w >> 16) & 0x1F;
	char r = ((w >> 31) & 1) ? 'x' : 'w';

	if (((w >> 21) & 0x2FF) == 0x2D6) {
		// Data processing
		int opcode2 = (w >> 16) & 0x1F;
		int opcode = (w >> 10) & 0x3F;
		// Data-processing (1 source)
		const char *opname[8] = { "rbit", "rev16", "rev32", "(unk)", "clz", "cls" };
		const char *op = opcode2 >= 8 ? "unk" : opname[opcode];
		snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", op, r, Rd, r, Rn);
	} else if (((w >> 21) & 0x2FF) == 0x0D6) {
		const char *opname[64] = {
			0, 0, "udiv", "sdiv", 0, 0, 0, 0,
			"lslv", "lsrv", "asrv", "rorv", 0, 0, 0, 0,
			"crc32b", "crc32h", "crc32w", 0, "crc32cb", "crc32ch", "crc32cw", 0,
		};
		int opcode = (w >> 10) & 0x3F;
		// Data processing (2 source)
		snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opname[opcode] ? opname[opcode] : "?", r, Rd, r, Rn, r, Rm);
	} else if (((w >> 24) & 0x1f) == 0xA) {
		// Logical (shifted register)
		int shift = (w >> 22) & 0x3;
		int imm6 = (w >> 10) & 0x3f;
		int N = (w >> 21) & 1;
		int opc = (((w >> 29) & 3) << 1) | N;
		const char *opnames[8] = { "and", "bic", "orr", "orn", "eor", "eon", "ands", "bics" };
		if (opc == 2 && Rn == 31) {
			// Special case for MOV (which is constructed from an ORR)
			if (imm6 != 0) {
				snprintf(instr->text, sizeof(instr->text), "mov %c%d, %c%d, %s #%d", r, Rd, r, Rm, shiftnames[shift], imm6);
			} else {
				snprintf(instr->text, sizeof(instr->text), "mov %c%d, %c%d", r, Rd, r, Rm);
			}
		} else if (imm6 == 0 && shift == 0) {
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opnames[opc], r, Rd, r, Rn, r, Rm);
		} else {
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %s #%d", opnames[opc], r, Rd, r, Rn, r, Rm, shiftnames[shift], imm6);
		}
	} else if (((w >> 21) & 0xf9) == 0x58) {
		// Add/sub/cmp (shifted register)
		bool S = (w >> 29) & 1;
		int shift = (w >> 22) & 0x3;
		int imm6 = (w >> 10) & 0x3f;
		int opc = ((w >> 29) & 3);
		const char *opnames[8] = { "add", "adds", "sub", "subs"};
		if (imm6 == 0 && shift == 0) {
			if (Rd == 31 && opc == 3) {
				// It's a CMP
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", "cmp", r, Rn, r, Rm);
			} else if (Rn == 31 && opc == 2) {
				// It's a NEG
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", "neg", r, Rd, r, Rm);
			} else {
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opnames[opc], r, Rd, r, Rn, r, Rm);
			}
		} else {
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %s #%d", opnames[opc], r, Rd, r, Rn, r, Rm, shiftnames[shift], imm6);
		}
	} else if (((w >> 21) & 0xFF) == 0x59) {
		// Add/sub (extended register)
		bool S = (w >> 29) & 1;
		bool sub = (w >> 30) & 1;
		int option = (w >> 13) & 0x7;
		int imm3 = (w >> 10) & 0x7;
		if (Rd == 31 && sub && S) {
			// It's a CMP
			snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, %c%d, %s", "cmp", S ? "s" : "", r, Rn, r, Rm, extendnames[option]);
		} else {
			snprintf(instr->text, sizeof(instr->text), "%s%s %c%d, %c%d, %c%d, %s", sub ? "sub" : "add", S ? "s" : "", r, Rd, r, Rn, r, Rm, extendnames[option]);
		}
	} else if (((w >> 21) & 0xFF) == 0xD6 && ((w >> 12) & 0xF) == 2) {
		// Variable shifts
		int opc = (w >> 10) & 3;
		snprintf(instr->text, sizeof(instr->text), "%sv %c%d, %c%d, %c%d", shiftnames[opc], r, Rd, r, Rn, r, Rm);
	} else if (((w >> 21) & 0xFF) == 0xD4) {
		// Conditional select
		int op = (w >> 30) & 1;
		int op2 = (w >> 10) & 3;
		int cond = (w >> 12) & 0xf;
		const char *opnames[4] = { "csel", "csinc", "csinv", "csneg" };
		snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %s", opnames[(op << 1) | op2], r, Rd, r, Rn, r, Rm, condnames[cond]);
	} else if (((w >> 24) & 0x1f) == 0x1b) {
		// Data processing - 3 source
		int op31 = (w >> 21) & 0x7;
		int o0 = (w >> 15) & 1;
		int Ra = (w >> 10) & 0x1f;
		static constexpr std::array<const char*, 8> opnames = { 0, 0, "maddl", "msubl", "smulh", 0, 0, 0 };

		if (op31 == 0) {
			// madd/msub supports both 32-bit and 64-bit modes
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %c%d", o0 ? "msub" : "madd", r, Rd, r, Rn, r, Rm, r, Ra);
		} else {
			// The rest are 64-bit accumulator, 32-bit operands
			char sign = (op31 >> 2) ? 'u' : 's';
			int opn = (op31 & 0x3) << 1 | o0;
			if (opn < 4 && Ra == 31) {
				snprintf(instr->text, sizeof(instr->text), "%cmull x%d, w%d, w%d", sign, Rd, Rn, Rm);
			} else {
				snprintf(instr->text, sizeof(instr->text), "%c%s x%d, w%d, w%d, x%d", sign, opnames[opn], Rd, Rn, Rm, Ra);
			}
		}
	} else {
		// Logical (extended register)
		snprintf(instr->text, sizeof(instr->text), "(DPR %08x)", w);
	}
}

inline bool GetQ(uint32_t w) { return (w >> 30) & 1; }
inline bool GetU(uint32_t w) { return (w >> 29) & 1; }
const char *GetArrangement(bool Q, bool sz) {
	if (Q == 0 && sz == 0) return "2s";
	else if (Q == 1 && sz == 0) return "4s";
	else if (Q == 1 && sz == 1) return "2d";
	else return "ERROR";
}

// (w >> 25) & 0xF  == 7
static void FPandASIMD1(uint32_t w, uint64_t addr, Instruction *instr) {
	int Rd = w & 0x1f;
	int Rn = (w >> 5) & 0x1f;
	int Rm = (w >> 16) & 0x1f;
	if (((w >> 21) & 0x4F9) == 0x71) {
		switch ((w >> 10) & 3) {
		case 1: case 3: {
			int opcode = (w >> 11) & 0x1f;
			int sz = (w >> 22) & 3;
			int Q = GetQ(w);
			int U = GetU(w);
			const char *opnames000[32] = {
				"shadd", "sqadd", "srhadd", 0,
				"shsub", "sqsub", "cmgt", "cmge",
				"sshl", "sqshl", "srshl", "sqrshl",
				"smax", "smin", "sabd", "saba",
				"add", "cmtst", "mla", "mul",
				"smaxp", "sminp", "sqdmulh", "addp",
				"fmaxnm", "fmla", "fadd", "fmulx",
				"fcmeq", 0, "fmax", "frecps",
			};
			const char *opnames100[32] = {
				"uhadd", "uqadd", "urhadd", 0,
				"uhsub", "uqsub", "cmhi", "cmhs",
				"ushl", "uqshl", "urshl", "uqrshl",
				"umax", "umin", "uabd", "uaba",
				"sub", "cmeq", "mls", "pmul",
				"umaxp", "uminp", "sqrdmulh", "addp",
				"fmaxnmp", 0, "faddp", "fmul",
				"fcmge", "facge", "fmaxp", "fdiv",
			};
			const char *opnames010[8] = { 
				"fminm", "fmls", "fsub", 0,
				0, 0, "fmin", "frsqrts",
			};
			const char *opnames110[8] = {
				"fminnmp", 0, "fabd", 0,
				"fcmgt", "facgt", "fminp", 0,
			};
			char r = Q ? 'q' : 'd';
			const char *opname = nullptr;
			bool fp = false;
			bool nosize = false;
			if (U == 0) {
				if (opcode < 0x18) {
					opname = opnames000[opcode];
				} else if ((sz & 0x2) == 0) {
					opname = opnames000[opcode];
					fp = true;
				} else if ((sz & 0x2) == 2) {
					opname = opnames010[opcode - 0x18];
					fp = true;
				}
				if (!opname && opcode == 3 && (sz & 2) == 0) {
					opname = !(sz & 1) ? "and" : "bic";
					nosize = true;
				} else if (!opname && opcode == 3 && (sz & 2) == 2) {
					opname = !(sz & 1) ? "orr" : "orn";
					nosize = true;
				}
			} else if (U == 1) {
				if (opcode < 0x18) {
					opname = opnames100[opcode];
				} else if ((sz & 0x2) == 0) {
					opname = opnames100[opcode];
					fp = true;
				} else if ((sz & 0x2) == 2) {
					opname = opnames110[opcode - 0x18];
					fp = true;
				}
				if (!opname && opcode == 3 && (sz & 2) == 0) {
					opname = !(sz & 1) ? "eor" : "bsl";
					if (!strcmp(opname, "eor"))
						nosize = true;
				} else if (!opname && opcode == 3 && (sz & 2) == 2) {
					opname = !(sz & 1) ? "bit" : "bif";
				}
			}
			int size = (fp ? ((sz & 1) ? 64 : 32) : (8 << sz));

			if (opname != nullptr) {
				if (!nosize) {
					snprintf(instr->text, sizeof(instr->text), "%s.%d %c%d, %c%d, %c%d", opname, size, r, Rd, r, Rn, r, Rm);
				} else {
					if (!strcmp(opname, "orr") && Rn == Rm) {
						snprintf(instr->text, sizeof(instr->text), "mov %c%d, %c%d", r, Rd, r, Rn);
					} else {
						snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opname, r, Rd, r, Rn, r, Rm);
					}
				}
			} else {
				snprintf(instr->text, sizeof(instr->text), "(asimd three-same %08x)", w);
			}
			break;
		}
		case 0:
			snprintf(instr->text, sizeof(instr->text), "(asimd three-different %08x)", w);
			break;
		case 2:
			if (((w >> 17) & 0xf) == 0) {
				int opcode = (w >> 12) & 0x1F;
				int sz = (w >> 22) & 3;
				int Q = GetQ(w);
				int U = GetU(w);
				const char *opname = nullptr;
				bool narrow = false;
				if (!U) {
					switch (opcode) {
					case 0: opname = "rev64"; break;
					case 1: opname = "rev16"; break;
					case 2: opname = "saddlp"; break;
					case 3: opname = "suqadd"; break;
					case 4: opname = "cls"; break;
					case 5: opname = "cnt"; break;
					case 6: opname = "sadalp"; break;
					case 7: opname = "sqabs"; break;
					case 8: opname = "cmgt"; break;
					case 9: opname = "cmeq"; break;
					case 0xA: opname = "cmlt"; break;
					case 0xB: opname = "abs"; break;
					case 0x12: opname = "xtn"; narrow = true; break;
					case 0x14: opname = "sqxtn"; narrow = true; break;
					default:
						if (!(sz & 0x2)) {
							switch (opcode) {
							case 0x16: opname = "fcvtn"; break;
							case 0x17: opname = "fcvtl"; break;
							case 0x18: opname = "frintn"; break;
							case 0x19: opname = "frintm"; break;
							case 0x1a: opname = "fcvtns"; break;
							case 0x1b: opname = "fcvtms"; break;
							case 0x1c: opname = "fcvtas"; break;
							case 0x1d: opname = "scvtf"; break;
							}
						} else {
							switch (opcode) {
							case 0xc: opname = "fcmgt"; break;
							case 0xd: opname = "fcmeq"; break;
							case 0xe: opname = "fcmlt"; break;
							case 0xf: opname = "fabs"; break;
							case 0x18: opname = "frintp"; break;
							case 0x19: opname = "frintz"; break;
							case 0x1a: opname = "fcvtps"; break;
							case 0x1b: opname = "fcvtzs"; break;
							case 0x1c: opname = "urepce"; break;
							case 0x1d: opname = "frepce"; break;
							}
						}
					}
				} else {
					switch (opcode) {
					case 0: opname = "rev32"; break;
					case 2: opname = "uaddlp"; break;
					case 3: opname = "usqadd"; break;
					case 4: opname = "clz"; break;
					case 6: opname = "uadalp"; break;
					case 7: opname = "sqneg"; break;
					case 8: opname = "cmge"; break; // with zero
					case 9: opname = "cmle"; break; // with zero
					case 0xB: opname = "neg"; break;
					case 0x12: opname = "sqxtun"; narrow = true; break;
					case 0x13: opname = "shll"; break;
					case 0x14: opname = "uqxtn"; narrow = true; break;
					case 5: if (sz == 0) opname = "not"; else opname = "rbit"; break;
					default:
						if (!(sz & 0x2)) {
							switch (opcode) {
							case 0x16: opname = "fcvtxn"; break;
							case 0x18: opname = "frinta"; break;
							case 0x19: opname = "frintx"; break;
							case 0x1a: opname = "fcvtnu"; break;
							case 0x1b: opname = "fcvtmu"; break;
							case 0x1c: opname = "fcvtau"; break;
							case 0x1d: opname = "ucvtf"; break;
							}
						} else {
							switch (opcode) {
							case 0xC: opname = "fcmge"; break;  // with zero
							case 0xD: opname = "fcmge"; break;  // with zero
							case 0xF: opname = "fneg"; break;
							case 0x19: opname = "frinti"; break;
							case 0x1a: opname = "fcvtpu"; break;
							case 0x1b: opname = "fcvtzu"; break;
							case 0x1c: opname = "ursqrte"; break;
							case 0x1d: opname = "frsqrte"; break;
							case 0x1f: opname = "fsqrt"; break;
							}
						}
					}
				}

				if (opname) {
					if (narrow) {
						int esize = 8 << sz;
						const char *two = "";  // todo
						snprintf(instr->text, sizeof(instr->text), "%s%s.%d.%d d%d, q%d", opname, two, esize, esize * 2, Rd, Rn);
					} else {
						snprintf(instr->text, sizeof(instr->text), "%s", opname);
					}
				} else {
					// Very similar to scalar two-reg misc. can we share code?
					snprintf(instr->text, sizeof(instr->text), "(asimd vector two-reg misc %08x)", w);
				}
			} else if (((w >> 17) & 0xf) == 1) {
				snprintf(instr->text, sizeof(instr->text), "(asimd across lanes %08x)", w);
			} else {
				goto bail;
			}
		}
	} else if (((w >> 21) & 0x4F9) == 0x70) {
		if (((w >> 10) & 0x21) == 1) {
			if (((w >> 11) & 3) == 3) {
				// From GPR
				snprintf(instr->text, sizeof(instr->text), "(asimd copy gpr %08x)", w);
			} else {
				int imm5 = (w >> 16) & 0x1F;
				int size = LowestSetBit(imm5, 5);
				int imm4 = (w >> 11) & 0xF;
				int dst_index = imm5 >> (size + 1);
				int src_index = imm4 >> size;
				int op = (w >> 29) & 1;
				char s = "bhsd"[size];
				if (op == 0 && imm4 == 0) {
					// DUP (element)
					int idxdsize = (imm5 & 8) ? 128 : 64;
					char r = "dq"[idxdsize == 128];
					snprintf(instr->text, sizeof(instr->text), "dup %c%d, %c%d.%c[%d]", r, Rd, r, Rn, s, dst_index);
				} else {
					int idxdsize = (imm4 & 8) ? 128 : 64;
					char r = "dq"[idxdsize == 128];
					snprintf(instr->text, sizeof(instr->text), "ins %c%d.%c[%d], %c%d.%c[%d]", r, Rd, s, dst_index, r, Rn, s, src_index);
				}
			}
		}
	} else if (((w >> 21) & 0x4F8) == 0x78) {
		if ((w >> 10) & 1) {
			if (((w >> 19) & 0xf) == 0) {
				snprintf(instr->text, sizeof(instr->text), "(asimd modified immediate %08x)", w);
			} else {
				bool Q = GetQ(w);
				bool U = GetU(w);
				int immh = (w >> 19) & 0xf;
				int immb = (w >> 16) & 7;
				int opcode = (w >> 11) & 0x1f;
				const char *opnamesU0[32] = {
					"sshr", 0, "ssra", 0,
					"srshr", 0, "srsra", 0,
					0, 0, "shl", 0,
					0, 0, "sqshl", 0,
					"shrn", "rshrn", "sqshrn", "sqrshrn",
					"sshll", 0, 0, 0,
					0, 0, 0, 0,
					"scvtf", 0, 0, "fcvtzs",
				};
				const char *opnamesU1[32] = {
					"ushr", 0, "usra", 0,
					"urshr", 0, "ursra", 0,
					"sri", 0, "sli", 0,
					"sqslu", 0, "uqshl", 0,
					"sqshrun", "sqrshrun", "uqshrn", "uqrshrn",
					"ushll", 0, 0, 0,
					0, 0, 0, 0,
					"ucvtf", 0, 0, "fcvtzu",
				};
				const char *opname = U ? opnamesU1[opcode] : opnamesU0[opcode];
				const char *two = Q ? "2" : "";  // TODO: This doesn't apply to all the ops
				if (opname && (!strcmp(opname, "scvtf") || !strcmp(opname, "ucvtf"))) {
					int esize = (8 << HighestSetBit(immh));
					int shift = 2 * esize - ((immh << 3) | immb);
					int r = Q ? 'q' : 'd';
					snprintf(instr->text, sizeof(instr->text), "%ccvtf %c%d.s, %c%d.s, #%d", U ? 'u' : 's', r, Rd, r, Rn, shift);
				} else if (opname && (!strcmp(opname, "ushr") || !strcmp(opname, "sshr"))) {
					int esize = (8 << HighestSetBit(immh));
					int shift = esize * 2 - ((immh << 3) | immb);
					int r = Q ? 'q' : 'd';
					snprintf(instr->text, sizeof(instr->text), "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, shift);
				} else if (opname && (!strcmp(opname, "rshrn") || !strcmp(opname, "shrn"))) {
					int esize = (8 << HighestSetBit(immh));
					int shift = esize * 2 - ((immh << 3) | immb);
					snprintf(instr->text, sizeof(instr->text), "%s%s.%d.%d d%d, q%d, #%d", opname, two, esize, esize * 2, Rd, Rn, shift);
				} else if (opname && (!strcmp(opname, "shl"))) {
					int esize = (8 << HighestSetBit(immh));
					int r = Q ? 'q' : 'd';
					int shift = ((immh << 3) | immb) - esize;
					snprintf(instr->text, sizeof(instr->text), "%s.%d %c%d, %c%d, #%d", opname, esize, r, Rd, r, Rn, shift);
				} else if (opname) {
					int esize = (8 << HighestSetBit(immh));
					int shift = ((immh << 3) | immb) - esize;
					if (shift == 0 && opcode == 0x14) {
						snprintf(instr->text, sizeof(instr->text), "%cxtl%s.%d.%d q%d, d%d", U ? 'u' : 's', two, esize * 2, esize, Rd, Rn);
					} else {
						snprintf(instr->text, sizeof(instr->text), "%s%s.%d.%d q%d, d%d, #%d", opname, two, esize * 2, esize, Rd, Rn, shift);
					}
				} else {
					snprintf(instr->text, sizeof(instr->text), "(asimd shift-by-immediate %08x)", w);
				}
			}
		} else {
			bool Q = GetQ(w);
			bool U = GetU(w);
			int size = (w >> 22) & 3;
			bool L = (w >> 21) & 1;
			bool M = (w >> 20) & 1;
			bool H = (w >> 11) & 1;
			int opcode = (w >> 12) & 0xf;
			if (size & 0x2) {
				const char *opname = 0;
				switch (opcode) {
				case 1: opname = "fmla"; break;
				case 5: opname = "fmls"; break;
				case 9: opname = "fmul"; break;
				}
				int index;
				if ((size & 1) == 0) {
					index = (H << 1) | (int)L;
				} else {
					index = H;
				}
				char r = Q ? 'q' : 'd';
				const char *arrangement = GetArrangement(Q, size & 1);
				snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d.%s[%d]", opname, r, Rd, r, Rn, r, Rm, arrangement, index);
			} else {
				snprintf(instr->text, sizeof(instr->text), "(asimd vector x indexed elem %08x)", w);
			}
		}
	} else {
		bail:
		snprintf(instr->text, sizeof(instr->text), "(FP1 %08x)", w);
	}
}

// (w >> 25) & 0xF  == f
static void FPandASIMD2(uint32_t w, uint64_t addr, Instruction *instr) {
	int Rd = w & 0x1f;
	int Rn = (w >> 5) & 0x1f;
	int Rm = (w >> 16) & 0x1f;
	int type = (w >> 22) & 0x3;
	int sf = (w >> 31);
	if (((w >> 21) & 0x2F9) == 0xF0) {
		int rmode = (w >> 19) & 3;
		int opcode = (w >> 16) & 7;
		int scale = 64 - ((w >> 10) & 0x3f);
		char fr = type == 1 ? 'd' : 's';
		char ir = sf == 1 ? 'x' : 'w';
		char sign = (opcode & 1) ? 'u' : 's';
		if (rmode == 0) {
			snprintf(instr->text, sizeof(instr->text), "%ccvtf %c%d, %c%d, #%d", sign, fr, Rd, ir, Rn, scale);
		} else if (rmode == 3) {
			snprintf(instr->text, sizeof(instr->text), "fcvtz%c %c%d, %c%d, #%d", sign, ir, Rd, fr, Rn, scale);
		} else {
			snprintf(instr->text, sizeof(instr->text), "(float<->fixed %08x)", w);
		}
	} else if (((w >> 21) & 0x2F9) == 0xF1) {
		int opcode = (w >> 16) & 7;
		if (((w >> 10) & 3) == 0) {
			if (((w >> 10) & 7) == 4) {
				uint8_t uimm8 = (w >> 13) & 0xff;
				float fl_imm = Arm64Gen::FPImm8ToFloat(uimm8);
				char fr = ((w >> 22) & 1) ? 'd' : 's';
				snprintf(instr->text, sizeof(instr->text), "fmov %c%d, #%f", fr, Rd, fl_imm);
			} else if (((w >> 10) & 0xf) == 8) {
				int opcode2 = w & 0x1f;
				int e = opcode2 >> 4;
				int z = (opcode2 >> 3) & 1;
				char r = type == 1 ? 'd' : 's';
				if (z) {
					snprintf(instr->text, sizeof(instr->text), "fcmp %c%d, #0.0", r, Rn);
				} else {
					snprintf(instr->text, sizeof(instr->text), "fcmp %c%d, %c%d", r, Rn, r, Rm);
				}
			} else if (((w >> 10) & 0x1f) == 0x10) {
				// snprintf(instr->text, sizeof(instr->text), "(data 1-source %08x)", w);
				const char *opnames[4] = { "fmov", "fabs", "fneg", "fsqrt" };
				int opc = (w >> 15) & 0x3;
				snprintf(instr->text, sizeof(instr->text), "%s s%d, s%d", opnames[opc], Rd, Rn);  // TODO: Support doubles too
			} else if (((w >> 10) & 0x1bf) == 0x180) {
				// Generalized FMOV
				char ir = sf ? 'x' : 'w';
				bool tosimd = (opcode & 0x1);
				char fr = ((w >> 22) & 1) ? 'd' : 's';
				if (tosimd) {
					snprintf(instr->text, sizeof(instr->text), "fmov %c%d, %c%d", fr, Rd, ir, Rn);
				} else {
					snprintf(instr->text, sizeof(instr->text), "fmov %c%d, %c%d", ir, Rd, fr, Rn);
				}
			} else if (((w >> 10) & 0x3f) == 0x0 && opcode == 0) {
				char ir = sf ? 'x' : 'w';
				char roundmode = "npmz"[(w >> 19) & 3];
				char fr = ((w >> 22) & 1) ? 'd' : 's';
				snprintf(instr->text, sizeof(instr->text), "fcvt%cs %c%d, %c%d", roundmode, ir, Rd, fr, Rn);
			} else if ((opcode & 6) == 2) {
				char ir = sf ? 'x' : 'w';
				char fr = ((w >> 22) & 1) ? 'd' : 's';
				char sign = (opcode & 1) ? 'u' : 's';
				snprintf(instr->text, sizeof(instr->text), "%ccvtf %c%d, %c%d", sign, fr, Rd, ir, Rn);
			}
		} else if (((w >> 10) & 3) == 1) {
			snprintf(instr->text, sizeof(instr->text), "(float cond compare %08x)", w);
		} else if (((w >> 10) & 3) == 2) {
			int opc = (w >> 12) & 0xf;
			const char *opnames[16] = { "fmul", "fdiv", "fadd", "fsub", "fmax", "fmin", "fmaxnm", "fminnm", "fnmul" };
			char r = ((w >> 22) & 1) ? 'd' : 's';
			snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d", opnames[opc] ? opnames[opc] : "?", r, Rd, r, Rn, r, Rm);
		} else if (((w >> 10) & 3) == 3) {
			char fr = ((w >> 22) & 1) ? 'd' : 's';
			int cond = (w >> 12) & 0xf;
			snprintf(instr->text, sizeof(instr->text), "fcsel %c%d, %c%d, %c%d, %s", fr, Rd, fr, Rn, fr, Rm, condnames[cond]);
		}
	} else if (((w >> 21) & 0x2F8) == 0xF8) {
		int opcode = ((w >> 15) & 1) | ((w >> 20) & 2);
		const char *opnames[9] = { "fmadd", "fmsub", "fnmadd", "fnmsub" };
		int size = (w >> 22) & 1;
		char r = size ? 'd' : 's';
		int Ra = (w >> 10) & 0x1f;
		snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d, %c%d, %c%d", opnames[opcode], r, Rd, r, Rn, r, Rm, r, Ra);
	} else if (((w >> 21) & 0x2F9) == 0x2F1) {
		if (((w >> 10) & 3) == 0) {
			snprintf(instr->text, sizeof(instr->text), "(asimd scalar three different %08x)", w);
		} else if (((w >> 10) & 1) == 1) {
			snprintf(instr->text, sizeof(instr->text), "(asimd scalar three same %08x)", w);
		} else if (((w >> 10) & 3) == 2) {
			// asimd scalar two-reg misc: This is a particularly sad and messy encoding :/
			if (((w >> 17) & 0xf) == 0) {
				int sz = (w >> 22) & 3;
				char r = (sz & 1) ? 'd' : 's';
				int opcode = (w >> 12) & 0x1f;
				bool U = ((w >> 29) & 1);
				const char *opname = NULL;
				bool zero = false;
				bool sign_suffix = false;
				bool sign_prefix = false;
				if ((sz & 2) == 0) {
					switch (opcode) {
					case 0x1a: opname = "fcvtn"; sign_suffix = true; break;
					case 0x1b: opname = "fcvtm"; sign_suffix = true; break;
					case 0x1c: opname = "fcvta"; sign_suffix = true; break;
					case 0x1d: opname = "cvtf"; sign_prefix = true;  break;
					}
				} else {
					if (U == 0) {
						switch (opcode) {
						case 0xC: opname = "fcmgt"; zero = true; break;
						case 0xD: opname = "fcmeq"; zero = true; break;
						case 0xE: opname = "fcmlt"; zero = true; break;
						case 0x1A: opname = "fcvtp"; sign_suffix = true;  break;
						case 0x1B: opname = "fcvtz"; sign_suffix = true;  break;
						}
					} else {
						switch (opcode) {
						case 0x1A: opname = "fcvtp"; sign_suffix = true;  break;
						case 0x1B: opname = "fcvtz"; sign_suffix = true;  break;
						}
					}
				}
				if (!opname) {  // These ignore size.
					if (U == 0) {
						switch (opcode) {
						case 3: opname = "suqadd"; break;
						case 7: opname = "sqabs"; break;
						case 8: opname = "cmgt"; zero = true;  break;
						case 9: opname = "cmeq"; zero = true;  break;
						case 0xa: opname = "cmlt"; zero = true;  break;
						case 0xb: opname = "abs"; break;
						case 0xc: opname = "sqxtn?"; break;
						}
					} else {
						switch (opcode) {
						case 3: opname = "usqadd"; break;
						case 7: opname = "sqneg"; break;
						case 8: opname = "cmge"; zero = true;  break;
						case 9: opname = "cmle"; zero = true;  break;
						case 0xB: opname = "neg"; break;
						case 0x12: opname = "sqxtun?"; break;
						case 0x14: opname = "uqxtn?"; break;
						}
					}
				}

				if (opname) {
					if (sign_suffix) {
						char sign = U ? 'u' : 's';
						snprintf(instr->text, sizeof(instr->text), "%s%c %c%d, %c%d", opname, sign, r, Rd, r, Rn);
					} else if (sign_prefix) {
						char sign = U ? 'u' : 's';
						snprintf(instr->text, sizeof(instr->text), "%c%s %c%d, %c%d", sign, opname, r, Rd, r, Rn);
					} else if (!zero) {
						snprintf(instr->text, sizeof(instr->text), "%s %c%d, %c%d", opname, r, Rd, r, Rn);
					} else if (zero) {
						snprintf(instr->text, sizeof(instr->text), "%s %c%d, #0.0", opname, r, Rd);
					}
				} else {
					snprintf(instr->text, sizeof(instr->text), "(asimd scalar two-reg misc %08x)", w);
				}
			} else if (((w >> 17) & 0xf) == 8) {
				snprintf(instr->text, sizeof(instr->text), "(asimd scalar pair-wise %08x)", w);
			} else {
				snprintf(instr->text, sizeof(instr->text), "(asimd scalar stuff %08x)", w);
			}
		} else {
			snprintf(instr->text, sizeof(instr->text), "(asimd stuff %08x)", w);
		}
	} else if (((w >> 21) & 0x2F1) == 0x2F0) {
		// many lines
		snprintf(instr->text, sizeof(instr->text), "(asimd stuff %08x)", w);
	} else {
		snprintf(instr->text, sizeof(instr->text), "(FP2 %08x)", w);
	}
}

static void DisassembleInstruction(uint32_t w, uint64_t addr, Instruction *instr, SymbolCallback symbolCallback) {
	memset(instr, 0, sizeof(*instr));
	
	// Identify the main encoding groups. See C3.1 A64 instruction index by encoding
	int id = (w >> 25) & 0xF;
	switch (id) {
	case 0: case 1: case 2: case 3: // 00xx
		instr->undefined = true;
		break;
	case 8: case 9:
		DataProcessingImmediate(w, addr, instr);
		break;
	case 0xA: case 0xB:
		BranchExceptionAndSystem(w, addr, instr, symbolCallback);
		break;
	case 4: case 6: case 0xC: case 0xE:
		LoadStore(w, addr, instr);
		break;
	case 5: case 0xD:
		DataProcessingRegister(w, addr, instr);
		break;
	case 7:
		FPandASIMD1(w, addr, instr);
		break;
	case 0xF:
		FPandASIMD2(w, addr, instr);
		break;
	}
}

void Arm64Dis(uint64_t addr, uint32_t w, char *output, int bufsize, bool includeWord, SymbolCallback symbolCallback) {
	Instruction instr;
	DisassembleInstruction(w, addr, &instr, symbolCallback);
	char temp[256];
	if (includeWord) {
		snprintf(output, bufsize, "%08x\t%s", w, instr.text);
	} else {
		snprintf(output, bufsize, "%s", instr.text);
	}
	if (instr.undefined || instr.badbits || instr.oddbits) {
		if (instr.undefined) snprintf(output, bufsize, "%08x\t[undefined instr]", w);
		if (instr.badbits) snprintf(output, bufsize, "%08x\t[illegal bits]", w);

		// strcat(output, " ? (extra bits)");  
		if (instr.oddbits) {
			snprintf(temp, sizeof(temp), " [unexpected bits %08x]", w);
			strcat(output, temp);
		}
	}
	// zap tabs
	while (*output) {
		if (*output == '\t')
			*output = ' ';
		output++;
	}
}
