// SPDX-FileCopyrightText: 2012-2016 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include <string.h>
#include <rz_types.h>
#include <rz_lib.h>
#include <rz_asm.h>
#include <rz_analysis.h>

#define ARC_REG_ILINK1 0x1d
#define ARC_REG_ILINK2 0x1e
#define ARC_REG_BLINK  0x1f
#define ARC_REG_LIMM   0x3e
#define ARC_REG_PCL    0x3f

/* the CPU fields that we decode get stored in this struct */
typedef struct arc_fields_t {
	ut8 opcode; /* major opcode */
	ut8 subopcode; /* sub opcode */
	ut8 format; /* operand format */
	ut8 format2;
	ut8 cond;
	ut16 a; /* destination register */
	ut16 b; /* source/destination register */
	ut16 c; /* source/destintaion register */
	ut8 mode_aa;
	ut8 mode_zz;
	ut8 mode_m;
	ut8 mode_n; /* Delay slot flag */
	st64 imm; /* data stored in the opcode */
	st64 limm; /* data stored immediately following the opcode */
} arc_fields;

/* For (arguably valid) reasons, the ARCompact CPU uses "middle endian"
	encoding on Little-Endian systems
 */
static inline ut32 rz_read_me32_arc(const void *src) {
	const ut8 *s = src;
	return (((ut32)s[1]) << 24) | (((ut32)s[0]) << 16) | (((ut32)s[3]) << 8) | (((ut32)s[2]) << 0);
}

static int sign_ext(int bits, int imm) {
	int maxsint = (1 << (bits - 1)) - 1;
	int maxuint = (1 << (bits)) - 1;

	if (imm > maxsint) {
		/* sign extend */
		imm = -maxuint + imm - 1;
	}
	return imm;
}

#define SIGN_EXT_S7(imm)  sign_ext(7, imm);
#define SIGN_EXT_S8(imm)  sign_ext(8, imm);
#define SIGN_EXT_S9(imm)  sign_ext(9, imm);
#define SIGN_EXT_S10(imm) sign_ext(10, imm);
#define SIGN_EXT_S12(imm) sign_ext(12, imm);
#define SIGN_EXT_S13(imm) sign_ext(13, imm);
#define SIGN_EXT_S21(imm) sign_ext(21, imm);
#define SIGN_EXT_S25(imm) sign_ext(25, imm);

static int map_cond2rizin(ut8 cond) {
	switch (cond) {
	case 0: return RZ_TYPE_COND_AL;
	case 1: return RZ_TYPE_COND_EQ;
	case 2: return RZ_TYPE_COND_NE;
	case 3: return RZ_TYPE_COND_PL;
	case 4: return RZ_TYPE_COND_MI;
	case 7: return RZ_TYPE_COND_VS;
	case 8: return RZ_TYPE_COND_VC;
	case 9: return RZ_TYPE_COND_GT;
	case 0xa: return RZ_TYPE_COND_GE;
	case 0xb: return RZ_TYPE_COND_LT;
	case 0xc: return RZ_TYPE_COND_LE;
	case 0xd: return RZ_TYPE_COND_HI;
	case 0xe: return RZ_TYPE_COND_LS;
#if 0
	/* TODO: */
	/* - rizin defines RZ_TYPE_COND_LO as carry clear and _HS as carry set */
	/*   which appears different to the ARC definitions. */
	/*   Need to do some math and double check the details */
	case 5: return RZ_TYPE_COND_?? - CS,C,LO - Carry set & LO
	case 6: return RZ_TYPE_COND_?? - CC,NC,HS - Carry clear & HS
	/* - Positive non-zero doesnt map to any Radare cond code.  Perhaps just add it? */
	case 0xf: return RZ_TYPE_COND_?? - PNZ - Positive non-zero
#endif
	}
	return -1;
}

static void arcompact_jump(RzAnalysisOp *op, ut64 addr, ut64 jump, ut8 delay) {
	op->jump = jump;
	op->fail = addr + op->size;
	op->delay = delay;
}

static void arcompact_jump_cond(RzAnalysisOp *op, ut64 addr, ut64 jump, ut8 delay, ut8 cond) {
	arcompact_jump(op, addr, jump, delay);
	op->cond = map_cond2rizin(cond);
}

static void arcompact_branch(RzAnalysisOp *op, ut64 addr, st64 offset, ut8 delay) {
	arcompact_jump(op, addr, (addr & ~3) + offset, delay);
}

static void map_zz2refptr(RzAnalysisOp *op, ut8 mode_zz) {
	switch (mode_zz) {
	case 0: op->refptr = 4; break;
	case 1: op->refptr = 1; break;
	case 2: op->refptr = 2; break;
	default:
		op->type = RZ_ANALYSIS_OP_TYPE_ILL;
		break;
	}
}

static int arcompact_genops_jmp(RzAnalysisOp *op, ut64 addr, arc_fields *f, ut64 basic_type) {
	ut64 type_ujmp;
	ut64 type_cjmp;
	ut64 type_ucjmp;

	switch (basic_type) {
	case RZ_ANALYSIS_OP_TYPE_JMP:
		type_ujmp = RZ_ANALYSIS_OP_TYPE_UJMP;
		type_cjmp = RZ_ANALYSIS_OP_TYPE_CJMP;
		type_ucjmp = RZ_ANALYSIS_OP_TYPE_UCJMP;
		break;
	case RZ_ANALYSIS_OP_TYPE_CALL:
		type_ujmp = RZ_ANALYSIS_OP_TYPE_UCALL;
		type_cjmp = RZ_ANALYSIS_OP_TYPE_CCALL;
		type_ucjmp = RZ_ANALYSIS_OP_TYPE_UCCALL;
		break;
	default:
		return -1; /* Should not happen */
	}

	f->cond = f->a & 0x1f;

	switch (f->format) {
	case 0: /* unconditional jumps via reg or long imm */
		if (f->c == ARC_REG_LIMM) {
			/* limm */
			op->type = basic_type;
			arcompact_jump(op, addr, f->limm, f->mode_n);
			return op->size;
		}
		if (f->c == ARC_REG_ILINK1 || f->c == ARC_REG_ILINK2 || f->c == ARC_REG_BLINK) {
			/* ilink1, ilink2, blink */
			/* Note: not valid for basic_type == CALL */
			op->type = RZ_ANALYSIS_OP_TYPE_RET;
			op->delay = f->mode_n;
			return op->size;
		}
		op->type = type_ujmp;
		return op->size;
	case 1: /* unconditional jumps via u6 imm */
		op->type = basic_type;
		arcompact_jump(op, addr, f->c, f->mode_n);
		return op->size;
	case 2: /* unconditional jumps via s12 imm */
		op->type = basic_type;
		f->imm = (f->a << 6 | f->c);
		f->imm = SIGN_EXT_S12(f->imm);
		arcompact_jump(op, addr, f->imm, f->mode_n);
		return op->size;
	case 3: /* conditional jumps */
		if (f->mode_m == 0) {
			if (f->c == ARC_REG_LIMM) {
				op->type = type_cjmp;
				arcompact_jump_cond(op, addr, f->limm, f->mode_n, f->cond);
				return op->size;
			}
			if (f->c == ARC_REG_ILINK1 || f->c == ARC_REG_ILINK2 || f->c == ARC_REG_BLINK) {
				/* ilink1, ilink2, blink */
				/* Note: not valid for basic_type == CALL */
				op->type = RZ_ANALYSIS_OP_TYPE_CRET;
				op->cond = map_cond2rizin(f->cond);
				op->delay = f->mode_n;
				return op->size;
			}

			op->cond = map_cond2rizin(f->cond);
			op->type = type_ucjmp;
			return op->size;
		}

		op->type = type_cjmp;
		arcompact_jump_cond(op, addr, f->c, f->mode_n, f->cond);
		return op->size;
	}

	/* should not be reached */
	return 0;
}

static int arcompact_genops(RzAnalysisOp *op, ut64 addr, ut32 words[2]) {
	arc_fields fields = { 0 };

	fields.format = (words[0] & 0x00c00000) >> 22;
	fields.subopcode = (words[0] & 0x003f0000) >> 16;
	fields.c = (words[0] & 0x00000fc0) >> 6;
	fields.a = (words[0] & 0x0000003f);
	fields.b = (words[0] & 0x07000000) >> 24 | (words[0] & 0x7000) >> 9;
	fields.mode_n = 0;

	/* increase the size to cover any limm reg fields */
	if (fields.b == ARC_REG_LIMM) {
		op->size = 8;
		fields.limm = words[1];
		/* FIXME: MOV<.f> 0,x is encoded as fields.b == ARC_REG_LIMM, but no limm */
	} else if ((fields.format == 0 || fields.format == 1) && (fields.a == ARC_REG_LIMM)) {
		op->size = 8;
		fields.limm = words[1];
	} else if ((fields.format == 0) && (fields.c == ARC_REG_LIMM)) {
		op->size = 8;
		fields.limm = words[1];
	} else if ((fields.format == 3) && ((fields.a & 0x20) == 0x20) && (fields.c == ARC_REG_LIMM)) {
		op->size = 8;
		fields.limm = words[1];
	}

	if (fields.format == 1) {
		/* REG_U6IMM */
		fields.imm = fields.c;
	} else if (fields.format == 2) {
		/* REG_S12IMM */
		fields.imm = SIGN_EXT_S12(fields.c | fields.a << 6);
	}

	switch (fields.subopcode) {
	case 0x00: /* add */
		if ((fields.format == 1 || fields.format == 2) && fields.b == ARC_REG_PCL) {
			/* dst = PCL + src */
			op->ptr = (addr & ~3) + fields.imm;
			op->refptr = 1; /* HACK! we don't actually know what size it is */
		}
		// fallthrough
	case 0x01: /* add with carry */
	case 0x14: /* add with left shift by 1 */
	case 0x15: /* add with left shift by 2 */
	case 0x16: /* add with left shift by 3 */
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		break;
	case 0x02: /* subtract */
	case 0x03: /* subtract with carry */
	case 0x0e: /* reverse subtract */
	case 0x17: /* subtract with left shift by 1 */
	case 0x18: /* subtract with left shift by 2 */
	case 0x19: /* subtract with left shift by 3 */
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		break;
	case 0x04: /* logical bitwise AND */
	case 0x06: /* logical bitwise AND with invert */
	case 0x10: /* bit clear */
	case 0x13: /* bit mask */
		op->type = RZ_ANALYSIS_OP_TYPE_AND;
		break;
	case 0x05: /* logical bitwise OR */
	case 0x0f: /* bit set */
		op->type = RZ_ANALYSIS_OP_TYPE_OR;
		break;
	case 0x07: /* logical bitwise exclusive-OR */
	case 0x12: /* bit xor */
		op->type = RZ_ANALYSIS_OP_TYPE_XOR;
		break;
	case 0x08: /* larger of 2 signed integers */
	case 0x09: /* smaller of 2 signed integers */
		op->type = RZ_ANALYSIS_OP_TYPE_CMOV;
		break;
	case 0x0a: /* move */
		if (fields.format == 2) {
			op->type = RZ_ANALYSIS_OP_TYPE_MOV;
			op->val = SIGN_EXT_S12(fields.a << 6 | fields.c);
		} else if (fields.format == 3) {
			fields.cond = fields.a & 0x1f;
			op->cond = map_cond2rizin(fields.cond);
			op->type = RZ_ANALYSIS_OP_TYPE_CMOV;
			if ((fields.a & 0x20)) {
				/* its a move from imm u6 */
				op->val = fields.c;
			} else if (fields.c == ARC_REG_LIMM) {
				/* its a move from limm */
				op->val = fields.limm;
			}
		}
		break;
	case 0x0b: /* test */
	case 0x0c: /* compare */
	case 0x0d: /* reverse compare */
	case 0x11: /* bit test */
		op->type = RZ_ANALYSIS_OP_TYPE_CMP;
		break;
	case 0x1a: /* 32 X 32 signed multiply */
	case 0x1b: /* 32 X 32 signed multiply */
	case 0x1c: /* 32 X 32 unsigned multiply */
	case 0x1d: /* 32 X 32 unsigned multiply */
		op->type = RZ_ANALYSIS_OP_TYPE_MUL;
		break;
	case 0x21: /* Jump with delay slot */
		fields.mode_n = 1;
	/* fall through */
	case 0x20: /* Jump */
		fields.mode_m = (words[0] & 0x20) >> 5;
		arcompact_genops_jmp(op, addr, &fields, RZ_ANALYSIS_OP_TYPE_JMP);
		break;
	case 0x23: /* jump and link with delay slot */
		fields.mode_n = 1;
	/* fall through */
	case 0x22: /* jump and link */
		fields.mode_m = (words[0] & 0x20) >> 5;
		arcompact_genops_jmp(op, addr, &fields, RZ_ANALYSIS_OP_TYPE_CALL);
		break;
	case 0x1e: /* Reserved */
	case 0x1f: /* Reserved */
	case 0x24: /* Reserved */
	case 0x25: /* Reserved */
	case 0x26: /* Reserved */
	case 0x27: /* Reserved */
	case 0x2c: /* Reserved */
	case 0x2d: /* Reserved */
	case 0x2e: /* Reserved */
	case 0x38: /* Reserved */
	case 0x39: /* Reserved */
	case 0x3a: /* Reserved */
	case 0x3b: /* Reserved */
	case 0x3c: /* Reserved */
	case 0x3d: /* Reserved */
	case 0x3e: /* Reserved */
	case 0x3f: /* Reserved */
		op->type = RZ_ANALYSIS_OP_TYPE_ILL;
		break;
	case 0x28: /* loop (16-bit aligned target address) */
		/* this is essentially a COME FROM instruction!! */
		/* TODO: describe it to rizin better? */
		switch (fields.format) {
		case 2: /* Loop Set Up (Unconditional) */
			fields.imm = SIGN_EXT_S13((fields.c | (fields.a << 6)) << 1);
			op->jump = (addr & ~3) + fields.imm;
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
			op->fail = addr + op->size;
			break;
		case 3: /* Loop Set Up (Conditional) */
			fields.imm = fields.c << 1;
			fields.cond = fields.a & 0x1f;
			op->cond = map_cond2rizin(fields.a & 0x1f);
			op->jump = (addr & ~3) + fields.imm;
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
			op->fail = addr + op->size;
			break;
		default:
			op->type = RZ_ANALYSIS_OP_TYPE_ILL;
			break;
		}
		break;
	case 0x29: /* set status flags */
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		break;
	case 0x2a: /* load from auxiliary register. */
	case 0x2b: /* store to auxiliary register. */
		op->type = RZ_ANALYSIS_OP_TYPE_IO;
		break;
	case 0x2f: /* Single Operand Instructions, 0x04, [0x2F, 0x00 - 0x3F] */
		switch (fields.a) {
		case 0: /* Arithmetic shift left by one */
			op->type = RZ_ANALYSIS_OP_TYPE_SAL;
			break;
		case 1: /* Arithmetic shift right by one */
			op->type = RZ_ANALYSIS_OP_TYPE_SAR;
			break;
		case 2: /* Logical shift right by one */
			op->type = RZ_ANALYSIS_OP_TYPE_SHR;
			break;
		case 3: /* Rotate right */
		case 4: /* Rotate right through carry */
			op->type = RZ_ANALYSIS_OP_TYPE_ROR;
			break;
		case 5: /* Sign extend byte */
		case 6: /* Sign extend word */
		case 7: /* Zero extend byte */
		case 8: /* Zero extend word */
			// op->type = RZ_ANALYSIS_OP_TYPE_UNK;
			op->type = RZ_ANALYSIS_OP_TYPE_MOV;
			/* TODO: a better encoding for SIGN and ZERO EXTEND instructions */
			break;
		case 9: /* Absolute */
			op->type = RZ_ANALYSIS_OP_TYPE_ABS;
			break;
		case 0xa: /* Logical NOT */
			op->type = RZ_ANALYSIS_OP_TYPE_NOT;
			break;
		case 0xb: /* Rotate left through carry */
			op->type = RZ_ANALYSIS_OP_TYPE_ROL;
			break;
		case 0xc: /* Atomic Exchange */
			op->type = RZ_ANALYSIS_OP_TYPE_XCHG;
			break;
		case 0x3f: /* See Zero operand (ZOP) table */
			switch (fields.b) {
			case 1: /* Sleep */
				/* TODO: a better encoding for this */
				op->type = RZ_ANALYSIS_OP_TYPE_NULL;
				break;
			case 2: /* Software interrupt */
				op->type = RZ_ANALYSIS_OP_TYPE_SWI;
				break;
			case 3: /* Wait for all data-based memory transactions to complete */
				/* TODO: a better encoding for this */
				op->type = RZ_ANALYSIS_OP_TYPE_NULL;
				break;
			case 4: /* Return from interrupt/exception */
				op->type = RZ_ANALYSIS_OP_TYPE_RET;
				break;
			case 5: /* Breakpoint instruction */
				op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
				break;
			default:
				op->type = RZ_ANALYSIS_OP_TYPE_ILL;
				break;
			}
			break;
		default:
			op->type = RZ_ANALYSIS_OP_TYPE_ILL;
			break;
		}
		break;
	case 0x30:
	case 0x31:
	case 0x32:
	case 0x33:
	case 0x34:
	case 0x35:
	case 0x36:
	case 0x37: /* Load Register-Register, 0x04, [0x30 - 0x37] */
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		break;
	}

	return op->size;
}

static int arcompact_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len) {
	ut32 words[2]; /* storage for the de-swizled opcode data */
	arc_fields fields;

	/* ARCompact ISA, including */
	/* ARCtangent-A5, ARC 600, ARC 700 */

	/* no unaligned code */
	if (addr % 2 != 0) {
		/* this fixes some of the reverse disassembly issues */
		op->type = RZ_ANALYSIS_OP_TYPE_ILL;
		return 0;
	}
	if (len < 8) {
		// when rz_read_me32_arc/be32 oob read
		return 0;
	}

	op->type = RZ_ANALYSIS_OP_TYPE_UNK;
	op->ptr = UT64_MAX;
	op->val = UT64_MAX;
	op->jump = UT64_MAX;
	op->fail = UT64_MAX;
	op->refptr = 0;
	op->delay = 0;

	if (analysis->big_endian) {
		words[0] = rz_read_be32(&data[0]);
		words[1] = rz_read_be32(&data[4]);
	} else {
		words[0] = rz_read_me32_arc(&data[0]);
		words[1] = rz_read_me32_arc(&data[4]);
	}

	fields.opcode = (words[0] & 0xf8000000) >> 27;

	op->size = (fields.opcode >= 0x0c) ? 2 : 4;
	op->nopcode = op->size;

	switch (fields.opcode) {
	case 0:
		fields.format = (words[0] & 0x00010000) >> 16;
		fields.a = (words[0] & 0x07fe0000) >> 17;
		fields.b = (words[0] & 0x0000ffc0) >> 6;
		fields.c = (words[0] & 0x0000000f);
		fields.mode_n = (words[0] & 0x20) >> 5;
		fields.limm = fields.a << 1 | fields.b << 11;

		if (fields.format == 0) {
			/* Branch Conditionally 0x00 [0x0] */
			fields.limm = SIGN_EXT_S21(fields.limm);
			fields.cond = (words[0] & 0x1f);
			op->cond = map_cond2rizin(fields.cond);
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		} else {
			/* Branch Unconditional Far 0x00 [0x1] */
			fields.limm |= (fields.c & 0x0f) << 21;
			/* the  & 0xf clearly shows we don't overflow */
			/* TODO: don't generate code to show this */
			fields.limm = SIGN_EXT_S25(fields.limm);
			op->type = RZ_ANALYSIS_OP_TYPE_JMP;
		}
		arcompact_branch(op, addr, fields.limm, fields.mode_n);
		break;
	case 1:
		fields.format = (words[0] & 0x00010000) >> 16;
		fields.mode_n = (words[0] & 0x20) >> 5;

		if (fields.format == 1) {
			fields.format2 = (words[0] & 0x10) >> 4;
			fields.subopcode = (words[0] & 0x0f);
			fields.b = (words[0] & 0x07000000) >> 24 | (words[0] & 0x7000) >> 9;
			fields.c = (words[0] & 0x00000fc0) >> 6;
			fields.imm = SIGN_EXT_S9((words[0] & 0x00fe0000) >> 16 | (words[0] & 0x8000) >> 7);
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;

			if (fields.format2 == 0) {
				/* Branch on Compare Register-Register, 0x01, [0x1, 0x0] */
				if (fields.b == ARC_REG_LIMM || fields.c == ARC_REG_LIMM) {
					op->size = 8;
					fields.limm = words[1];
				}
				/* TODO: cond codes (using the "br" mapping) */
			} else {
				/* Branch on Compare/Bit Test Register-Immediate, 0x01, [0x1, 0x1] */
				/* TODO: cond codes and imm u6 (using the "br" mapping) */
			}
			arcompact_branch(op, addr, fields.imm, fields.mode_n);
		} else {
			fields.format2 = (words[0] & 0x00020000) >> 17;
			fields.a = (words[0] & 0x07fc0000) >> 18;
			fields.b = (words[0] & 0x0000ffc0) >> 6;
			fields.c = (words[0] & 0x0000000f);
			fields.imm = fields.a << 2 | fields.b << 11;

			if (fields.format2 == 0) {
				/* Branch and Link Conditionally, 0x01, [0x0, 0x0] */
				fields.imm = SIGN_EXT_S21(fields.imm);
				fields.cond = (words[0] & 0x1f);
				op->cond = map_cond2rizin(fields.cond);
				op->type = RZ_ANALYSIS_OP_TYPE_CCALL;
			} else {
				/* Branch and Link Unconditional Far, 0x01, [0x0, 0x1] */
				fields.imm |= (fields.c & 0x0f) << 21;
				/* the  & 0xf clearly shows we don't overflow */
				/* TODO: don't generate code to show this */
				fields.imm = SIGN_EXT_S25(fields.imm);
				op->type = RZ_ANALYSIS_OP_TYPE_CALL;
			}
			arcompact_branch(op, addr, fields.imm, fields.mode_n);
		}
		break;
	case 2: /* Load Register with Offset, 0x02 */
		fields.a = (words[0] & 0x0000003f);
		fields.b = (words[0] & 0x07000000) >> 24 | (words[0] & 0x7000) >> 9;
		fields.imm = SIGN_EXT_S9((words[0] & 0x00ff0000) >> 16 | (words[0] & 0x8000) >> 7);
		/* fields.mode_aa = (words[0] & 0x600) >> 9; */
		fields.mode_zz = (words[0] & 0x180) >> 7;

		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;

		/* dst (fields.a) cannot be an extension core register */
		if (fields.a == ARC_REG_PCL || fields.a == 61 || (fields.a >= 0x20 && fields.a <= 0x2b)) {
			op->type = RZ_ANALYSIS_OP_TYPE_ILL;
		}

		map_zz2refptr(op, fields.mode_zz);

		if (fields.b == ARC_REG_LIMM) {
			op->size = 8;
			fields.limm = words[1];
			op->ptr = fields.limm + fields.imm;
			/* fields.aa is reserved - and ignored with limm */
		} else if (fields.b == ARC_REG_PCL) { /* PCL */
			op->ptr = (addr & ~3) + fields.imm;
		}
		/* TODO: set op with GP,FP,SP src/dst details */
		break;
	case 3: /* Store Register with Offset, 0x03 */
		fields.c = (words[0] & 0xfc0) >> 6;
		fields.b = (words[0] & 0x07000000) >> 24 | (words[0] & 0x7000) >> 9;
		fields.imm = SIGN_EXT_S9((words[0] & 0x00ff0000) >> 16 | (words[0] & 0x8000) >> 7);
		/* ut8 mode_aa = (words[0] & 0x18) >> 3; */
		fields.mode_zz = (words[0] & 0x6) >> 1;

		op->type = RZ_ANALYSIS_OP_TYPE_STORE;

		map_zz2refptr(op, fields.mode_zz);

		if (fields.b == ARC_REG_LIMM) {
			op->size = 8;
			fields.limm = words[1];
			op->ptr = fields.limm;
		} else if (fields.c == ARC_REG_LIMM) {
			op->size = 8;
			fields.limm = words[1];
			op->val = fields.limm;
		}

		if (fields.b == ARC_REG_PCL) { /* PCL */
			op->ptr = (addr & ~3) + fields.imm;
		}
		/* TODO: set op with GP,FP,SP src/dst details */
		break;
	case 4: /* General Operations */
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		return arcompact_genops(op, addr, words);
	case 5:
	case 6:
	case 7:
	case 8: /* 32-bit Extension Instructions, 0x05 - 0x08 */
		fields.subopcode = (words[0] & 0x003f0000) >> 16;
		fields.format = (words[0] & 0x00c00000) >> 22;
		fields.c = (words[0] & 0x00000fc0) >> 6;
		fields.a = (words[0] & 0x0000003f);
		fields.b = (words[0] & 0x07000000) >> 24 | (words[0] & 0x7000) >> 9;

		if (fields.b == ARC_REG_LIMM) {
			op->size = 8;
			fields.limm = words[1];
		} else if ((fields.format == 0 || fields.format == 1) && (fields.a == ARC_REG_LIMM)) {
			op->size = 8;
			fields.limm = words[1];
		} else if ((fields.format == 0) && (fields.c == ARC_REG_LIMM)) {
			op->size = 8;
			fields.limm = words[1];
		} else if ((fields.format == 3) && ((fields.a & 0x20) == 0x20) && (fields.c == ARC_REG_LIMM)) {
			op->size = 8;
			fields.limm = words[1];
		}
		/* TODO: fill in the extansion functions */
		// op->type = RZ_ANALYSIS_OP_TYPE_UNK;
		// op->type = RZ_ANALYSIS_OP_TYPE_SHL;
		op->type = RZ_ANALYSIS_OP_TYPE_SHR;
		break;
	case 0x09:
	case 0x0a:
	case 0x0b: /* Market Specific Extension Instructions, 0x09 - 0x0B */
		op->type = RZ_ANALYSIS_OP_TYPE_UNK;
		break;
	case 0x0c: /* Load /Add Register-Register, 0x0C, [0x00 - 0x03] */
		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		fields.subopcode = (words[0] & 0x00180000) >> 19;
		/* fields.a	= (words[0] & 0x00070000) >> 16; */
		/* fields.c	= (words[0] & 0x00e00000) >> 21; */
		/* fields.b	= (words[0] & 0x07000000) >> 24; */

		switch (fields.subopcode) {
		case 0: /* Load long word (reg.+reg.) */
		case 1: /* Load unsigned byte (reg.+reg.) */
		case 2: /* Load unsigned word (reg.+reg.) */
			op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
			break;
		case 3: /* Add */
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
			break;
		}
		break;
	case 0x0d: /* Add/Sub/Shift Register-Immediate, 0x0D, [0x00 - 0x03] */
		fields.subopcode = (words[0] & 0x00180000) >> 19;
		/* fields.imm = (words[0] & 0x00070000) >> 16; src2 u3 */
		/* fields.c = (words[0] & 0x00e00000) >> 21; dst */
		/* fields.b = (words[0] & 0x07000000) >> 24; src1 */

		switch (fields.subopcode) {
		case 0: /* Add */
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
			break;
		case 1: /* Subtract */
			op->type = RZ_ANALYSIS_OP_TYPE_SUB;
			break;
		case 2: /* Multiple arithmetic shift left */
			op->type = RZ_ANALYSIS_OP_TYPE_SHL;
			break;
		case 3: /* Multiple arithmetic shift right */
			op->type = RZ_ANALYSIS_OP_TYPE_SHR;
			break;
		}
		break;
	case 0x0e: /* Mov/Cmp/Add with High Register, 0x0E, [0x00 - 0x03] */
		fields.subopcode = (words[0] & 0x00180000) >> 19;
		/* fields.b	= (words[0] & 0x07000000) >> 24; dst, src1 */
		fields.c = (words[0] & 0x00e00000) >> 21 | (words[0] & 0x00070000) >> 13; /* src2 */

		if (fields.c == ARC_REG_LIMM) {
			op->size = 6;
			op->val = (words[0] & 0x0000ffff) << 16 | (words[1] & 0xffff0000) >> 16;
		}

		switch (fields.subopcode) {
		case 0: /* Add */
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
			break;
		case 1: /* Move */
		case 3: /* Move */
			op->type = RZ_ANALYSIS_OP_TYPE_MOV;
			break;
		case 2: /* Compare */
			op->type = RZ_ANALYSIS_OP_TYPE_CMP;
			break;
		}
		break;
	case 0xf: /* General Register Format Instructions, 0x0F, [0x00 - 0x1F] */
		fields.subopcode = (words[0] & 0x001f0000) >> 16;
		fields.c = (words[0] & 0x00e00000) >> (16 + 5);
		fields.b = (words[0] & 0x07000000) >> (16 + 8);

		switch (fields.subopcode) {
		case 0: /* Single Operand, Jumps and Special Format Instructions, 0x0F, [0x00, 0x00 - 0x07] */
			switch (fields.c) {
			case 0: /* J_S [r]*/
				op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
				arcompact_jump(op, 0, 0, 0);
				break;
			case 1: /* J_S.D [r] */
				op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
				arcompact_jump(op, 0, 0, 1);
				break;
			case 2: /* JL_S [r] */
				op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
				arcompact_jump(op, 0, 0, 0);
				break;
			case 3: /* JL_S.D [r] */
				op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
				arcompact_jump(op, 0, 0, 1);
				break;
			case 4:
			case 5: /* Reserved - instruction error */
				op->type = RZ_ANALYSIS_OP_TYPE_ILL;
				break;
			case 6: /* SUB_S.NE [b] */
				op->cond = RZ_TYPE_COND_NE;
				op->type = RZ_ANALYSIS_OP_TYPE_SUB;
				break;
			case 7: /* Zero Operand Instructions, 0x0F, [0x00, 0x07, 0x00 - 0x07] */
				switch (fields.b) {
				case 0: /* nop_s */
					op->type = RZ_ANALYSIS_OP_TYPE_NOP;
					op->size = 4;
					break;
				case 1:
				case 2:
				case 3: /* unimplemented and Reserved - instruction error */
					op->type = RZ_ANALYSIS_OP_TYPE_ILL;
					break;
				case 4: /* JEQ_S [blink] */
					op->cond = RZ_TYPE_COND_EQ;
					op->type = RZ_ANALYSIS_OP_TYPE_CRET;
					break;
				case 5: /* JNE_S [blink] */
					op->cond = RZ_TYPE_COND_NE;
					op->type = RZ_ANALYSIS_OP_TYPE_CRET;
					break;
				case 7: /* J_S.D [blink] */
					op->delay = 1;
				/* fall through */
				case 6: /* J_S [blink] */
					op->type = RZ_ANALYSIS_OP_TYPE_RET;
					break;
				}
				break;
			}
			break;
		case 1:
		case 3:
		case 8:
		case 9:
		case 0xa:
		case 0x17: /* Reserved - instruction error */
			op->type = RZ_ANALYSIS_OP_TYPE_ILL;
			break;
		case 2:
			op->type = RZ_ANALYSIS_OP_TYPE_SUB;
			break;
		case 4:
			op->type = RZ_ANALYSIS_OP_TYPE_AND;
			break;
		case 5:
			op->type = RZ_ANALYSIS_OP_TYPE_OR;
			break;
		case 6: /* Logical bitwise AND with invert */
			/* dest = src1 AND NOT src2 */
			op->type = RZ_ANALYSIS_OP_TYPE_AND;
			break;
		case 7:
			op->type = RZ_ANALYSIS_OP_TYPE_XOR;
			break;
		case 0xb: /* Test */
			/* no dst, b AND c */
			op->type = RZ_ANALYSIS_OP_TYPE_AND;
			break;
		case 0xc:
			op->type = RZ_ANALYSIS_OP_TYPE_MUL;
			break;
		case 0xd: /* Sign extend byte */
		case 0xe: /* Sign extend word */
		case 0xf: /* Zero extend byte */
		case 0x10: /* Zero extend word */
		case 0x13: /* Negate */
			op->type = RZ_ANALYSIS_OP_TYPE_CPL;
			break;
		case 0x11:
			op->type = RZ_ANALYSIS_OP_TYPE_ABS;
			break;
		case 0x12:
			op->type = RZ_ANALYSIS_OP_TYPE_NOT;
			break;
		case 0x14: /* Add with left shift by 1 */
		case 0x15: /* Add with left shift by 2 */
		case 0x16: /* Add with left shift by 3 */
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
			break;
		case 0x18: /* Multiple arithmetic shift left */
			op->type = RZ_ANALYSIS_OP_TYPE_SAL;
			break;
		case 0x19: /* Multiple logical shift right */
			op->type = RZ_ANALYSIS_OP_TYPE_SHR;
			break;
		case 0x1a: /* Multiple arithmetic shift right */
			op->type = RZ_ANALYSIS_OP_TYPE_SAR;
			break;
		case 0x1b: /* Arithmetic shift left by one */
			op->type = RZ_ANALYSIS_OP_TYPE_SAL;
			break;
		case 0x1c: /* Arithmetic shift right by one */
			op->type = RZ_ANALYSIS_OP_TYPE_SAR;
			break;
		case 0x1d: /* Logical shift right by one */
			op->type = RZ_ANALYSIS_OP_TYPE_SHL;
			break;
		case 0x1e:
			op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
			/* TODO: the description sounds more like a */
			/* RZ_ANALYSIS_OP_TYPE_SWI, but I don't know what */
			/* difference that would make to rizin */
			break;
		case 0x1f:
			op->type = RZ_ANALYSIS_OP_TYPE_TRAP;
			/* TODO: this should be RZ_ANALYSIS_OP_TYPE_DEBUG, */
			/* but that type is commented out */
			break;
		}
		break;
	case 0x10: /* LD_S	c,[b,u7] */
	case 0x11: /* LDB_S	c,[b,u5] */
	case 0x12: /* LDW_S	c,[b,u6] */
	case 0x13: /* LDW_S.X	c,[b,u6] */
		/* Load/Store with Offset, 0x10 - 0x16 */
		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		break;
	case 0x14: /* ST_S	c, [b, u7] */
	case 0x15: /* STB_S c, [b, u5] */
	case 0x16: /* STW_S c, [b, u6] */
		/* Load/Store with Offset, 0x10 - 0x16 */
		op->type = RZ_ANALYSIS_OP_TYPE_STORE;
		break;
	case 0x17: /* Shift/Subtract/Bit Immediate, 0x17, [0x00 - 0x07] */
		fields.subopcode = (words[0] & 0x00e00000) >> (16 + 5);
		switch (fields.subopcode) {
		case 0: /* Multiple arithmetic shift left */
			op->type = RZ_ANALYSIS_OP_TYPE_SAL;
			break;
		case 1: /* Multiple logical shift left */
			op->type = RZ_ANALYSIS_OP_TYPE_SHL;
			break;
		case 2: /* Multiple arithmetic shift right */
			op->type = RZ_ANALYSIS_OP_TYPE_SAR;
			break;
		case 3: /* Subtract */
			op->type = RZ_ANALYSIS_OP_TYPE_SUB;
			break;
		case 4: /* Bit set */
			op->type = RZ_ANALYSIS_OP_TYPE_OR;
			break;
		case 5: /* Bit clear */
		case 6: /* Bit mask */
		case 7: /* Bit test */
			op->type = RZ_ANALYSIS_OP_TYPE_AND;
			break;
		}
		break;
	case 0x18: /* Stack Pointer Based Instructions, 0x18, [0x00 - 0x07]	*/
		fields.subopcode = (words[0] & 0x00e00000) >> (16 + 5);
		switch (fields.subopcode) {
		case 0: /* Load long word sp-rel. */
		case 1: /* Load unsigned byte sp-rel. */
			op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
			break;
		case 2: /* Store long word sp-rel. */
		case 3: /* Store unsigned byte sp-rel. */
			op->type = RZ_ANALYSIS_OP_TYPE_STORE;
			break;
		case 4: /* Add */
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
			break;
		case 5: /* Add/Subtract SP Relative, 0x18, [0x05, 0x00-0x07] */
			fields.b = (words[0] & 0x07000000) >> (16 + 8);
			switch (fields.b) {
			case 0: /* Add immediate to SP */
				op->type = RZ_ANALYSIS_OP_TYPE_ADD;
				break;
			case 1: /* Subtract immediate from SP */
				op->type = RZ_ANALYSIS_OP_TYPE_SUB;
				break;
			default:
				op->type = RZ_ANALYSIS_OP_TYPE_ILL;
				break;
			}
			break;
		case 6: /* POP Register from Stack, 0x18, [0x06, 0x00-0x1F] */
			fields.c = (words[0] & 0x001f0000) >> 16;
			switch (fields.c) {
			case 1: /* Pop register from stack */
			case 0x11: /* Pop blink from stack */
				op->type = RZ_ANALYSIS_OP_TYPE_POP;
				break;
			default:
				op->type = RZ_ANALYSIS_OP_TYPE_ILL;
				break;
			}
			break;
		case 7: /* PUSH Register to Stack, 0x18, [0x07, 0x00-0x1F] */
			fields.c = (words[0] & 0x001f0000) >> 16;
			switch (fields.c) {
			case 1: /* Push register to stack */
			case 0x11: /* Push blink to stack */
				op->type = RZ_ANALYSIS_OP_TYPE_PUSH;
				break;
			default:
				op->type = RZ_ANALYSIS_OP_TYPE_ILL;
				break;
			}
			break;
		}
		break;
	case 0x19: /* Load/Add GP-Relative, 0x19, [0x00 - 0x03] */
		fields.subopcode = (words[0] & 0x06000000) >> (16 + 9);
		switch (fields.subopcode) {
		case 0: /* Load gp-relative (32-bit aligned) to r0 */
		case 1: /* Load unsigned byte gp-relative (8-bit aligned) to r0 */
		case 2: /* Load unsigned word gp-relative (16-bit aligned) to r0 */
			op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
			break;
		case 3: /* Add gp-relative (32-bit aligned) to r0 */
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
			break;
		}
		op->type = RZ_ANALYSIS_OP_TYPE_UNK;
		break;
	case 0x1a: /* Load PCL-Relative, 0x1A */
		fields.c = (words[0] & 0x00ff0000) >> 14;
		op->ptr = (addr & ~3) + fields.c;
		op->refptr = 4;
		op->type = RZ_ANALYSIS_OP_TYPE_LOAD;
		break;
	case 0x1b: /* Move Immediate, 0x1B */
		op->val = (words[0] & 0x00ff0000) >> 16;
		op->type = RZ_ANALYSIS_OP_TYPE_MOV;
		break;
	case 0x1c: /* ADD/CMP Immediate, 0x1C, [0x00 - 0x01] */
		fields.subopcode = (words[0] & 0x00800000) >> (16 + 7);
		if (fields.subopcode == 0) {
			op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		} else {
			op->type = RZ_ANALYSIS_OP_TYPE_CMP;
		}
		break;
	case 0x1d: /* Branch on Compare Register with Zero, 0x1D, [0x00 - 0x01] */
		/* fields.subopcode = (words[0] & 0x00800000) >> (16+7); */
		fields.imm = SIGN_EXT_S8((words[0] & 0x007f0000) >> (16 - 1));
		/* fields.subopcode? reg NE: reg EQ; */
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		arcompact_branch(op, addr, fields.imm, 0);
		break;
	case 0x1e: /* Branch Conditionally, 0x1E, [0x00 - 0x03] */
		fields.subopcode = (words[0] & 0x06000000) >> (16 + 9);
		fields.imm = SIGN_EXT_S10((words[0] & 0x01ff0000) >> (16 - 1));
		switch (fields.subopcode) {
		case 0: /* B_S */
			op->type = RZ_ANALYSIS_OP_TYPE_JMP;
			break;
		case 1: /* BEQ_S */
			op->cond = RZ_TYPE_COND_EQ;
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
			break;
		case 2: /* BNE_S */
			op->cond = RZ_TYPE_COND_NE;
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
			break;
		case 3: /* Bcc_S */
			op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
			fields.imm = SIGN_EXT_S7((words[0] & 0x003f0000) >> (16 - 1));
			/* TODO: cond codes (looks like it is the BR table again?) */
			break;
		}
		arcompact_branch(op, addr, fields.imm, 0);
		break;
	case 0x1f: /* Branch and Link Unconditionally, 0x1F */
		fields.imm = SIGN_EXT_S13((words[0] & 0x07ff0000) >> (16 - 2));
		op->type = RZ_ANALYSIS_OP_TYPE_CALL;
		arcompact_branch(op, addr, fields.imm, 0);
		break;
	}
	return op->size;
}

static int arc_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *data, int len, RzAnalysisOpMask mask) {
	const ut8 *b = (ut8 *)data;

	if (analysis->bits == 16) {
		return arcompact_op(analysis, op, addr, data, len);
	}

	/* ARCtangent A4 */
	op->size = 4;
	op->fail = addr + 4;
	ut8 basecode = (len > 3) ? ((b[3] & 0xf8) >> 3) : 0;
	switch (basecode) {
	case 0x04: /* Branch */
	case 0x05: /* Branch with Link */
	case 0x06: /* Loop */
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		op->jump = addr + 4 + ((rz_read_le32(&data[0]) & 0x07ffff80) >> (7 - 2));
		break;
	case 0x07: /* Conditional Jump and Jump with Link */
		op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
		op->jump = 0;
		break;
	case 0x08:
	case 0x09:
		op->type = RZ_ANALYSIS_OP_TYPE_ADD;
		break;
	case 0x0a:
	case 0x0b:
		op->type = RZ_ANALYSIS_OP_TYPE_SUB;
		break;
	case 0x0c:
		op->type = RZ_ANALYSIS_OP_TYPE_AND;
		break;
	case 0x0d:
		op->type = RZ_ANALYSIS_OP_TYPE_OR;
		break;
	case 0x0f:
		if ((b[0] == 0xff) && (b[1] == 0xff)) {
			op->type = RZ_ANALYSIS_OP_TYPE_NOP;
			break;
		}
		op->type = RZ_ANALYSIS_OP_TYPE_XOR;
		break;
	case 0x13:
		op->type = RZ_ANALYSIS_OP_TYPE_ROR;
		break;
	default:
		break;
	}
	return op->size;
}

static int archinfo(RzAnalysis *analysis, RzAnalysisInfoType query) {
	if (analysis->bits != 16) {
		return -1;
	}
	switch (query) {
	case RZ_ANALYSIS_ARCHINFO_TEXT_ALIGN:
		return 2;
	case RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE:
		/* all ops are at least 1 word long */
		return 2;
	case RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE:
		return 8;
	case RZ_ANALYSIS_ARCHINFO_CAN_USE_POINTERS:
		return true;
	default:
		return -1;
	}
}

static char *get_reg_profile(RzAnalysis *analysis) {
	if (analysis->bits != 16) {
		return false;
	}
	const char *p16 =
		"=PC	pcl\n"
		"=SP	sp\n"
		"=LR	blink\n"
		// "=BP	r27\n" // ??
		"=A0	r0\n"
		"=A1	r1\n"
		"=A2	r2\n"
		"=A3	r3\n"

		"gpr	r0	.32	0	0\n"
		"gpr	r1	.32	4	0\n"
		"gpr	r2	.32	8	0\n"
		"gpr	r3	.32	12	0\n"
		"gpr	r4	.32	16	0\n"
		"gpr	r5	.32	20	0\n"
		"gpr	r6	.32	24	0\n"
		"gpr	r7	.32	28	0\n"
		"gpr	r8	.32	32	0\n"
		"gpr	r9	.32	36	0\n"
		"gpr	r10	.32	40	0\n"
		"gpr	r11	.32	44	0\n"
		"gpr	r12	.32	48	0\n"
		"gpr	r13	.32	52	0\n"
		"gpr	r14	.32	56	0\n"
		"gpr	r15	.32	60	0\n"
		"gpr	r16	.32	64	0\n"
		"gpr	r17	.32	68	0\n"
		"gpr	r18	.32	72	0\n"
		"gpr	r19	.32	76	0\n"
		"gpr	r20	.32	80	0\n"
		"gpr	r21	.32	84	0\n"
		"gpr	r22	.32	88	0\n"
		"gpr	r23	.32	92	0\n"
		"gpr	r24	.32	96	0\n"
		"gpr	r25	.32	100	0\n"
		"gpr	gp	.32	104	0\n"
		"gpr	fp	.32	108	0\n"
		"gpr	sp	.32	112	0\n"
		"gpr	ilink1	.32	116	0\n"
		"gpr	ilink2	.32	120	0\n"
		"gpr	blink	.32	124	0\n"
		"gpr	lp_count	.32	128	0\n"
		"gpr	pcl	.32	132	0\n";

	/* TODO: */
	/* Should I add the Auxiliary Register Set? */
	/* it contains the flag bits, amongst other things */
	return rz_str_dup(p16);
}

RzAnalysisPlugin rz_analysis_plugin_arc_gnu = {
	.name = "arc",
	.arch = "arc",
	.license = "LGPL3",
	.bits = 16 | 32,
	.desc = "ARC code analysis plugin",
	.op = &arc_op,
	.archinfo = archinfo,
	.get_reg_profile = get_reg_profile,
};
