// SPDX-FileCopyrightText: 2022 Rot127 <unisono@quyllur.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include "ppc_il.h"
#include "capstone.h"
#include "ppc_analysis.h"
#include <capstone/ppc.h>
#include <rz_il/rz_il_opcodes.h>
#include <rz_util/rz_log.h>
#include <rz_util/rz_assert.h>
#include <rz_endian.h>
#include <rz_analysis.h>
#include <rz_il.h>
#include <rz_types.h>

RZ_IPI RzAnalysisILConfig *rz_ppc_cs_64_il_config(bool big_endian) {
	RzAnalysisILConfig *r = rz_analysis_il_config_new(64, big_endian, 64);
	return r;
}

RZ_IPI RzAnalysisILConfig *rz_ppc_cs_32_il_config(bool big_endian) {
	RzAnalysisILConfig *r = rz_analysis_il_config_new(32, big_endian, 32);
	return r;
}

/**
 * \brief Returns true if the given load/store instruction is in X form (uses register RB as second operand).
 *
 * \param insn_id The instruction id.
 * \return bool True if the load/store instruction is in X form. False otherwise.
 */
RZ_IPI bool ppc_is_x_form(ut32 insn_id) {
	switch (insn_id) {
	default:
		return false;
	case PPC_INS_LBZUX:
	case PPC_INS_LBZX:
	case PPC_INS_LDARX:
	case PPC_INS_LDBRX:
	case PPC_INS_LDUX:
	case PPC_INS_LDX:
	case PPC_INS_LHAUX:
	case PPC_INS_LHAX:
	case PPC_INS_LHBRX:
	case PPC_INS_LHZUX:
	case PPC_INS_LHZX:
	case PPC_INS_LWARX:
	case PPC_INS_LWAUX:
	case PPC_INS_LWAX:
	case PPC_INS_LWBRX:
	case PPC_INS_LWZUX:
	case PPC_INS_LWZX:
	case PPC_INS_STBUX:
	case PPC_INS_STHUX:
	case PPC_INS_STWUX:
	case PPC_INS_STDUX:
	case PPC_INS_STBX:
	case PPC_INS_STHX:
	case PPC_INS_STWX:
	case PPC_INS_STDX:
	case PPC_INS_STDCX:
	case PPC_INS_STWCX:
	case PPC_INS_LBZCIX:
	case PPC_INS_LDCIX:
	case PPC_INS_LHZCIX:
	case PPC_INS_LWZCIX:
	case PPC_INS_STBCIX:
	case PPC_INS_STHCIX:
	case PPC_INS_STWCIX:
	case PPC_INS_STDCIX:
		return true;
	}
}

/**
 * \brief Returns the memory access size in bytes for a given load/store instruction.
 *
 * \param insn_id The instruction id.
 * \return st32 The memory access size in bytes.
 * Or 0 if the load instruction does not access the memory.
 * Or -1 if the instruction is no load/store instruction.
 */
RZ_IPI st32 ppc_get_mem_acc_size(ut32 insn_id) {
	switch (insn_id) {
	default:
		RZ_LOG_INFO("Memory access size for instruction %d requested. But it is not in the switch case.\n", insn_id);
		return -1;
#if CS_NEXT_VERSION < 6
	case PPC_INS_LI:
	case PPC_INS_LIS:
#endif
		// Doesn't read from memory.
		return 0;
	case PPC_INS_LBZ:
	case PPC_INS_LBZU:
	case PPC_INS_LBZUX:
	case PPC_INS_LBZX:
	case PPC_INS_STB:
	case PPC_INS_STBU:
	case PPC_INS_STBUX:
	case PPC_INS_STBX:
	case PPC_INS_STBCIX:
	case PPC_INS_LBZCIX:
		return PPC_BYTE;
	case PPC_INS_LHA:
	case PPC_INS_LHAU:
	case PPC_INS_LHAUX:
	case PPC_INS_LHAX:
	case PPC_INS_LHBRX:
	case PPC_INS_LHZ:
	case PPC_INS_LHZU:
	case PPC_INS_LHZUX:
	case PPC_INS_LHZX:
	case PPC_INS_STH:
	case PPC_INS_STHBRX:
	case PPC_INS_STHU:
	case PPC_INS_STHUX:
	case PPC_INS_STHX:
	case PPC_INS_LHZCIX:
	case PPC_INS_STHCIX:
		return PPC_HWORD;
	case PPC_INS_LWA:
	case PPC_INS_LWARX:
	case PPC_INS_LWAUX:
	case PPC_INS_LWAX:
	case PPC_INS_LWBRX:
	case PPC_INS_LWZ:
	case PPC_INS_LWZU:
	case PPC_INS_LWZUX:
	case PPC_INS_LWZX:
	case PPC_INS_LMW:
	case PPC_INS_STW:
	case PPC_INS_STWBRX:
	case PPC_INS_STWCX:
	case PPC_INS_STWU:
	case PPC_INS_STWUX:
	case PPC_INS_STWX:
	case PPC_INS_STMW:
	case PPC_INS_LWZCIX:
	case PPC_INS_STWCIX:
		return PPC_WORD;
	case PPC_INS_LD:
	case PPC_INS_LDARX:
	case PPC_INS_LDBRX:
	case PPC_INS_LDU:
	case PPC_INS_LDUX:
	case PPC_INS_LDX:
	case PPC_INS_STD:
	case PPC_INS_STDBRX:
	case PPC_INS_STDCX:
	case PPC_INS_STDU:
	case PPC_INS_STDUX:
	case PPC_INS_STDX:
	case PPC_INS_LDCIX:
	case PPC_INS_STDCIX:
		return PPC_DWORD;
	}
}

/**
 * \brief Returns true if the given load/store instruction updates the RA register with EA after memory access.
 *
 * \param insn_id The instruction id.
 * \return bool True if RA is set to EA after the instruction executed. False otherwise.
 */
RZ_IPI bool ppc_updates_ra_with_ea(ut32 insn_id) {
	switch (insn_id) {
	default:
		return false;
	case PPC_INS_LBZU:
	case PPC_INS_LBZUX:
	case PPC_INS_LDU:
	case PPC_INS_LDUX:
	case PPC_INS_LHAU:
	case PPC_INS_LHAUX:
	case PPC_INS_LHZU:
	case PPC_INS_LHZUX:
	case PPC_INS_LWAUX:
	case PPC_INS_LWZU:
	case PPC_INS_LWZUX:
	case PPC_INS_LFDU:
	case PPC_INS_LFDUX:
	case PPC_INS_LFSU:
	case PPC_INS_LFSUX:
	case PPC_INS_STBU:
	case PPC_INS_STDU:
	case PPC_INS_STHU:
	case PPC_INS_STWU:
	case PPC_INS_STBUX:
	case PPC_INS_STHUX:
	case PPC_INS_STWUX:
	case PPC_INS_STDUX:
	case PPC_INS_STFDU:
	case PPC_INS_STFDUX:
	case PPC_INS_STFSU:
	case PPC_INS_STFSUX:
		return true;
	}
}

/**
 * \brief Returns true if the given load instruction is algebraic.
 *
 * \param insn_id The instruction id.
 * \return bool True if the load instruction is algebraic. False otherwise.
 */
RZ_IPI bool ppc_is_algebraic(ut32 insn_id) {
	switch (insn_id) {
	default:
		return false;
	case PPC_INS_LA:
	case PPC_INS_LDARX:
	case PPC_INS_LHA:
	case PPC_INS_LHAU:
	case PPC_INS_LHAUX:
	case PPC_INS_LHAX:
	case PPC_INS_LWA:
	case PPC_INS_LWAX:
	case PPC_INS_LWARX:
	case PPC_INS_LWAUX:
		return true;
	}
}

/**
 * \brief Returns true if the given branch instruction sets the LR register.
 *
 * \param insn_id The instruction id.
 * \return bool True if the branch instruction writes the LR register. False otherwise.
 */
RZ_IPI bool ppc_insn_sets_lr(const cs_insn *insn) {
	rz_return_val_if_fail(insn, false);
	for (int i = 0; i < insn->detail->regs_write_count; ++i) {
		ppc_reg reg = insn->detail->regs_write[i];
		if (reg == PPC_REG_LR) {
			return true;
		}
	}
	return false;
}

/**
 * \brief Returns true if the given branch instruction sets the LR register.
 *
 * \param insn_id The instruction id.
 * \return bool True if the branch instruction writes the LR register. False otherwise.
 */
RZ_IPI bool ppc_sets_lr(ut32 insn_id) {
	switch (insn_id) {
	default:
		return false;
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQCTRL:
	case PPC_INS_BFCTRL:
	case PPC_INS_BGECTRL:
	case PPC_INS_BGTCTRL:
	case PPC_INS_BLECTRL:
	case PPC_INS_BLTCTRL:
	case PPC_INS_BNECTRL:
	case PPC_INS_BNGCTRL:
	case PPC_INS_BNLCTRL:
	case PPC_INS_BNSCTRL:
	case PPC_INS_BNUCTRL:
	case PPC_INS_BSOCTRL:
	case PPC_INS_BTCTRL:
	case PPC_INS_BUNCTRL:
	case PPC_INS_BGEL:
	case PPC_INS_BGELRL:
	case PPC_INS_BGELA:
	case PPC_INS_BDNZL:
	case PPC_INS_BDNZLA:
	case PPC_INS_BDNZLRL:
	case PPC_INS_BDZL:
	case PPC_INS_BDZLA:
	case PPC_INS_BDZLRL:
	case PPC_INS_BDNZTL:
	case PPC_INS_BDNZTLA:
	case PPC_INS_BDNZFL:
	case PPC_INS_BDNZFLA:
	case PPC_INS_BDZTL:
	case PPC_INS_BDZTLA:
	case PPC_INS_BDZFL:
	case PPC_INS_BDZFLA:
#endif
	case PPC_INS_BCCTRL:
	case PPC_INS_BCL:
	case PPC_INS_BCLRL:
	case PPC_INS_BCTRL:
	case PPC_INS_BL:
	case PPC_INS_BLA:
	case PPC_INS_BLRL:
	case PPC_INS_BCLA:
		return true;
	}
}

#if CS_NEXT_VERSION >= 6
/**
 * \brief Returns true if the given branch instruction is conditional.
 *
 * \param insn_id The instruction id.
 * \return bool True if the branch instruction only branches if a condition is met. False otherwise.
 */
RZ_IPI bool ppc_insn_is_conditional(const cs_insn *insn) {
	rz_return_val_if_fail(insn, false);
	return PPC_DETAIL(insn).bc.pred_cr != PPC_PRED_INVALID || PPC_DETAIL(insn).bc.pred_ctr != PPC_PRED_INVALID;
}
#endif

/**
 * \brief Returns true if the given branch instruction is conditional.
 *
 * \param insn_id The instruction id.
 * \return bool True if the branch instruction only branches if a condition is met. False otherwise.
 */
RZ_IPI bool ppc_is_conditional(ut32 insn_id) {
	switch (insn_id) {
	default:
		return false;
	case PPC_INS_BC:
	case PPC_INS_BCCTR:
	case PPC_INS_BCCTRL:
	case PPC_INS_BCL:
	case PPC_INS_BCLR:
	case PPC_INS_BCLRL:
	case PPC_INS_BCA:
	case PPC_INS_BCLA:
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQ:
	case PPC_INS_BEQA:
	case PPC_INS_BF:
	case PPC_INS_BFA:
	case PPC_INS_BGE:
	case PPC_INS_BGEA:
	case PPC_INS_BGT:
	case PPC_INS_BGTA:
	case PPC_INS_BLE:
	case PPC_INS_BLEA:
	case PPC_INS_BLT:
	case PPC_INS_BLTA:
	case PPC_INS_BNE:
	case PPC_INS_BNEA:
	case PPC_INS_BNG:
	case PPC_INS_BNGA:
	case PPC_INS_BNL:
	case PPC_INS_BNLA:
	case PPC_INS_BNS:
	case PPC_INS_BNSA:
	case PPC_INS_BNU:
	case PPC_INS_BNUA:
	case PPC_INS_BSO:
	case PPC_INS_BSOA:
	case PPC_INS_BT:
	case PPC_INS_BTA:
	case PPC_INS_BUN:
	case PPC_INS_BUNA:
	case PPC_INS_BGEL:
	case PPC_INS_BGELA:
	case PPC_INS_BGELR:
	case PPC_INS_BGELRL:
	case PPC_INS_BGECTR:
	case PPC_INS_BGECTRL:
	case PPC_INS_BDNZT:
	case PPC_INS_BDNZTL:
	case PPC_INS_BDNZTA:
	case PPC_INS_BDNZTLA:
	case PPC_INS_BDNZF:
	case PPC_INS_BDNZFL:
	case PPC_INS_BDNZFA:
	case PPC_INS_BDNZFLA:
	case PPC_INS_BDZT:
	case PPC_INS_BDZTA:
	case PPC_INS_BDZTL:
	case PPC_INS_BDZTLA:
	case PPC_INS_BDZF:
	case PPC_INS_BDZFA:
	case PPC_INS_BDZFL:
	case PPC_INS_BDZFLA:
	case PPC_INS_BDNZ:
	case PPC_INS_BDNZA:
	case PPC_INS_BDNZLR:
	case PPC_INS_BDNZLRL:
	case PPC_INS_BDZ:
	case PPC_INS_BDZA:
	case PPC_INS_BDZLA:
	case PPC_INS_BDZLR:
	case PPC_INS_BDZLRL:
#endif
		return true;
	}
}

/**
 * \brief Returns true if the given instruction sets a SPR register.
 *
 * \param insn_id The instruction id.
 * \return bool True if the instructions moves a value to a SPR. False otherwise.
 */
RZ_IPI bool ppc_moves_to_spr(ut32 insn_id) {
	switch (insn_id) {
	default:
		return false;
	case PPC_INS_MTCTR:
	case PPC_INS_MTCRF:
	case PPC_INS_MTDCR:
	case PPC_INS_MTFSB0:
	case PPC_INS_MTFSB1:
	case PPC_INS_MTFSF:
	case PPC_INS_MTLR:
	case PPC_INS_MTMSR:
	case PPC_INS_MTMSRD:
	case PPC_INS_MTOCRF:
	case PPC_INS_MTSPR:
	case PPC_INS_MTSR:
	case PPC_INS_MTSRIN:
	case PPC_INS_MTVSCR:
#if CS_NEXT_VERSION < 6
	case PPC_INS_MTCR:
	case PPC_INS_MTBR0:
	case PPC_INS_MTBR1:
	case PPC_INS_MTBR2:
	case PPC_INS_MTBR3:
	case PPC_INS_MTBR4:
	case PPC_INS_MTBR5:
	case PPC_INS_MTBR6:
	case PPC_INS_MTBR7:
	case PPC_INS_MTXER:
	case PPC_INS_MTDSCR:
	case PPC_INS_MTDSISR:
	case PPC_INS_MTDAR:
	case PPC_INS_MTSRR2:
	case PPC_INS_MTSRR3:
	case PPC_INS_MTCFAR:
	case PPC_INS_MTAMR:
	case PPC_INS_MTPID:
	case PPC_INS_MTTBL:
	case PPC_INS_MTTBU:
	case PPC_INS_MTTBLO:
	case PPC_INS_MTTBHI:
	case PPC_INS_MTDBATU:
	case PPC_INS_MTDBATL:
	case PPC_INS_MTIBATU:
	case PPC_INS_MTIBATL:
	case PPC_INS_MTDCCR:
	case PPC_INS_MTICCR:
	case PPC_INS_MTDEAR:
	case PPC_INS_MTESR:
	case PPC_INS_MTSPEFSCR:
	case PPC_INS_MTTCR:
#endif
	case PPC_INS_MFSRIN:
		return true;
	}
}

/**
 * \brief Returns true if the given branch instruction decrements the CTR register.
 *
 * \param insn Instruction id.
 * \param mode Capstone mode.
 * \return bool True if the instruction decrements the counter. False otherwise.
 */
RZ_IPI bool ppc_decrements_ctr(RZ_BORROW cs_insn *insn, const cs_mode mode) {
	rz_return_val_if_fail(insn, false);
#if CS_NEXT_VERSION >= 6
	return cs_ppc_bc_decr_ctr(PPC_DETAIL(insn).bc.bo);
#else
	ut32 id = insn->id;

	switch (id) {
	default:
		return false;
	case PPC_INS_BC:
	case PPC_INS_BCL:
	case PPC_INS_BCA:
	case PPC_INS_BCLA:
	case PPC_INS_BCLR:
	case PPC_INS_BCLRL:
#if CS_API_MAJOR == 5
	case PPC_INS_BGEL:
	case PPC_INS_BGELA:
#endif
	case PPC_INS_BDNZ:
	case PPC_INS_BDNZA:
	case PPC_INS_BDNZL:
	case PPC_INS_BDNZLA:
	case PPC_INS_BDNZLR:
	case PPC_INS_BDNZLRL:
	case PPC_INS_BDZ:
	case PPC_INS_BDZA:
	case PPC_INS_BDZL:
	case PPC_INS_BDZLA:
	case PPC_INS_BDZLR:
	case PPC_INS_BDZLRL:
		return !(0x4 & PPC_READ_BO_FIELD); // not BO_2
	case PPC_INS_BDNZT:
	case PPC_INS_BDNZTL:
	case PPC_INS_BDNZTA:
	case PPC_INS_BDNZTLA:
	case PPC_INS_BDNZF:
	case PPC_INS_BDNZFL:
	case PPC_INS_BDNZFA:
	case PPC_INS_BDNZFLA:
	case PPC_INS_BDZT:
	case PPC_INS_BDZTA:
	case PPC_INS_BDZTL:
	case PPC_INS_BDZTLA:
	case PPC_INS_BDZF:
	case PPC_INS_BDZFA:
	case PPC_INS_BDZFL:
	case PPC_INS_BDZFLA:
		return true;
	}
#endif
}

//
// IL helper BEGIN
//

#include <rz_il/rz_il_opbuilder_begin.h>

/**
 * \brief Returns the value of the a bit at position \p pos in CR.
 *
 * NOTE: The Condition Register and its fields (cr0-cr7) start at bit 32.
 * The CR reg is not defined for bits at positions smaller than 32 or larger than 63.
 *
 * \param pos The bit position to look up.
 * \return RzILOpBool* The value of the bit at position \p pos in the CR register.
 * Or IL_FALSE for invalid access.
 */
static RZ_OWN RzILOpBool *get_cr_bit(const ut8 pos) {
	if (pos > 63 || pos < 32) {
		RZ_LOG_WARN("Undefined access into CR register.\n");
		return IL_FALSE;
	}
	ut8 field_bit;
	RzILOpPure *cr_field;
	if (pos < 36) {
		field_bit = 0b1000 >> (pos - 32);
		cr_field = VARG("cr0");
	} else if (pos < 40) {
		field_bit = 0b1000 >> (pos - 36);
		cr_field = VARG("cr1");
	} else if (pos < 44) {
		field_bit = 0b1000 >> (pos - 40);
		cr_field = VARG("cr2");
	} else if (pos < 48) {
		field_bit = 0b1000 >> (pos - 44);
		cr_field = VARG("cr3");
	} else if (pos < 52) {
		field_bit = 0b1000 >> (pos - 48);
		cr_field = VARG("cr4");
	} else if (pos < 56) {
		field_bit = 0b1000 >> (pos - 52);
		cr_field = VARG("cr5");
	} else if (pos < 60) {
		field_bit = 0b1000 >> (pos - 56);
		cr_field = VARG("cr6");
	} else {
		field_bit = 0b1000 >> (pos - 60);
		cr_field = VARG("cr7");
	}
	return NON_ZERO(LOGAND(cr_field, UN(4, field_bit)));
}

/**
 * \brief Synchronizes the CR register with the CR0-CR7 registers.
 * Since CR contains CR0-CR7 but are separated in the register profile
 * this function should be called before CR as a whole is read and after it was written.
 *
 * \param to_cr True: CR0-CR7 are copied to CR. False: CR is copied to CR0-CR7 according to \p cr_mask.
 * \param cr_mask Masks the bits which are copied from CR to CR0-CR7. Ignored if \p crx_to_cr == true.
 * \return RzILOpEffect* Sequence of effects to sync the CR/CRx registers.
 */
RZ_IPI RZ_OWN RzILOpEffect *ppc_sync_crx_cr(const bool crx_to_cr, const ut32 cr_mask) {
	RzILOpEffect *effect;
	if (crx_to_cr) {
		effect = SETL("cr", LOGOR(UNSIGNED(32, VARG("cr7")), LOGOR(SHIFTL0(UNSIGNED(32, VARG("cr6")), U8(0x4)), LOGOR(SHIFTL0(UNSIGNED(32, VARG("cr5")), U8(0x8)), LOGOR(SHIFTL0(UNSIGNED(32, VARG("cr4")), U8(0xc)), LOGOR(SHIFTL0(UNSIGNED(32, VARG("cr3")), U8(0x10)), LOGOR(SHIFTL0(UNSIGNED(32, VARG("cr2")), U8(0x14)), LOGOR(SHIFTL0(UNSIGNED(32, VARG("cr1")), U8(0x18)), SHIFTL0(UNSIGNED(32, VARG("cr0")), U8(0x1c))))))))));
		return effect;
	}
	effect = SEQN(10,
		SETL("cr_mask", U32(cr_mask)),
		SETL("crm", LOGAND(VARL("cr"), U32(cr_mask))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf))), EMPTY(), SETG("cr7", UNSIGNED(4, VARL("crm")))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf0))), EMPTY(), SETG("cr6", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0x4))))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf00))), EMPTY(), SETG("cr5", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0x8))))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf000))), EMPTY(), SETG("cr4", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0xc))))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf0000))), EMPTY(), SETG("cr3", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0x10))))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf00000))), EMPTY(), SETG("cr2", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0x14))))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf000000))), EMPTY(), SETG("cr1", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0x18))))),
		BRANCH(IS_ZERO(LOGAND(VARL("cr_mask"), U32(0xf0000000))), EMPTY(), SETG("cr0", UNSIGNED(4, SHIFTR0(VARL("crm"), U8(0x1c))))));
	return effect;
}

/**
 * \brief Returns the mask for a given fxm operand.
 * For details look up the "mtcrf" instruction in the Power ISA
 *
 * \param fmx The fmx value.
 * \return ut32 The mask for writing to the CR register.
 */
RZ_IPI ut32 ppc_fmx_to_mask(const ut8 fmx) {
	ut32 x = 0xf;
	return (
		(fmx & 0x80 ? x << 28 : 0) |
		(fmx & 0x40 ? x << 24 : 0) |
		(fmx & 0x20 ? x << 20 : 0) |
		(fmx & 0x10 ? x << 16 : 0) |
		(fmx & 0x08 ? x << 12 : 0) |
		(fmx & 0x04 ? x << 8 : 0) |
		(fmx & 0x02 ? x << 4 : 0) |
		(fmx & 0x01 ? x : 0));
}

#if CS_NEXT_VERSION < 6
static const char *get_crx_reg(const csh handle, cs_insn *insn, size_t n) {
#if CS_API_MAJOR == 5 && CS_API_MINOR == 0
	// bug on crx not being populated in capstone v5.0
	ppc_reg reg = INSOP(n).reg;
	if (reg >= PPC_REG_CR0EQ && reg <= PPC_REG_CR7EQ) {
		reg = (reg - PPC_REG_CR0EQ) + PPC_REG_CR0;
	} else if (reg >= PPC_REG_CR0GT && reg <= PPC_REG_CR7GT) {
		reg = (reg - PPC_REG_CR0GT) + PPC_REG_CR0;
	} else if (reg >= PPC_REG_CR0LT && reg <= PPC_REG_CR7LT) {
		reg = (reg - PPC_REG_CR0LT) + PPC_REG_CR0;
	} else if (reg >= PPC_REG_CR0UN && reg <= PPC_REG_CR7UN) {
		reg = (reg - PPC_REG_CR0UN) + PPC_REG_CR0;
	} else {
		rz_warn_if_reached();
	}
	return cs_reg_name(handle, reg);
#else
	return cs_reg_name(handle, INSOP(n).crx.reg);
#endif
}

static ut32 get_crx_cond(const csh handle, cs_insn *insn, size_t n) {
#if CS_API_MAJOR == 5 && CS_API_MINOR == 0
	// bug on crx not being populated in capstone v5.0
	ppc_reg reg = INSOP(n).reg;
	if (reg >= PPC_REG_CR0EQ && reg <= PPC_REG_CR7EQ) {
		return PPC_BC_EQ;
	} else if (reg >= PPC_REG_CR0GT && reg <= PPC_REG_CR7GT) {
		return PPC_BC_GT;
	} else if (reg >= PPC_REG_CR0LT && reg <= PPC_REG_CR7LT) {
		return PPC_BC_LT;
	} else if (reg >= PPC_REG_CR0UN && reg <= PPC_REG_CR7UN) {
		return PPC_BC_UN;
	}
	rz_warn_if_reached();
	return PPC_BC_INVALID;
#else
	return INSOP(n).crx.cond;
#endif
}
#endif

/**
 * \brief Get the branch condition for a given instruction.
 * Checkout the "Simple Branch Mnemonics" in Appendix C in PowerISA v3.1B and
 * the chapter about branch instructions for an overview of possible conditions.
 *
 * NOTE: This function *does not* decrement CTR, if required by the instruction.
 * This should have been done before.
 *
 * \param insn The capstone instructions.
 * \param mode The capstone mode.
 * \return RzILOpPure* The condition the branch occurs as a Pure.
 */
RZ_IPI RZ_OWN RzILOpPure *ppc_get_branch_cond(const csh handle, RZ_BORROW cs_insn *insn, const cs_mode mode) {
	rz_return_val_if_fail(insn, NULL);
	ut32 id = insn->id;

#if CS_NEXT_VERSION >= 6
	ut8 bi = PPC_DETAIL(insn).bc.bi;
	ut8 bo = PPC_DETAIL(insn).bc.bo;
	RzILOpBool *decr_ctr = cs_ppc_bc_decr_ctr(bo) ? IL_TRUE : IL_FALSE;
	RzILOpBool *test_cr_bit = cs_ppc_bc_cr_is_tested(bo) ? IL_TRUE : IL_FALSE;
	RzILOpBool *check_ctr_is_zero = cs_ppc_bc_tests_ctr_is_zero(bo) ? IL_TRUE : IL_FALSE;
	RzILOpBool *check_cr_bit_is_one = cs_ppc_bc_cr_bit_is_one(bo) ? IL_TRUE : IL_FALSE;
#else
	ut8 bo = PPC_READ_BO_FIELD;
	ut8 bi = PPC_READ_BI_FIELD;
	RzILOpPure *bo_0;
	RzILOpPure *bo_1;
	RzILOpPure *bo_2;
	RzILOpPure *bo_3;
	RzILOpPure *cr;
	RzILOpPure *cr_bit;
#endif
	RzILOpPure *ctr_cond_fullfilled;
	RzILOpPure *cr_cond_fullfilled;

	switch (id) {
	default:
		RZ_LOG_WARN("Instruction %d has no condition implemented.\n", id);
		return IL_FALSE;
		// For learning how the conditions of BCxxx branch instructions are
		// formed see the Power ISA
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQ:
	case PPC_INS_BEQA:
	case PPC_INS_BF:
	case PPC_INS_BFA:
	case PPC_INS_BGE:
	case PPC_INS_BGEA:
	case PPC_INS_BGT:
	case PPC_INS_BGTA:
	case PPC_INS_BLE:
	case PPC_INS_BLEA:
	case PPC_INS_BLT:
	case PPC_INS_BLTA:
	case PPC_INS_BNE:
	case PPC_INS_BNEA:
	case PPC_INS_BNG:
	case PPC_INS_BNGA:
	case PPC_INS_BNL:
	case PPC_INS_BNLA:
	case PPC_INS_BNS:
	case PPC_INS_BNSA:
	case PPC_INS_BNU:
	case PPC_INS_BNUA:
	case PPC_INS_BSO:
	case PPC_INS_BSOA:
	case PPC_INS_BT:
	case PPC_INS_BTA:
	case PPC_INS_BUN:
	case PPC_INS_BUNA:
	case PPC_INS_BGEL:
	case PPC_INS_BGELA:
	case PPC_INS_BGELR:
	case PPC_INS_BGELRL:
#endif
	case PPC_INS_BC:
	case PPC_INS_BCL:
	case PPC_INS_BCA:
	case PPC_INS_BCLA:
	case PPC_INS_BCLR:
	case PPC_INS_BCLRL:
#if CS_NEXT_VERSION >= 6
		ctr_cond_fullfilled = ITE(decr_ctr, XOR(NON_ZERO(VARG("ctr")), check_ctr_is_zero), IL_TRUE);
		cr_cond_fullfilled = ITE(test_cr_bit, XOR(get_cr_bit(bi + 32), INV(check_cr_bit_is_one)), IL_TRUE);
		return AND(ctr_cond_fullfilled, cr_cond_fullfilled);
#else
		// BO_2 == 0: Decrement CTR
		// BO_2 == 1: Don't use CTR
		bo_2 = NON_ZERO(LOGAND(UN(5, 0b00100), VARLP("bo")));

		// BO_3 == 0: Check CTR != 0
		// BO_3 == 1: Check CTR == 0
		bo_3 = NON_ZERO(LOGAND(UN(5, 0b00010), VARLP("bo")));
		ctr_cond_fullfilled = OR(bo_2, XOR(NON_ZERO(VARG("ctr")), bo_3)); // BO_2 | (CTR_M:63 ≠ 0) ⊕ BO_3

		// BO_0 == 0: Check CR_bi
		// BO_0 == 1: Don't check CR_bi
		bo_0 = NON_ZERO(LOGAND(UN(5, 0b10000), VARLP("bo")));

		// BO_1 == 0: Check CR_bi == 0
		// BO_1 == 1: Check CR_bi == 1
		bo_1 = NON_ZERO(LOGAND(UN(5, 0b01000), VARLP("bo")));
		cr_cond_fullfilled = OR(bo_0, XOR(get_cr_bit(bi + 32), INV(bo_1))); //  BO_0 | (CR_BI+32 ≡ BO_1)
		return LET("bo", UN(5, bo), AND(cr_cond_fullfilled, ctr_cond_fullfilled));
#endif
	case PPC_INS_BCCTR:
	case PPC_INS_BCCTRL:
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQCTR:
	case PPC_INS_BEQCTRL:
	case PPC_INS_BFCTR:
	case PPC_INS_BFCTRL:
	case PPC_INS_BGTCTR:
	case PPC_INS_BGTCTRL:
	case PPC_INS_BLECTR:
	case PPC_INS_BLECTRL:
	case PPC_INS_BLTCTR:
	case PPC_INS_BLTCTRL:
	case PPC_INS_BNECTR:
	case PPC_INS_BNECTRL:
	case PPC_INS_BNGCTR:
	case PPC_INS_BNGCTRL:
	case PPC_INS_BNLCTR:
	case PPC_INS_BNLCTRL:
	case PPC_INS_BNSCTR:
	case PPC_INS_BNSCTRL:
	case PPC_INS_BNUCTR:
	case PPC_INS_BNUCTRL:
	case PPC_INS_BSOCTR:
	case PPC_INS_BSOCTRL:
	case PPC_INS_BTCTR:
	case PPC_INS_BTCTRL:
	case PPC_INS_BUNCTR:
	case PPC_INS_BUNCTRL:
	case PPC_INS_BGECTR:
	case PPC_INS_BGECTRL:
#endif
#if CS_NEXT_VERSION >= 6
		cr_cond_fullfilled = AND(test_cr_bit, XOR(get_cr_bit(bi + 32), INV(check_cr_bit_is_one)));
		return cr_cond_fullfilled;
#else
		bo_0 = NON_ZERO(LOGAND(UN(5, 0b10000), VARLP("bo")));
		bo_1 = NON_ZERO(LOGAND(UN(5, 0b01000), VARLP("bo")));
		cr_cond_fullfilled = OR(bo_0, XOR(get_cr_bit(bi + 32), INV(bo_1))); //  BO_0 | (CR_BI+32 ≡ BO_1)

		return LET("bo", UN(5, bo), cr_cond_fullfilled);
	// CTR != 0
	case PPC_INS_BDNZ:
	case PPC_INS_BDNZA:
	case PPC_INS_BDNZL:
	case PPC_INS_BDNZLA:
	case PPC_INS_BDNZLR:
	case PPC_INS_BDNZLRL:
		return NON_ZERO(VARG("ctr"));
	// CTR == 0
	case PPC_INS_BDZ:
	case PPC_INS_BDZA:
	case PPC_INS_BDZL:
	case PPC_INS_BDZLA:
	case PPC_INS_BDZLR:
	case PPC_INS_BDZLRL:
		return IS_ZERO(VARG("ctr"));
	// ctr != 0 && cr_bi == 1
	case PPC_INS_BDNZT:
	case PPC_INS_BDNZTL:
	case PPC_INS_BDNZTA:
	case PPC_INS_BDNZTLA:
	// ctr == 0 && cr_bi == 1
	case PPC_INS_BDZT:
	case PPC_INS_BDZTL:
	case PPC_INS_BDZTA:
	case PPC_INS_BDZTLA:
		if (insn->detail->ppc.op_count == 1) {
			// If Capstone doesn't provide a CR register it means that the LT bit in cr0 is checked.
			cr = VARG("cr0");
			// LT bit
			cr_bit = UN(4, 8);
		} else {
			cr = VARG(get_crx_reg(handle, insn, 0));
			ut32 cond = get_crx_cond(handle, insn, 0);
			cr_bit = UN(4, ((cond & PPC_BC_SO) ? 1 : ((cond & PPC_BC_EQ) ? 2 : ((cond & PPC_BC_GT) ? 4 : 8))));
		}
		if (id == PPC_INS_BDZT || id == PPC_INS_BDZTL || id == PPC_INS_BDZTA || id == PPC_INS_BDZTLA) {
			return AND(IS_ZERO(VARG("ctr")), NON_ZERO(LOGAND(cr, cr_bit)));
		}
		return AND(NON_ZERO(VARG("ctr")), NON_ZERO(LOGAND(cr, cr_bit)));
	// ctr != 0 && cr_bi == 0
	case PPC_INS_BDNZF:
	case PPC_INS_BDNZFL:
	case PPC_INS_BDNZFA:
	case PPC_INS_BDNZFLA:
	// ctr == 0 && cr_bi == 0
	case PPC_INS_BDZF:
	case PPC_INS_BDZFL:
	case PPC_INS_BDZFA:
	case PPC_INS_BDZFLA:
		if (insn->detail->ppc.op_count == 1) {
			cr = VARG("cr0");
			cr_bit = UN(4, 8);
		} else {
			cr = VARG(get_crx_reg(handle, insn, 0));
			ut32 cond = get_crx_cond(handle, insn, 0);
			cr_bit = UN(4, ((cond & PPC_BC_SO) ? 1 : ((cond & PPC_BC_EQ) ? 2 : ((cond & PPC_BC_GT) ? 4 : 8))));
		}
		cr = insn->detail->ppc.op_count == 1 ? VARG("cr0") : VARG(get_crx_reg(handle, insn, 0));
		if (id == PPC_INS_BDZF || id == PPC_INS_BDZFL || id == PPC_INS_BDZFA || id == PPC_INS_BDZFLA) {
			return AND(IS_ZERO(VARG("ctr")), IS_ZERO(LOGAND(cr, cr_bit)));
		}
		return AND(NON_ZERO(VARG("ctr")), IS_ZERO(LOGAND(cr, cr_bit)));
#endif
	}
}

/**
 * \brief Get the branch instruction's target address.
 * In case of conditional branches it returns the address if the condition would be fulfilled.
 *
 * There are five types of target addresses:
 * * Absolute address
 * * Relative address (relative to current instruction address)
 * * Address stored in LR
 * * Address stored in CTR
 * * Address stored in TAR
 *
 * NOTE: Capstone calculates the NIA and fills the operand member with it.
 * We don't need to do the shift, add and extend here.
 *
 * \param insn The capstone instructions.
 * \param mode The capstone mode.
 * \return RzILOpPure* The target address of the jump.
 */
RZ_IPI RZ_OWN RzILOpPure *ppc_get_branch_ta(RZ_BORROW cs_insn *insn, const cs_mode mode) {
	rz_return_val_if_fail(insn, NULL);
	ut32 id = insn->id;

	switch (id) {
	default:
		RZ_LOG_WARN("Target address of branch instruction %d can not be resolved.\n", id);
		return UA(0);
	// Target address is pre-calculated by Capstone and stored in INSOP().imm
	// The comments show only the TA calculation according to the ISA.
	// Banch to absolute address
	case PPC_INS_BA:
	case PPC_INS_BLA:
	case PPC_INS_BCA:
	case PPC_INS_BCLA:
#if CS_NEXT_VERSION < 6
	case PPC_INS_BDNZTA:
	case PPC_INS_BDNZTLA:
	case PPC_INS_BDNZFA:
	case PPC_INS_BDNZFLA:
	case PPC_INS_BDZTA:
	case PPC_INS_BDZTLA:
	case PPC_INS_BDZFA:
	case PPC_INS_BDZFLA:
#endif
		// EXTS(LI || 0b00)
		// Branch to relative address
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQ:
	case PPC_INS_BEQA:
	case PPC_INS_BF:
	case PPC_INS_BFA:
	case PPC_INS_BGE:
	case PPC_INS_BGEA:
	case PPC_INS_BGT:
	case PPC_INS_BGTA:
	case PPC_INS_BLE:
	case PPC_INS_BLEA:
	case PPC_INS_BLT:
	case PPC_INS_BLTA:
	case PPC_INS_BNE:
	case PPC_INS_BNEA:
	case PPC_INS_BNG:
	case PPC_INS_BNGA:
	case PPC_INS_BNL:
	case PPC_INS_BNLA:
	case PPC_INS_BNS:
	case PPC_INS_BNSA:
	case PPC_INS_BNU:
	case PPC_INS_BNUA:
	case PPC_INS_BSO:
	case PPC_INS_BSOA:
	case PPC_INS_BT:
	case PPC_INS_BTA:
	case PPC_INS_BUN:
	case PPC_INS_BUNA:
#endif
	case PPC_INS_B:
	case PPC_INS_BL:
#if CS_NEXT_VERSION < 6
	case PPC_INS_BDZF:
	case PPC_INS_BDZFL:
	case PPC_INS_BDZT:
	case PPC_INS_BDZTL:
	case PPC_INS_BDNZF:
	case PPC_INS_BDNZFL:
	case PPC_INS_BDNZT:
	case PPC_INS_BDNZTL:
#endif
		// CIA + EXTS(LI || 0b00)
		if (insn->detail->ppc.op_count == 2) {
			return UA(INSOP(1).imm);
		} else {
			return UA(INSOP(0).imm);
		}
#if CS_NEXT_VERSION < 6
	case PPC_INS_BDZA:
	case PPC_INS_BDZLA:
	case PPC_INS_BDNZA:
	case PPC_INS_BDNZLA:
#endif
		// EXTS(BD || 0b00)
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BGEL:
	case PPC_INS_BGELA:
#endif
	case PPC_INS_BC:
	case PPC_INS_BCL:
#if CS_NEXT_VERSION >= 6
		return UA(INSOP(2).imm);
#else
	case PPC_INS_BDZ:
	case PPC_INS_BDZL:
	case PPC_INS_BDNZ:
	case PPC_INS_BDNZL:
		// If bits in cr0 are checked, the opcount is 1.
		// CIA + EXTS(BD || 0b00)
		if (insn->detail->ppc.op_count == 2) {
			return UA(INSOP(1).imm);
		} else {
			return UA(INSOP(0).imm);
		}
#endif
		// Branch to LR
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQLR:
	case PPC_INS_BEQLRL:
	case PPC_INS_BLELR:
	case PPC_INS_BLELRL:
	case PPC_INS_BLTLR:
	case PPC_INS_BLTLRL:
	case PPC_INS_BGELR:
	case PPC_INS_BGELRL:
#endif
	case PPC_INS_BLR:
	case PPC_INS_BLRL:
	case PPC_INS_BCLR:
	case PPC_INS_BCLRL:
#if CS_NEXT_VERSION < 6
	case PPC_INS_BDZLR:
	case PPC_INS_BDZLRL:
	case PPC_INS_BDNZLR:
	case PPC_INS_BDNZLRL:
#endif
		//  LR_0:61 || 0b00
		return LOGAND(UA(-4), VARG("lr"));
		// Branch to CTR
#if CS_API_MAJOR == 5 && CS_NEXT_VERSION < 6
	case PPC_INS_BEQCTR:
	case PPC_INS_BEQCTRL:
	case PPC_INS_BFCTR:
	case PPC_INS_BFCTRL:
	case PPC_INS_BGECTR:
	case PPC_INS_BGECTRL:
	case PPC_INS_BGTCTR:
	case PPC_INS_BGTCTRL:
	case PPC_INS_BLECTR:
	case PPC_INS_BLECTRL:
	case PPC_INS_BLTCTR:
	case PPC_INS_BLTCTRL:
	case PPC_INS_BNECTR:
	case PPC_INS_BNECTRL:
	case PPC_INS_BNGCTR:
	case PPC_INS_BNGCTRL:
	case PPC_INS_BNLCTR:
	case PPC_INS_BNLCTRL:
	case PPC_INS_BNSCTR:
	case PPC_INS_BNSCTRL:
	case PPC_INS_BNUCTR:
	case PPC_INS_BNUCTRL:
	case PPC_INS_BSOCTR:
	case PPC_INS_BSOCTRL:
	case PPC_INS_BTCTR:
	case PPC_INS_BTCTRL:
	case PPC_INS_BUNCTR:
	case PPC_INS_BUNCTRL:
#endif
	case PPC_INS_BCTR:
	case PPC_INS_BCTRL:
	case PPC_INS_BCCTR:
	case PPC_INS_BCCTRL:
		//  CTR_0:61 || 0b00
		return LOGAND(UA(-4), VARG("ctr"));
	}
}

/**
 * \brief Returns true if the multiplication instruction operates on double words.
 *
 * \param id The instruction id.
 * \param mode The Capstone mode.
 * \return bool True if the instruction operates on double words. False otherwise;
 */
RZ_IPI bool ppc_is_mul_div_d(const ut32 id, const cs_mode mode) {
	return id == PPC_INS_MULHD || id == PPC_INS_MULLD || id == PPC_INS_MULHDU ||
		id == PPC_INS_DIVD || id == PPC_INS_DIVDU || ((id == PPC_INS_MULLI) && IN_64BIT_MODE);
}

/**
 * \brief Returns true if the division instruction operates on double words.
 *
 * \param id The instruction id.
 * \param mode The Capstone mode.
 * \return bool True if the instruction operates on double words. False otherwise;
 */
RZ_IPI bool ppc_is_mul_div_u(const ut32 id) {
	return id == PPC_INS_MULHDU || id == PPC_INS_MULHWU || id == PPC_INS_DIVWU || id == PPC_INS_DIVDU;
}

/**
 * \brief Assembles the current XER value by combining the values
 * from the flag registers "so", "ov", "ca".
 *
 * \param mode The capstone mode.
 * \return RzILOpPure* The Pure containing the current XER value.
 */
RZ_IPI RZ_OWN RzILOpPure *ppc_get_xer(cs_mode mode) {
	RzILOpPure *so = SHIFTL0(EXTZ(BOOL_TO_BV(VARG("so"), 1)), U8(31));
	RzILOpPure *ov = SHIFTL0(EXTZ(BOOL_TO_BV(VARG("ov"), 1)), U8(30));
	RzILOpPure *ca = SHIFTL0(EXTZ(BOOL_TO_BV(VARG("ca"), 1)), U8(29));
	// For ISA v3 CPUs register ca32 and ov32 should be handled here as well.
	// Currently they are ignored. If you want to add them take a look at:
	// https://github.com/Rot127/rizin/tree/Examples-ppc-rzil-isav3-regs
	return LOGOR(LOGOR(so, ov), ca);
}

/**
 * \brief Sets the XER register to \p val and updates the flag register "so", "ov", "ca" accordingly.
 *
 * \param val The new value of XER.
 * \param mode The capstone mode.
 * \return RzILOpEffect* The sequence of effects setting all registers to their respective values.
 */
RZ_IPI RZ_OWN RzILOpEffect *ppc_set_xer(RzILOpPure *val, cs_mode mode) {
	rz_return_val_if_fail(val, NULL);
	RzILOpPure *v = LOGAND(BIT_MASK(64, U8(32), U8(34)), UNSIGNED(64, val));
	return SEQ5(SETL("v", v), SETG("xer", VARL("v")),
		SETG("so", BIT_IS_SET(VARL("v"), 64, U8(32))),
		SETG("ov", BIT_IS_SET(VARL("v"), 64, U8(33))),
		SETG("ca", BIT_IS_SET(VARL("v"), 64, U8(34))));
	// For ISA v3 CPUs register ca32 and ov32 should be handled here as well.
	// Currently they are ignored. If you want to add them take a look at:
	// https://github.com/Rot127/rizin/tree/Examples-ppc-rzil-isav3-regs
}

#include <rz_il/rz_il_opbuilder_end.h>

//
// IL helper END
//