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

#include <rz_analysis.h>
#include <rz_lib.h>

#if CC_SUPPORTS_W_ENUM_COMPARE
#pragma GCC diagnostic ignored "-Wenum-compare"
#endif

#ifdef CC_SUPPORTS_W_ENUM_CONVERION
#pragma GCC diagnostic ignored "-Wenum-conversion"
#endif

#define CAPSTONE_SYSTEMZ_COMPAT_HEADER
#include <capstone/capstone.h>
#include <capstone/systemz.h>
// instruction set: http://www.tachyonsoft.com/inst390m.htm

#define INSOP(n) insn->detail->sysz.operands[n]

static RzStructuredData *sysz_opex(csh handle, cs_insn *insn) {
	if (!insn->detail) {
		return NULL;
	}

	RzStructuredData *root = rz_structured_data_new_map();
	if (!root) {
		return NULL;
	}

	RzStructuredData *opex = rz_structured_data_map_add_map(root, "opex");
	if (!opex) {
		rz_structured_data_free(root);
		return NULL;
	}

	RzStructuredData *operands = rz_structured_data_map_add_array(opex, "operands");
	cs_sysz *x = &insn->detail->sysz;
	for (st32 i = 0; i < x->op_count; i++) {
		cs_sysz_op *op = x->operands + i;
		RzStructuredData *operand = rz_structured_data_array_add_map(operands);
		switch (op->type) {
		case SYSZ_OP_REG:
			rz_structured_data_map_add_string(operand, "type", "reg");
			rz_structured_data_map_add_string(operand, "value", cs_reg_name(handle, op->reg));
			break;
		case SYSZ_OP_IMM:
			rz_structured_data_map_add_string(operand, "type", "imm");
			rz_structured_data_map_add_signed(operand, "value", op->imm);
			break;
		case SYSZ_OP_MEM:
			rz_structured_data_map_add_string(operand, "type", "mem");
			if (op->mem.base != SYSZ_REG_INVALID) {
				rz_structured_data_map_add_string(operand, "base", cs_reg_name(handle, op->mem.base));
			}
			rz_structured_data_map_add_signed(operand, "disp", op->mem.disp);
			break;
		default:
			rz_structured_data_map_add_string(operand, "type", "invalid");
			break;
		}
	}

	return root;
}

static int analyze_op(RzAnalysis *a, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, RzAnalysisOpMask mask) {
	csh handle;
	cs_insn *insn;
	int mode = CS_MODE_BIG_ENDIAN;
	int ret = cs_open(CS_ARCH_SYSZ, mode, &handle);
	if (ret == CS_ERR_OK) {
		cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);
		// capstone-next
		int n = cs_disasm(handle, (const ut8 *)buf, len, addr, 1, &insn);
		if (n < 1) {
			op->type = RZ_ANALYSIS_OP_TYPE_ILL;
		} else {
			if (mask & RZ_ANALYSIS_OP_MASK_OPEX) {
				op->opex = sysz_opex(handle, insn);
			}
			op->size = insn->size;
			switch (insn->id) {
			case SYSZ_INS_BRCL:
			case SYSZ_INS_BRASL:
				op->type = RZ_ANALYSIS_OP_TYPE_CALL;
				break;
			case SYSZ_INS_BR:
				op->type = RZ_ANALYSIS_OP_TYPE_JMP;
				break;
			case SYSZ_INS_BRC:
			case SYSZ_INS_BER:
			case SYSZ_INS_BHR:
			case SYSZ_INS_BHER:
			case SYSZ_INS_BLR:
			case SYSZ_INS_BLER:
			case SYSZ_INS_BLHR:
			case SYSZ_INS_BNER:
			case SYSZ_INS_BNHR:
			case SYSZ_INS_BNHER:
			case SYSZ_INS_BNLR:
			case SYSZ_INS_BNLER:
			case SYSZ_INS_BNLHR:
			case SYSZ_INS_BNOR:
			case SYSZ_INS_BOR:
			case SYSZ_INS_BASR:
			case SYSZ_INS_BRAS:
			case SYSZ_INS_BRCT:
			case SYSZ_INS_BRCTG:
				op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
				break;
#if CS_NEXT_VERSION < 6
			case SYSZ_INS_JGE:
			case SYSZ_INS_JGHE:
			case SYSZ_INS_JGH:
			case SYSZ_INS_JGLE:
			case SYSZ_INS_JGLH:
			case SYSZ_INS_JGL:
			case SYSZ_INS_JGNE:
			case SYSZ_INS_JGNHE:
			case SYSZ_INS_JGNH:
			case SYSZ_INS_JGNLE:
			case SYSZ_INS_JGNLH:
			case SYSZ_INS_JGNL:
			case SYSZ_INS_JGNO:
			case SYSZ_INS_JGO:
			case SYSZ_INS_JG:
#endif
			case SYSZ_INS_JNE:
			case SYSZ_INS_JNHE:
			case SYSZ_INS_JNH:
			case SYSZ_INS_JNL:
			case SYSZ_INS_JE:
			case SYSZ_INS_JHE:
			case SYSZ_INS_JH:
			case SYSZ_INS_JLE:
			case SYSZ_INS_JLH:
			case SYSZ_INS_JL:
			case SYSZ_INS_JNLE:
			case SYSZ_INS_JNLH:
			case SYSZ_INS_JNO:
			case SYSZ_INS_JO:
				op->type = RZ_ANALYSIS_OP_TYPE_CJMP;
				op->jump = INSOP(0).imm;
				op->fail = addr + op->size;
				break;
			case SYSZ_INS_J:
				op->type = RZ_ANALYSIS_OP_TYPE_JMP;
				op->jump = INSOP(0).imm;
				op->fail = UT64_MAX;
				break;
			}
		}
		cs_free(insn, n);
		cs_close(&handle);
	}
	return op->size;
}

static char *get_reg_profile(RzAnalysis *analysis) {
	if (analysis->bits == 64) {
		// linux/arch/s390/include/uapi/asm/ptrace.h: struct s390_regs { psw_t psw; unsigned long gprs[16]; unsigned int acrs[16]; unsigned long orig_gpr2; }
		const char *p =
			"=PC	pswa\n"
			"=SP	r15\n"
			"=A0	r2\n"
			"gpr	pswm	.64	0	0\n" // psw.mask
			"gpr	pswa	.64	8	0\n" // psw.addr = PC
			"gpr	r0	.64	16	0\n"
			"gpr	r1	.64	24	0\n"
			"gpr	r2	.64	32	0\n"
			"gpr	r3	.64	40	0\n"
			"gpr	r4	.64	48	0\n"
			"gpr	r5	.64	56	0\n"
			"gpr	r6	.64	64	0\n"
			"gpr	r7	.64	72	0\n"
			"gpr	r8	.64	80	0\n"
			"gpr	r9	.64	88	0\n"
			"gpr	r10	.64	96	0\n"
			"gpr	r11	.64	104	0\n"
			"gpr	r12	.64	112	0\n"
			"gpr	r13	.64	120	0\n"
			"gpr	r14	.64	128	0\n"
			"gpr	r15	.64	136	0\n"
			"gpr	acr0	.32	144	0\n"
			"gpr	acr1	.32	148	0\n"
			"gpr	acr2	.32	152	0\n"
			"gpr	acr3	.32	156	0\n"
			"gpr	acr4	.32	160	0\n"
			"gpr	acr5	.32	164	0\n"
			"gpr	acr6	.32	168	0\n"
			"gpr	acr7	.32	172	0\n"
			"gpr	acr8	.32	176	0\n"
			"gpr	acr9	.32	180	0\n"
			"gpr	acr10	.32	184	0\n"
			"gpr	acr11	.32	188	0\n"
			"gpr	acr12	.32	192	0\n"
			"gpr	acr13	.32	196	0\n"
			"gpr	acr14	.32	200	0\n"
			"gpr	acr15	.32	204	0\n"
			"gpr	orig_r2	.64	208	0\n";
		return rz_str_dup(p);
	}
	const char *p =
		"=PC	r15\n"
		"=LR	r14\n"
		"=SP	r13\n"
		"=BP	r12\n"
		"=A0	r0\n"
		"=A1	r1\n"
		"=A2	r2\n"
		"=A3	r3\n"
		"=SN	r0\n"
		"gpr	sb	.32	36	0\n" // r9
		"gpr	sl	.32	40	0\n" // rl0
		"gpr	fp	.32	44	0\n" // r11
		"gpr	ip	.32	48	0\n" // r12
		"gpr	sp	.32	52	0\n" // r13
		"gpr	lr	.32	56	0\n" // r14
		"gpr	pc	.32	60	0\n" // r15

		"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";
	return rz_str_dup(p);
}

static int archinfo(RzAnalysis *a, RzAnalysisInfoType query) {
	switch (query) {
	case RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE:
		return 2;
	case RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE:
		return 4;
	case RZ_ANALYSIS_ARCHINFO_TEXT_ALIGN:
		/* fall-thru */
	case RZ_ANALYSIS_ARCHINFO_DATA_ALIGN:
		return 2;
	case RZ_ANALYSIS_ARCHINFO_CAN_USE_POINTERS:
		return true;
	default:
		return -1;
	}
}

RzAnalysisPlugin rz_analysis_plugin_sysz = {
	.name = "sysz",
	.desc = "Capstone SystemZ microanalysis",
	.esil = false,
	.license = "BSD",
	.arch = "sysz",
	.bits = 32 | 64,
	.op = &analyze_op,
	.archinfo = archinfo,
	.get_reg_profile = &get_reg_profile,
};
