// SPDX-FileCopyrightText: 2022 Dhruv Maroo <dhruvmaru007@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only

#include "sh_il.h"
#include <rz_il/rz_il_opbuilder_begin.h>
#include "../../../asm/arch/sh/regs.h"

/**
 * \file sh_il.c
 *
 * Converts SuperH-4 instructions to RzIL statements
 * References:
 *  - https://www.st.com/resource/en/user_manual/cd00147165-sh-4-32-bit-cpu-core-architecture-stmicroelectronics.pdf (SH-4 32-bit architecture manual)
 *  - https://www.renesas.com/in/en/document/mas/sh-4-software-manual?language=en (SH-4 manual by Renesas)
 *
 * Both the above references are almost the same
 *
 * \note Some things to know before working on this code:
 * 	- I have used the terms "operand(s)" and "param(s)" interchangeably, and both of them refer to the arguments/params/operands of the instruction
 *	- `op` doesn NOT mean operand. It is more akin to instruction or opcode. In majority of the places, it means the type of instruction
 *	- I have used the term "pure" in function names very loosely and shouldn't be used in a literal sense. Refer to the Doxygen documentation to
 * 	  know what a function does, do not infer it from the function name
 */

#define SH_U_ADDR(x) UN(SH_ADDR_SIZE, x)
#define SH_S_ADDR(x) SN(SH_ADDR_SIZE, x)
#define SH_U_REG(x)  UN(SH_REG_SIZE, (x))
#define SH_S_REG(x)  SN(SH_REG_SIZE, (x))
#define SH_BIT(x)    UN(1, x)
#define SH_TRUE      SH_U_REG(1)
#define SH_FALSE     SH_U_REG(0)

#define sh_il_get_pure_param(x) \
	sh_il_get_param(op->param[x], op->scaling).pure

#define sh_il_set_pure_param(x, val) \
	sh_il_set_param(op->param[x], val, op->scaling)

#define sh_il_get_effective_addr_param(x) \
	sh_il_get_effective_addr(op->param[x], op->scaling)

/* Utilities */

static bool sh_valid_gpr(ut16 reg) {
	return reg < SH_GPR_COUNT;
}

static bool sh_banked_reg(ut16 reg) {
	return reg < SH_BANKED_REG_COUNT;
}

/**
 * Registers available as global variables in the IL
 */
static const char *sh_global_registers[] = {
	"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", ///< bank 0 registers (user mode)
	"r0b", "r1b", "r2b", "r3b", "r4b", "r5b", "r6b", "r7b", ///< bank 1 registers (privileged mode)
	"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "sr",
	SH_SR_T, SH_SR_S, SH_SR_I, SH_SR_I, SH_SR_Q, SH_SR_M, SH_SR_F, SH_SR_B, SH_SR_R, SH_SR_D, ///< status register bits
	"gbr", "ssr", "spc", "sgr", "dbr", "vbr", "mach", "macl", "pr"
};

/**
 * \brief Cast \p val to \p len bits
 * This uses a local temp variable \p temp_var to store the value \p val
 * and then casts the local variable \p temp_var to its signed value
 * and stores that value in the local variable \p cast_var
 *
 * The purpose of this function is to remove the redundant IL block introduced
 * by `SIGNED` opbuilder macro (`MSB` of \p val for fill bits, and \p val for value),
 * and instead use a local variable to refer to \p val in both the above places,
 * making the IL dump much more concise and readable
 *
 * TODO: Ideally this should be integrated in `SIGNED` itself (need to be clever about it though)
 *
 * \param len
 * \param val
 * \param cast_var Casted variable name ; Use this variable to access the casted value in the caller
 * \param temp_var Temp variable name ; Do NOT use this variable outside this function
 * \return RzILOpEffect* Effect corresponding to setting the local variables
 */
RzILOpEffect *sh_il_signed(unsigned int len, RZ_OWN RzILOpPure *val, const char *cast_var, const char *temp_var) {
	RzILOpEffect *init = SETL(temp_var, val);
	RzILOpPure *cast = rz_il_op_new_cast(len, MSB(VARL(temp_var)), VARL(temp_var));
	RzILOpEffect *set = SETL(cast_var, cast);

	return SEQ2(init, set);
}

/**
 * \brief Get the register name for \p reg in bank \p bank
 *
 * \param reg Register index
 * \param bank Bank number
 * \return const char* IL global variable name
 */
static const char *sh_get_banked_reg(ut16 reg, ut8 bank) {
	if (!sh_banked_reg(reg) || bank > 1) {
		return NULL;
	}
	return sh_global_registers[reg + bank * SH_BANKED_REG_COUNT];
}

/**
 * \brief Convert \p b to SH_TRUE or SH_FALSE (bool zero-extended to SH_REG_SIZE)
 *
 * \param b RzILOpBool to be converted
 * \return RzILOpBitVector* Zero extended bitvector value
 */
static RzILOpBitVector *sh_il_bool_to_bv(RzILOpBool *b) {
	return ITE(b, SH_TRUE, SH_FALSE);
}

/**
 * \brief We need this because sometimes we would want an `RzILOpBitvector` back
 * when we ask for a status reg bit, so this returns us an RzILOpBitvector instead
 * of the `RzILOpBool` returned when using `VARG`
 *
 * \param bit The status register bit global variable name
 * \return RzILOpBitVector* Zero extended bitvector value
 */
static RzILOpBitVector *sh_il_get_status_reg_bit(const char *bit) {
	return sh_il_bool_to_bv(VARG(bit));
}

/**
 * \brief Return the status register (sr), calculated by shifting all the status register bits at the correct offsets
 *
 * \return RzILOpPure* The status register IL bitvector
 */
static RzILOpPure *sh_il_get_status_reg() {
	RzILOpPure *val = SH_U_REG(0);
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_D), val);
	val = SHIFTL0(val, SH_U_REG(1));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_R), val);
	val = SHIFTL0(val, SH_U_REG(1));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_B), val);
	val = SHIFTL0(val, SH_U_REG(13));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_F), val);
	val = SHIFTL0(val, SH_U_REG(6));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_M), val);
	val = SHIFTL0(val, SH_U_REG(1));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_Q), val);
	val = SHIFTL0(val, SH_U_REG(4));
	// VARG(SH_SR_I) is of 4 bits
	val = LOGOR(UNSIGNED(SH_REG_SIZE, VARG(SH_SR_I)), val);
	val = SHIFTL0(val, SH_U_REG(3));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_S), val);
	val = SHIFTL0(val, SH_U_REG(1));
	val = LOGOR(sh_il_get_status_reg_bit(SH_SR_T), val);

	return val;
}

/**
 * \brief Set the value of the status register (sr) to \p val by setting the values of the individual status register bits
 *
 * \param val
 * \return RzILOpEffect*
 */
static RzILOpEffect *sh_il_set_status_reg(RZ_OWN RzILOpPure *val) {
	RzILOpEffect *sreg = SETL("_sreg", val);
	RzILOpEffect *eff = SETG(SH_SR_T, LSB(VARL("_sreg")));

	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(1))));
	eff = SEQ2(eff, SETG(SH_SR_S, LSB(VARL("_sreg"))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(3))));
	eff = SEQ2(eff, SETG(SH_SR_I, LOGAND(UN(4, 0xf), UNSIGNED(4, VARL("_sreg")))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(4))));
	eff = SEQ2(eff, SETG(SH_SR_Q, LSB(VARL("_sreg"))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(1))));
	eff = SEQ2(eff, SETG(SH_SR_M, LSB(VARL("_sreg"))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(6))));
	eff = SEQ2(eff, SETG(SH_SR_F, LSB(VARL("_sreg"))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(13))));
	eff = SEQ2(eff, SETG(SH_SR_B, LSB(VARL("_sreg"))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(1))));
	eff = SEQ2(eff, SETG(SH_SR_R, LSB(VARL("_sreg"))));
	eff = SEQ2(eff, SETL("_sreg", SHIFTR0(VARL("_sreg"), SH_U_REG(1))));
	eff = SEQ2(eff, SETG(SH_SR_D, LSB(VARL("_sreg"))));

	return SEQ2(sreg, eff);
}

/**
 * \brief Set the value of the local variable "_priv"
 * This exists so that the privilege mode IL doesn't have to be duplicated everywhere,
 * instead one can directly use the local variable
 *
 * \return RzILOpEffect*
 */
static RzILOpEffect *sh_il_initialize_privilege() {
	return SETL("_priv", AND(VARG(SH_SR_D), VARG(SH_SR_R)));
}

/**
 * \brief Get the privilege mode
 * Do NOT call this before initializing privilege through `sh_il_initialize_privilege`
 * Otherwise, the local variable would not have been initialized
 * For all the liftings, this is taken care of in `rz_sh_il_opcode`
 *
 * \param ctx SHILContext instance used to store the whether privilege was checked or not
 * \return RzILOpPure* (RzILOpBool*) IL_TRUE if in privilege mode ; IL_FALSE otherwise
 */
static RzILOpPure *sh_il_get_privilege_ctx(SHILContext *ctx) {
	if (ctx) {
		ctx->privilege_check = true;
	}
	return VARL("_priv");
}

#define sh_il_get_privilege() sh_il_get_privilege_ctx(ctx)

/**
 * \brief Get register corresponding to \p reg index
 * This function is smart enough to give the correct register in case of banked registers or status register
 *
 * \param reg
 * \param ctx SHILContext instance
 * \return RzILOpPure*
 */
static RzILOpPure *sh_il_get_reg_ctx(ut16 reg, SHILContext *ctx) {
	if (!sh_banked_reg(reg) || !ctx->use_banked) {
		if (reg == SH_REG_IND_SR) {
			return sh_il_get_status_reg();
		}
		return VARG(sh_registers[reg]);
	}

	// check if both SR.MD = 1 and SR.RB = 1
	return ITE(sh_il_get_privilege(), VARG(sh_get_banked_reg(reg, 1)), VARG(sh_get_banked_reg(reg, 0)));
}

#define sh_il_get_reg(reg) sh_il_get_reg_ctx(reg, ctx)

/**
 * \brief Set the value of the register corresponding to index \p reg to value \p val
 * This function is smart enough to set values correctly in case of banked registers or status register
 *
 * \param reg
 * \param val
 * \param ctx SHILContext instance
 * \return RzILOpEffect*
 */
static RzILOpEffect *sh_il_set_reg_ctx(ut16 reg, RZ_OWN RzILOpPure *val, SHILContext *ctx) {
	if (!sh_banked_reg(reg) || !ctx->use_banked) {
		if (reg == SH_REG_IND_SR) {
			return sh_il_set_status_reg(val);
		}
		return SETG(sh_registers[reg], val);
	}

	return SEQ2(SETL("_regv", val), BRANCH(sh_il_get_privilege(), SETG(sh_get_banked_reg(reg, 1), VARL("_regv")), SETG(sh_get_banked_reg(reg, 0), VARL("_regv"))));
}

#define sh_il_set_reg(reg, val) sh_il_set_reg_ctx(reg, val, ctx)

/**
 * \brief Helper struct to take care of converting operands to IL
 */
typedef struct sh_param_helper_t {
	RzILOpEffect *pre; ///< pre effect for the operand
	RzILOpPure *pure; ///< pure effect for the operand
	RzILOpEffect *post; ///< post effect for the operand
} SHParamHelper;

/**
 * \brief Convert an unsigned 12 bit \p num to signed 12 bit num
 * I have used 16 bits to represent the numbers involved here,
 * but the 4 most significant bits are the same, so for all purposes
 * these are basically 12 bit numbers extended to 16 bits
 *
 * \param num
 * \return st16
 */
st16 convert_to_st12(ut16 num) {
	return (num << 4) >> 4;
}

/**
 * \brief Get the effective address obtained from the given \p param and \p scaling
 *
 * \param param
 * \param scaling
 * \param pc Program counter
 * \param ctx SHILContext instance
 * \return RzILOpPure*
 */
static RzILOpPure *sh_il_get_effective_addr_pc_ctx(SHParam param, SHScaling scaling, ut64 pc, SHILContext *ctx) {
	switch (param.mode) {
	case SH_REG_INDIRECT:
	case SH_REG_INDIRECT_I:
	case SH_REG_INDIRECT_D:
		return sh_il_get_reg(param.param[0]);
	case SH_REG_INDIRECT_DISP:
		return ADD(sh_il_get_reg(param.param[0]), MUL(SH_U_ADDR(param.param[1]), SH_U_ADDR(sh_scaling_size[scaling])));
	case SH_REG_INDIRECT_INDEXED:
		return ADD(sh_il_get_reg(SH_REG_IND_R0), sh_il_get_reg(param.param[0]));
	case SH_GBR_INDIRECT_DISP:
		return ADD(VARG("gbr"), MUL(SH_U_ADDR(param.param[0]), SH_U_ADDR(sh_scaling_size[scaling])));
	case SH_GBR_INDIRECT_INDEXED:
		return ADD(VARG("gbr"), sh_il_get_reg(SH_REG_IND_R0));
	case SH_PC_RELATIVE_DISP: {
		RzILOpBitVector *pcbv = SH_U_ADDR(pc);
		// mask lower 2 bits if long word
		if (scaling == SH_SCALING_L) {
			pcbv = LOGAND(pcbv, SH_U_ADDR(0xfffffffc));
		}
		pcbv = ADD(pcbv, SH_U_ADDR(4));
		return ADD(pcbv, MUL(SH_U_ADDR(param.param[0]), SH_U_ADDR(sh_scaling_size[scaling])));
	}
	case SH_PC_RELATIVE8: {
		// sign-extended for 8 bits and shifted left by 1 (i.e. multiplied by 2)
		RzILOpBitVector *relative = SHIFTL0(SH_S_ADDR((st8)param.param[0]), U32(1));
		return ADD(ADD(SH_U_ADDR(pc), SH_U_ADDR(4)), relative);
	}
	case SH_PC_RELATIVE12: {
		// sign-extended for 12 bits and shifted left by 1 (i.e. multiplied by 2)
		RzILOpBitVector *relative = SHIFTL0(SH_S_ADDR(convert_to_st12(param.param[0])), U32(1));
		return ADD(ADD(SH_U_ADDR(pc), SH_U_ADDR(4)), relative);
	}
	case SH_PC_RELATIVE_REG:
		return ADD(ADD(SH_U_ADDR(pc), SH_U_ADDR(4)), sh_il_get_reg(param.param[0]));
	default:
		RZ_LOG_WARN("RzIL: SuperH: No effective address for this mode: %u\n", param.mode);
	}

	return NULL;
}

#define sh_il_get_effective_addr(x, y) sh_il_get_effective_addr_pc_ctx(x, y, pc, ctx)

/**
 * \brief Convert the \p param with \p scaling to it's IL representation
 *
 * \param param
 * \param scaling
 * \param pc Program counter
 * \param ctx SHILContext instance
 * \return SHParamHelper Consists of the value of the param and the pre, post effects
 */
static SHParamHelper sh_il_get_param_pc_ctx(SHParam param, SHScaling scaling, ut64 pc, SHILContext *ctx) {
	SHParamHelper ret = {
		.pre = NULL,
		.pure = NULL,
		.post = NULL
	};

	/* In case of invalid scaling, just default to `SH_SCALING_L`, and assume param width of 32 bits (4 bytes)
	This makes the param calculation process simpler and doesn't lead to any functional difference */
	if (scaling == SH_SCALING_INVALID) {
		scaling = SH_SCALING_L;
	}

	switch (param.mode) {
	case SH_REG_DIRECT:
		if (scaling == SH_SCALING_L) {
			ret.pure = sh_il_get_reg(param.param[0]);
		} else {
			ret.pure = UNSIGNED(BITS_PER_BYTE * sh_scaling_size[scaling], sh_il_get_reg(param.param[0]));
		}
		break;
	case SH_REG_INDIRECT_I:
		ret.post = sh_il_set_reg(param.param[0], ADD(sh_il_get_reg(param.param[0]), SH_U_ADDR(sh_scaling_size[scaling])));
		goto set_pure;
	case SH_REG_INDIRECT_D:
		ret.pre = sh_il_set_reg(param.param[0], SUB(sh_il_get_reg(param.param[0]), SH_U_ADDR(sh_scaling_size[scaling])));
		goto set_pure;
	case SH_REG_INDIRECT:
	case SH_REG_INDIRECT_DISP:
	case SH_REG_INDIRECT_INDEXED:
	case SH_GBR_INDIRECT_DISP:
	case SH_GBR_INDIRECT_INDEXED:
	case SH_PC_RELATIVE_DISP:
	case SH_PC_RELATIVE8:
	case SH_PC_RELATIVE12:
	case SH_PC_RELATIVE_REG:
	set_pure:
		ret.pure = LOADW(BITS_PER_BYTE * sh_scaling_size[scaling], sh_il_get_effective_addr(param, scaling));
		break;
	case SH_IMM_U:
		ret.pure = UN(sh_scaling_size[scaling] * BITS_PER_BYTE, param.param[0]);
		break;
	case SH_IMM_S:
		ret.pure = SN(sh_scaling_size[scaling] * BITS_PER_BYTE, param.param[0]);
		break;
	default:
		RZ_LOG_ERROR("RzIL: SuperH: Invalid addressing mode\n");
	}

	return ret;
}

#define sh_il_get_param(x, y) sh_il_get_param_pc_ctx(x, y, pc, ctx)

/**
 * \brief Apply the effects in order: \p pre, \p target, \p post
 * The good thing about this function is that any of the arguments can be NULL
 * which implies that they do not exist/matter, and the final effect woulds
 * be calculated without these NULL arguments (keeping in mind the above order)
 *
 * \param target
 * \param pre
 * \param post
 * \return RzILOpEffect*
 */
static RzILOpEffect *sh_apply_effects(RZ_NULLABLE RzILOpEffect *target, RZ_NULLABLE RzILOpEffect *pre, RZ_NULLABLE RzILOpEffect *post) {
	if (!target) {
		if (pre) {
			target = pre;
			goto append;
		} else if (post) {
			return post;
		}
		return NULL;
	}

	if (pre) {
		target = SEQ2(pre, target);
	}
append:
	if (post) {
		target = SEQ2(target, post);
	}

	return target;
}

/**
 * \brief Set the value of the \p param at \p scaling to \p val
 * This function is smart enough to also apply any effects corresponding to the \p param
 *
 * \param param
 * \param val
 * \param scaling
 * \param pc Program counter
 * \param ctx SHILContext instance
 * \return RzILOpEffect*
 */
static RzILOpEffect *sh_il_set_param_pc_ctx(SHParam param, RZ_OWN RzILOpPure *val, SHScaling scaling, ut64 pc, SHILContext *ctx) {
	RzILOpEffect *ret = NULL, *pre = NULL, *post = NULL;
	switch (param.mode) {
	case SH_REG_DIRECT:
		if (scaling == SH_SCALING_INVALID || scaling == SH_SCALING_L) {
			ret = sh_il_set_reg(param.param[0], val);
		} else {
			/* We don't need to worry about sizes not matching up when calling `sh_il_signed` two times in an effect.
			This is because within an effect, the scaling will stay the same, so all the time `sh_il_signed` is called,
			it will be setting the local variables "_sign" and "_temp" to the same bitvector size.
			Thus, there will be no IL validation errors. */
			RzILOpEffect *cast = sh_il_signed(SH_REG_SIZE, val, "_sign", "_temp");
			ret = SEQ2(cast, sh_il_set_reg(param.param[0], VARL("_sign")));
		}
		break;
	case SH_REG_INDIRECT:
	case SH_REG_INDIRECT_I:
	case SH_REG_INDIRECT_D:
	case SH_REG_INDIRECT_DISP:
	case SH_REG_INDIRECT_INDEXED:
	case SH_GBR_INDIRECT_DISP:
	case SH_GBR_INDIRECT_INDEXED:
	case SH_PC_RELATIVE_DISP:
	case SH_PC_RELATIVE8:
	case SH_PC_RELATIVE12:
	case SH_PC_RELATIVE_REG:
		break;
	case SH_IMM_U:
	case SH_IMM_S:
	default:
		RZ_LOG_ERROR("RzIL: SuperH: Cannot set value for addressing mode: %u\n", param.mode);
		return NULL;
	}

	if (!ret) {
		SHParamHelper ret_h = sh_il_get_param(param, scaling);
		RZ_FREE(ret_h.pure);
		RzILOpPure *eff_addr = sh_il_get_effective_addr(param, scaling);
		ret = STOREW(eff_addr, val);
		pre = ret_h.pre;
		post = ret_h.post;
	}

	return sh_apply_effects(ret, pre, post);
}

#define sh_il_set_param(x, y, z) sh_il_set_param_pc_ctx(x, y, z, pc, ctx)

/**
 * \brief Check if there was a carry in the addition of \p x and \p y to get \p res
 * Here \p res = \p x + \p y (+ 1, optional)
 * This function can also be used of there was a carry bit added as well
 *
 * Pass in local variables to this function because otherwise the `DUP`s inside it will
 * lead to an unnecessarily long IL
 *
 * \param res
 * \param x
 * \param y
 * \return RzILOpBool* IL_TRUE if carry during addition ; IL_FALSE otherwise
 */
static RzILOpBool *sh_il_is_add_carry(RZ_OWN RzILOpPure *res, RZ_OWN RzILOpPure *x, RZ_OWN RzILOpPure *y) {
	// res = x + y
	RzILOpBool *xmsb = MSB(x);
	RzILOpBool *ymsb = MSB(y);
	RzILOpBool *resmsb = MSB(res);

	// x & y
	RzILOpBool *xy = AND(xmsb, ymsb);
	RzILOpBool *nres = INV(resmsb);

	// !res & y
	RzILOpBool *ry = AND(nres, DUP(ymsb));
	// x & !res
	RzILOpBool *xr = AND(DUP(xmsb), DUP(nres));

	// bit = xy | ry | xr
	RzILOpBool * or = OR(xy, ry);
	or = OR(or, xr);

	return or ;
}

/**
 * \brief Check if there was a borrow in the subtraction of \p x and \p y to get \p res
 * Here \p res = \p x - \p y (- 1, optional)
 * This function can also be used of there was a borrow bit added as well
 *
 * Pass in local variables to this function because otherwise the `DUP`s inside it will
 * lead to an unnecessarily long IL
 *
 * \param res
 * \param x
 * \param y
 * \return RzILOpBool* IL_TRUE if borrow during subtraction ; IL_FALSE otherwise
 */
static RzILOpBool *sh_il_is_sub_borrow(RZ_OWN RzILOpPure *res, RZ_OWN RzILOpPure *x, RZ_OWN RzILOpPure *y) {
	// res = x - y
	RzILOpBool *xmsb = MSB(x);
	RzILOpBool *ymsb = MSB(y);
	RzILOpBool *resmsb = MSB(res);

	// !x & y
	RzILOpBool *nx = INV(xmsb);
	RzILOpBool *nxy = AND(nx, ymsb);

	// y & res
	RzILOpBool *rny = AND(DUP(ymsb), resmsb);
	// res & !x
	RzILOpBool *rnx = AND(DUP(resmsb), DUP(nx));

	// bit = nxy | rny | rnx
	RzILOpBool * or = OR(nxy, rny);
	or = OR(or, rnx);

	return or ;
}

/**
 * \brief Check if there was a overflow in the addition of \p x and \p y to get \p res
 * Here \p res = \p x + \p y
 *
 * Pass in local variables to this function because otherwise the `DUP`s inside it will
 * lead to an unnecessarily long IL
 *
 * \param res
 * \param x
 * \param y
 * \return RzILOpBool* IL_TRUE if overflow during addition ; IL_FALSE otherwise
 */
static RzILOpBool *sh_il_is_add_overflow(RZ_OWN RzILOpPure *res, RZ_OWN RzILOpPure *x, RZ_OWN RzILOpPure *y) {
	// res = x + y
	RzILOpBool *xmsb = MSB(x);
	RzILOpBool *ymsb = MSB(y);
	RzILOpBool *resmsb = MSB(res);

	// !res & x & y
	RzILOpBool *nrxy = AND(AND(INV(resmsb), xmsb), ymsb);
	// res & !x & !y
	RzILOpBool *rnxny = AND(AND(DUP(resmsb), INV(DUP(xmsb))), INV(DUP(ymsb)));
	// or = nrxy | rnxny
	RzILOpBool * or = OR(nrxy, rnxny);

	return or ;
}

/**
 * \brief Check if there was a underflow in the subtraction of \p x and \p y to get \p res
 * Here \p res = \p x - \p y
 *
 * Pass in local variables to this function because otherwise the `DUP`s inside it will
 * lead to an unnecessarily long IL
 *
 * \param res
 * \param x
 * \param y
 * \return RzILOpBool* IL_TRUE if underflow during subtraction ; IL_FALSE otherwise
 */
static RzILOpBool *sh_il_is_sub_underflow(RZ_OWN RzILOpPure *res, RZ_OWN RzILOpPure *x, RZ_OWN RzILOpPure *y) {
	// res = x - y
	RzILOpBool *xmsb = MSB(x);
	RzILOpBool *ymsb = MSB(y);
	RzILOpBool *resmsb = MSB(res);

	// !res & x & !y
	RzILOpBool *nrxny = AND(AND(INV(resmsb), xmsb), INV(ymsb));
	// res & !x & y
	RzILOpBool *rnxy = AND(AND(DUP(resmsb), INV(DUP(xmsb))), DUP(ymsb));
	// or = nrxny | rnxy
	RzILOpBool * or = OR(nrxny, rnxy);

	return or ;
}

/* Instruction implementations */

/**
 * Unknown instruction
 */
static RzILOpEffect *sh_il_invalid(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return NULL;
}

/**
 * MOV family instructions
 */
static RzILOpEffect *sh_il_mov(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	SHParamHelper shp = sh_il_get_param(op->param[0], op->scaling);
	return sh_apply_effects(sh_il_set_pure_param(1, shp.pure), shp.pre, shp.post);
}

/**
 * MOVT	 Rn
 * T -> Rn
 * 0000nnnn00101001
 */
static RzILOpEffect *sh_il_movt(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, UNSIGNED(SH_REG_SIZE, sh_il_get_status_reg_bit(SH_SR_T)));
}

/**
 * SWAP.B  Rm, Rn
 * Rm -> swap lower 2 bytes -> REG
 * 0110nnnnmmmm1000
 *
 * SWAP.W  Rm, Rn
 * Rm -> swap upper/lower words -> Rn
 * 0110nnnnmmmm1001
 */
static RzILOpEffect *sh_il_swap(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	/* We won't be using `sh_il_{get,set}_param_pure`, because it will cast the pure value to the scaling size,
	but we want the whole register, which is why we need to call `sh_il_{get,set}_param directly` */
	if (op->scaling == SH_SCALING_B) {
		// swap lower two bytes
		RzILOpPure *lower_byte = LOGAND(sh_il_get_param(op->param[0], SH_SCALING_L).pure, SH_U_REG(0xff));
		RzILOpPure *new_lower_byte = LOGAND(SHIFTR0(sh_il_get_param(op->param[0], SH_SCALING_L).pure, SH_U_REG(BITS_PER_BYTE)), SH_U_REG(0xff));
		RzILOpPure *new_upper_byte = SHIFTL0(lower_byte, SH_U_REG(BITS_PER_BYTE));
		RzILOpPure *upper_word = LOGAND(sh_il_get_param(op->param[0], SH_SCALING_L).pure, SH_U_REG(0xffff0000));
		return sh_il_set_param(op->param[1], LOGOR(upper_word, LOGOR(new_upper_byte, new_lower_byte)), SH_SCALING_L);
	} else if (op->scaling == SH_SCALING_W) {
		// swap upper and lower words and store in dst
		RzILOpPure *high = SHIFTL0(sh_il_get_param(op->param[0], SH_SCALING_L).pure, SH_U_REG(BITS_PER_BYTE * 2));
		RzILOpPure *low = SHIFTR0(sh_il_get_param(op->param[0], SH_SCALING_L).pure, SH_U_REG(BITS_PER_BYTE * 2));
		return sh_il_set_param(op->param[1], LOGOR(high, low), SH_SCALING_L);
	}

	return NULL;
}

/**
 * XTRCT  Rm, Rn
 * Rm:Rn middle 32 bits -> Rn
 * 0010nnnnmmmm1101
 */
static RzILOpEffect *sh_il_xtrct(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *high = SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(BITS_PER_BYTE * 2));
	RzILOpPure *low = SHIFTR0(sh_il_get_pure_param(1), SH_U_REG(BITS_PER_BYTE * 2));
	return sh_il_set_pure_param(1, LOGOR(high, low));
}

/**
 * ADD  Rm, Rn
 * Rn + Rm -> Rn
 * 0011nnnnmmmm1100
 *
 * ADD #imm, Rn
 * Rn + imm -> Rn
 * 0111nnnniiiiiiii
 */
static RzILOpEffect *sh_il_add(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, ADD(sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
}

/**
 * ADDC  Rm, Rn
 * Rn + Rm + T -> Rn
 * carry -> T
 * 0011nnnnmmmm1110
 */
static RzILOpEffect *sh_il_addc(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *sum = ADD(sh_il_get_pure_param(0), sh_il_get_pure_param(1));
	RzILOpEffect *local_sum = SETL("sum", ADD(sum, UNSIGNED(SH_REG_SIZE, sh_il_get_status_reg_bit(SH_SR_T))));

	RzILOpEffect *tbit = SETG(SH_SR_T, sh_il_is_add_carry(VARL("sum"), sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
	RzILOpEffect *ret = sh_il_set_pure_param(1, VARL("sum"));
	return SEQ3(local_sum, tbit, ret);
}

/**
 * ADDV  Rm, Rn
 * Rn + Rm -> Rn
 * overflow -> T
 * 0011nnnnmmmm1111
 */
static RzILOpEffect *sh_il_addv(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *sum = ADD(sh_il_get_pure_param(0), sh_il_get_pure_param(1));
	RzILOpEffect *local_sum = SETL("sum", sum);

	RzILOpEffect *tbit = SETG(SH_SR_T, sh_il_is_add_overflow(VARL("sum"), sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
	RzILOpEffect *ret = sh_il_set_pure_param(1, VARL("sum"));
	return SEQ3(local_sum, tbit, ret);
}

/**
 * CMP/EQ  #imm, R0
 * When R0 = imm, 1 -> T ; Otherwise, 0 -> T
 * 10001000iiiiiiii
 *
 * CMP/EQ  Rm, Rn
 * When Rn = Rm, 1 -> T ; Otherwise, 0 -> T
 * 0011nnnnmmmm0000
 */
static RzILOpEffect *sh_il_cmp_eq(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, EQ(sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
}

/**
 * CMP/HS  Rm, Rn
 * When Rn >= Rm (unsigned), 1 -> T ; Otherwise, 0 -> T
 * 0011nnnnmmmm0010
 */
static RzILOpEffect *sh_il_cmp_hs(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, UGE(sh_il_get_pure_param(1), sh_il_get_pure_param(0)));
}

/**
 * CMP/GE  Rm, Rn
 * When Rn >= Rm (signed), 1 -> T ; Otherwise, 0 -> T
 * 0011nnnnmmmm0011
 */
static RzILOpEffect *sh_il_cmp_ge(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, SGE(sh_il_get_pure_param(1), sh_il_get_pure_param(0)));
}

/**
 * CMP/HI  Rm, Rn
 * When Rn > Rm (unsigned), 1 -> T ; Otherwise, 0 -> T
 * 0011nnnnmmmm0110
 */
static RzILOpEffect *sh_il_cmp_hi(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, UGT(sh_il_get_pure_param(1), sh_il_get_pure_param(0)));
}

/**
 * CMP/GT  Rm, Rn
 * When Rn > Rm (signed), 1 -> T ; Otherwise, 0 -> T
 * 0011nnnnmmmm0111
 */
static RzILOpEffect *sh_il_cmp_gt(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, SGT(sh_il_get_pure_param(1), sh_il_get_pure_param(0)));
}

/**
 * CMP/PZ  Rn
 * When Rn >= 0, 1 -> T ; Otherwise, 0 -> T
 * 0100nnnn00010001
 */
static RzILOpEffect *sh_il_cmp_pz(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, SGE(sh_il_get_pure_param(0), SH_S_REG(0)));
}

/**
 * CMP/PL  Rn
 * When Rn > 0, 1 -> T ; Otherwise, 0 -> T
 * 0100nnnn00010101
 */
static RzILOpEffect *sh_il_cmp_pl(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, SGT(sh_il_get_pure_param(0), SH_S_REG(0)));
}

/**
 * CMP/STR  Rm, Rn
 * When any bytes are equal, 1 -> T ; Otherwise, 0 -> T
 * 0010nnnnmmmm1100
 */
static RzILOpEffect *sh_il_cmp_str(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *full_xor = LOGXOR(sh_il_get_pure_param(0), sh_il_get_pure_param(1));
	RzILOpEffect *eff = SETL("xor", full_xor);

	eff = SEQ2(eff, SETL("eq", EQ(LOGAND(VARL("xor"), SH_U_REG(0xff)), SH_U_REG(0x0))));
	eff = SEQ2(eff, SETL("xor", SHIFTR0(VARL("xor"), SH_U_REG(BITS_PER_BYTE))));
	eff = SEQ2(eff, SETL("eq", OR(VARL("eq"), EQ(LOGAND(VARL("xor"), SH_U_REG(0xff)), SH_U_REG(0x0)))));
	eff = SEQ2(eff, SETL("xor", SHIFTR0(VARL("xor"), SH_U_REG(BITS_PER_BYTE))));
	eff = SEQ2(eff, SETL("eq", OR(VARL("eq"), EQ(LOGAND(VARL("xor"), SH_U_REG(0xff)), SH_U_REG(0x0)))));
	eff = SEQ2(eff, SETL("xor", SHIFTR0(VARL("xor"), SH_U_REG(BITS_PER_BYTE))));
	eff = SEQ2(eff, SETL("eq", OR(VARL("eq"), EQ(LOGAND(VARL("xor"), SH_U_REG(0xff)), SH_U_REG(0x0)))));

	return SEQ2(eff, SETG(SH_SR_T, VARL("eq")));
}

/**
 * DIV1  Rm, Rn
 * 1-step division (Rn ÷ Rm) ; Calculation result -> T
 * 0011nnnnmmmm0100
 */
static RzILOpEffect *sh_il_div1(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *ret = NULL;
	RzILOpEffect *q = SETL("q", VARG(SH_SR_Q));
	RzILOpEffect *m = SETL("m", VARG(SH_SR_M));
	RzILOpEffect *t = SETL("t", VARG(SH_SR_T));
	ret = SEQ3(q, m, t);

	RzILOpEffect *op1 = SETL("op1", sh_il_get_pure_param(0));
	RzILOpEffect *op2 = SETL("op2", sh_il_get_pure_param(1));
	RzILOpEffect *old_q = SETL("old_q", VARL("q"));
	ret = SEQ4(ret, op1, op2, old_q);

	// Get 31st bit of var q
	q = SETL("q", NON_ZERO(LOGAND(VARL("op2"), SH_U_REG(0x80000000))));
	op2 = SETL("op2", LOGOR(SHIFTL0(VARL("op2"), SH_U_REG(1)), sh_il_bool_to_bv(VARL("t"))));
	ret = SEQ3(ret, q, op2);

	RzILOpEffect *true_eff = SETL("op2", SUB(VARL("op2"), VARL("op1")));
	RzILOpEffect *false_eff = SETL("op2", ADD(VARL("op2"), VARL("op1")));
	RzILOpEffect *cond = BRANCH(EQ(sh_il_bool_to_bv(VARL("old_q")), sh_il_bool_to_bv(VARL("m"))), true_eff, false_eff);
	ret = SEQ2(ret, cond);

	// q = q ^ m ^ msb(op2)
	q = SETL("q", XOR(XOR(VARL("q"), VARL("m")), MSB(VARL("op2"))));
	t = SETL("t", NON_ZERO(SUB(SH_U_REG(1), LOGXOR(sh_il_bool_to_bv(VARL("q")), sh_il_bool_to_bv(VARL("m"))))));
	ret = SEQ3(ret, q, t);

	RzILOpEffect *rn = sh_il_set_pure_param(1, VARL("op2"));
	q = SETG(SH_SR_Q, VARL("q"));
	t = SETG(SH_SR_T, VARL("t"));
	ret = SEQ4(ret, rn, q, t);

	return ret;
}

/**
 * DIV0S  Rm, Rn
 * MSB of Rn -> Q ; MSB of Rm -> M, M^Q -> T
 * 0010nnnnmmmm0111
 */
static RzILOpEffect *sh_il_div0s(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *setq = SETG(SH_SR_Q, MSB(sh_il_get_pure_param(1)));
	RzILOpEffect *setm = SETG(SH_SR_M, MSB(sh_il_get_pure_param(0)));
	RzILOpEffect *sett = SETG(SH_SR_T, XOR(VARG(SH_SR_M), VARG(SH_SR_Q)));

	return SEQ3(setq, setm, sett);
}

/**
 * DIV0U  Rm, Rn
 * 0 -> M/Q/T
 * 0000000000011001
 */
static RzILOpEffect *sh_il_div0u(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SEQ3(SETG(SH_SR_M, IL_FALSE), SETG(SH_SR_Q, IL_FALSE), SETG(SH_SR_T, IL_FALSE));
}

/**
 * DMULS.L  Rm, Rn
 * Signed, Rn * Rm -> MAC ; 32 * 32 -> 64 bits
 * 0011nnnnmmmm1101
 */
static RzILOpEffect *sh_il_dmuls(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *eff = SETL("res_wide", MUL(SIGNED(2 * SH_REG_SIZE, sh_il_get_pure_param(0)), SIGNED(2 * SH_REG_SIZE, sh_il_get_pure_param(1))));
	RzILOpPure *lower_bits = UNSIGNED(SH_REG_SIZE, LOGAND(VARL("res_wide"), UN(2 * SH_REG_SIZE, 0xffffffff)));
	RzILOpPure *higher_bits = UNSIGNED(SH_REG_SIZE, SHIFTR0(VARL("res_wide"), SH_U_REG(SH_REG_SIZE)));
	return SEQ3(eff, SETG("macl", lower_bits), SETG("mach", higher_bits));
}

/**
 * DMULU.L  Rm, Rn
 * Unsigned, Rn * Rm -> MAC ; 32 * 32 -> 64 bits
 * 0011nnnnmmmm0101
 */
static RzILOpEffect *sh_il_dmulu(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *eff = SETL("res_wide", MUL(UNSIGNED(2 * SH_REG_SIZE, sh_il_get_pure_param(0)), UNSIGNED(2 * SH_REG_SIZE, sh_il_get_pure_param(1))));
	RzILOpPure *lower_bits = UNSIGNED(SH_REG_SIZE, LOGAND(VARL("res_wide"), UN(2 * SH_REG_SIZE, 0xffffffff)));
	RzILOpPure *higher_bits = UNSIGNED(SH_REG_SIZE, SHIFTR0(VARL("res_wide"), SH_U_REG(SH_REG_SIZE)));
	return SEQ3(eff, SETG("macl", lower_bits), SETG("mach", higher_bits));
}

/**
 * DT  Rn
 * Rn - 1 -> Rn ; When Rn = 0, 1 -> T ; Otherwise 0 -> T
 * 0100nnnn00010000
 */
static RzILOpEffect *sh_il_dt(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SEQ2(sh_il_set_pure_param(0, SUB(sh_il_get_pure_param(0), SH_U_REG(1))), SETG(SH_SR_T, IS_ZERO(sh_il_get_pure_param(0))));
}

/**
 * EXTS.B  Rm, Rn
 * Rm sign-extended from byte -> Rn
 * 0110nnnnmmmm1110
 *
 * EXTS.W  Rm, Rn
 * Rm sign-extended from word -> Rn
 * 0110nnnnmmmm1111
 */
static RzILOpEffect *sh_il_exts(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, sh_il_get_pure_param(0));
}

/**
 * EXTU.B  Rm, Rn
 * Rm zero-extended from byte -> Rn
 * 0110nnnnmmmm1100
 *
 * EXTU.W  Rm, Rn
 * Rm zero-extended from word -> Rn
 * 0110nnnnmmmm1101
 */
static RzILOpEffect *sh_il_extu(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	/* Do not use `sh_il_set_pure_param` here since that will sign extend the Rn value, but we want
	it extend it unsigned, which is why we need to directly call `sh_il_set_reg` */
	return sh_il_set_reg(op->param[1].param[0], UNSIGNED(SH_REG_SIZE, sh_il_get_pure_param(0)));
}

/**
 * MAC.L  @Rm+, @Rn+
 * Rn * Rm + MAC -> MAC (Signed) (32 * 32 + 64 -> 64 bits)
 * Rn + 4 -> Rn ; Rm + 4 -> Rm
 * 0000nnnnmmmm1111
 *
 * When S bit is enabled, the MAC addition is a saturation operation of 48 bits
 * So only the lower 48 bits of result and MAC are considered
 *
 * MAC.W  @Rm+, @Rn+
 * Rn * Rm + MAC -> MAC (Signed) (16 * 16 + 64 -> 64 bits)
 * Rn + 2 -> Rn ; Rm + 2 -> Rm
 * 0000nnnnmmmm1111
 *
 * When S bit is enabled, the MAC addition is a saturation operation of 32 bits
 * So only the lower 32 bits of result and MAC are considered (which is basically MACL register)
 */
static RzILOpEffect *sh_il_mac(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	SHParamHelper shp_rm = sh_il_get_param(op->param[0], op->scaling);
	SHParamHelper shp_rn = sh_il_get_param(op->param[1], op->scaling);
	RzILOpEffect *eff = NULL;

	if (op->scaling == SH_SCALING_L) {
		RzILOpEffect *mac = SETL("mac", LOGOR(SHIFTL0((UNSIGNED(2 * SH_REG_SIZE, VARG("mach"))), SH_U_REG(SH_REG_SIZE)), UNSIGNED(2 * SH_REG_SIZE, VARG("macl"))));
		RzILOpEffect *rm = SETL("rm", shp_rm.pure);
		RzILOpEffect *rn = SETL("rn", shp_rn.pure);
		eff = SEQ2(rm, rn);

		RzILOpEffect *mul = SETL("mul", MUL(SIGNED(2 * SH_REG_SIZE, VARL("rm")), SIGNED(2 * SH_REG_SIZE, VARL("rn"))));
		RzILOpEffect *add = SETL("add", ADD(VARL("mul"), VARL("mac")));
		RzILOpPure *lower_bits = UNSIGNED(SH_REG_SIZE, LOGAND(VARL("add"), UN(2 * SH_REG_SIZE, 0xffffffff)));
		RzILOpEffect *higher_bits = SETL("high", UNSIGNED(SH_REG_SIZE, SHIFTR0(VARL("add"), SH_U_REG(SH_REG_SIZE))));

		eff = SEQ7(eff, mac, mul, add, SETG("macl", lower_bits), higher_bits, BRANCH(VARG(SH_SR_S), SETG("mach", LOGAND(VARL("high"), SH_U_REG(0xffff))), SETG("mach", VARL("high"))));
	} else if (op->scaling == SH_SCALING_W) {
		RzILOpEffect *mac = SETL("mac", LOGOR(SHIFTL0((UNSIGNED(2 * SH_REG_SIZE, VARG("mach"))), SH_U_REG(SH_REG_SIZE)), UNSIGNED(2 * SH_REG_SIZE, VARG("macl"))));
		RzILOpEffect *rm = SETL("rm", shp_rm.pure);
		RzILOpEffect *rn = SETL("rn", shp_rn.pure);
		eff = SEQ2(rm, rn);

		RzILOpEffect *mul = SETL("mul", UNSIGNED(2 * SH_REG_SIZE, MUL(SIGNED(SH_REG_SIZE, VARL("rm")), SIGNED(SH_REG_SIZE, VARL("rn")))));
		RzILOpEffect *add = SETL("add", ADD(VARL("mul"), VARL("mac")));
		RzILOpEffect *lower_bits = SETL("low", UNSIGNED(SH_REG_SIZE, LOGAND(VARL("add"), UN(2 * SH_REG_SIZE, 0xffffffff))));
		RzILOpPure *higher_bits = UNSIGNED(SH_REG_SIZE, SHIFTR0(VARL("add"), SH_U_REG(SH_REG_SIZE)));

		eff = SEQ6(eff, mac, mul, add, lower_bits, BRANCH(VARG(SH_SR_S), SETG("macl", VARL("low")), SEQ2(SETG("macl", VARL("low")), SETG("mach", higher_bits))));
	}

	eff = SEQ3(eff, shp_rn.post, shp_rm.post);
	return eff;
}

/**
 * MUL.L  Rm, Rn
 * Rn * Rm -> MACL (32 * 32 -> 32 bits)
 * 0000nnnnmmmm0111
 */
static RzILOpEffect *sh_il_mul(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG("macl", MUL(sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
}

/**
 * MULS.W  Rm, Rn
 * Rn * Rm -> MACL (Signed) (16 * 16 -> 32 bits)
 * 0010nnnnmmmm1111
 */
static RzILOpEffect *sh_il_muls(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *rm = SETL("rm", sh_il_get_pure_param(0));
	RzILOpEffect *rn = SETL("rn", sh_il_get_pure_param(1));
	RzILOpPure *m = SIGNED(SH_REG_SIZE, VARL("rm"));
	RzILOpPure *n = SIGNED(SH_REG_SIZE, VARL("rn"));
	return SEQ3(rm, rn, SETG("macl", MUL(m, n)));
}

/**
 * MULU.W  Rm, Rn
 * Rn * Rm -> MACL (Unsigned) (16 * 16 -> 32 bits)
 * 0010nnnnmmmm1110
 */
static RzILOpEffect *sh_il_mulu(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *m = UNSIGNED(SH_REG_SIZE, sh_il_get_pure_param(0));
	RzILOpPure *n = UNSIGNED(SH_REG_SIZE, sh_il_get_pure_param(1));
	return SETG("macl", MUL(m, n));
}

/**
 * NEG  Rm, Rn
 * 0 - Rm -> Rn
 * 0110nnnnmmmm1011
 */
static RzILOpEffect *sh_il_neg(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *sub = SUB(SH_U_REG(0), sh_il_get_pure_param(0));
	return sh_il_set_pure_param(1, sub);
}

/**
 * NEGC  Rm, Rn
 * 0 - Rm - T -> Rn ; borrow -> T
 * 0110nnnnmmmm1010
 */
static RzILOpEffect *sh_il_negc(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *sub = SUB(SH_U_REG(0), sh_il_get_pure_param(0));
	RzILOpEffect *subvar = SETL("sub", SUB(sub, sh_il_get_status_reg_bit(SH_SR_T)));
	return SEQ3(subvar, sh_il_set_pure_param(1, VARL("sub")), SETG(SH_SR_T, sh_il_is_sub_borrow(VARL("sub"), SH_U_REG(0), sh_il_get_pure_param(0))));
}

/**
 * SUB  Rm, Rn
 * Rn - Rm -> Rn
 * 0011nnnnmmmm1000
 */
static RzILOpEffect *sh_il_sub(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, SUB(sh_il_get_pure_param(1), sh_il_get_pure_param(0)));
}

/**
 * SUBC  Rm, Rn
 * Rn - Rm - T -> Rn ; borrow -> T
 * 0011nnnnmmmm1010
 */
static RzILOpEffect *sh_il_subc(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *dif = SUB(sh_il_get_pure_param(1), sh_il_get_pure_param(0));
	dif = SUB(dif, sh_il_get_status_reg_bit(SH_SR_T));
	RzILOpEffect *local_dif = SETL("dif", dif);

	RzILOpEffect *ret = sh_il_set_pure_param(1, VARL("dif"));
	RzILOpEffect *tbit = SETG(SH_SR_T, sh_il_is_sub_borrow(VARL("dif"), sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
	return SEQ3(local_dif, ret, tbit);
}

/**
 * SUBV  Rm, Rn
 * Rn - Rm -> Rn ; underflow -> T
 * 0011nnnnmmmm1011
 */
static RzILOpEffect *sh_il_subv(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *dif = SUB(sh_il_get_pure_param(1), sh_il_get_pure_param(0));
	RzILOpEffect *local_dif = SETL("dif", dif);

	RzILOpEffect *ret = sh_il_set_pure_param(1, VARL("dif"));
	RzILOpEffect *tbit = SETG(SH_SR_T, sh_il_is_sub_underflow(VARL("dif"), sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
	return SEQ3(local_dif, ret, tbit);
}

/**
 * AND  Rm, Rn
 * Rn & Rm -> Rn
 * 0010nnnnmmmm1001
 *
 * AND  #imm, R0
 * R0 & imm -> R0
 * 11001001iiiiiiii
 *
 * AND.B  #imm, @(R0, GBR)
 * (R0 + GBR) & imm -> (R0 + GBR)
 * 11001101iiiiiiii
 */
static RzILOpEffect *sh_il_and(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, LOGAND(sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
}

/**
 * NOT  Rm, Rn
 * ~Rm -> Rn
 * 0110nnnnmmmm0111
 */
static RzILOpEffect *sh_il_not(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, LOGNOT(sh_il_get_pure_param(0)));
}

/**
 * OR  Rm, Rn
 * Rn | Rm -> Rn
 * 0010nnnnmmmm1011
 *
 * OR  #imm, R0
 * R0 | imm -> R0
 * 11001011iiiiiiii
 *
 * OR.B  #imm, @(R0, GBR)
 * (R0 + GBR) | imm -> (R0 + GBR)
 * 11001111iiiiiiii
 */
static RzILOpEffect *sh_il_or(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, LOGOR(sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
}

/**
 * TAS.B  @Rn
 * If (Rn) = 0, 1 -> T ; Otherwise 0 -> T
 * 1 -> MSB of (Rn)
 * 0110nnnnmmmm0111
 */
static RzILOpEffect *sh_il_tas(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *mem = sh_il_get_pure_param(0);
	RzILOpEffect *tbit = SETG(SH_SR_T, IS_ZERO(mem));
	return SEQ2(tbit, sh_il_set_pure_param(0, LOGOR(DUP(mem), UN(8, 0x80))));
}

/**
 * TST  Rm, Rn
 * If Rn & Rm = 0, 1 -> T ; Otherwise 0 -> T
 * 0010nnnnmmmm1000
 *
 * TST  #imm, R0
 * If R0 & imm = 0, 1 -> T ; Otherwise 0 -> T
 * 11001000iiiiiiii
 *
 * TST.B  #imm, @(R0, GBR)
 * If (R0 + GBR) & imm = 0, 1 -> T ; Otherwise 0 -> T
 * 11001100iiiiiiii
 */
static RzILOpEffect *sh_il_tst(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, IS_ZERO(LOGAND(sh_il_get_pure_param(0), sh_il_get_pure_param(1))));
}

/**
 * XOR  Rm, Rn
 * Rn ^ Rm -> Rn
 * 0010nnnnmmmm1010
 *
 * XOR  #imm, R0
 * R0 ^ imm -> R0
 * 11001010iiiiiiii
 *
 * XOR.B  #imm, @(R0, GBR)
 * (R0 + GBR) ^ imm -> (R0 + GBR)
 * 11001110iiiiiiii
 */
static RzILOpEffect *sh_il_xor(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, LOGXOR(sh_il_get_pure_param(0), sh_il_get_pure_param(1)));
}

/**
 * ROTL  Rn
 * T <- Rn <- MSB
 * 0100nnnn00000100
 */
static RzILOpEffect *sh_il_rotl(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *msb = SETL("msb_", MSB(sh_il_get_pure_param(0)));
	RzILOpEffect *tbit = SETG(SH_SR_T, VARL("msb_"));
	RzILOpEffect *shl = SETL("shl_", SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(1)));
	RzILOpPure *lsb = ITE(VARL("msb_"), LOGOR(VARL("shl_"), SH_U_REG(1)), VARL("shl_"));
	return SEQ4(msb, tbit, shl, sh_il_set_pure_param(0, lsb));
}

/**
 * ROTR  Rn
 * LSB -> Rn -> T
 * 0100nnnn00000101
 */
static RzILOpEffect *sh_il_rotr(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *lsb = SETL("lsb_", LSB(sh_il_get_pure_param(0)));
	RzILOpEffect *tbit = SETG(SH_SR_T, VARL("lsb_"));
	RzILOpEffect *shr = SETL("shr_", SHIFTR0(sh_il_get_pure_param(0), SH_U_REG(1)));
	RzILOpPure *msb = ITE(VARL("lsb_"), LOGOR(VARL("shr_"), SH_U_REG(0x80000000)), VARL("shr_"));
	return SEQ4(lsb, tbit, shr, sh_il_set_pure_param(0, msb));
}

/**
 * ROTCL  Rn
 * T <- Rn <- T
 * 0100nnnn00100100
 */
static RzILOpEffect *sh_il_rotcl(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *msb = SETL("msb_", MSB(sh_il_get_pure_param(0)));
	RzILOpEffect *shl = SETL("shl_", SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(1)));
	RzILOpPure *lsb = ITE(VARG(SH_SR_T), LOGOR(VARL("shl_"), SH_U_REG(1)), VARL("shl_"));
	RzILOpEffect *tbit = SETG(SH_SR_T, VARL("msb_"));
	return SEQ4(msb, shl, sh_il_set_pure_param(0, lsb), tbit);
}

/**
 * ROTCR  Rn
 * T -> Rn -> T
 * 0100nnnn00100101
 */
static RzILOpEffect *sh_il_rotcr(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *lsb = SETL("lsb_", LSB(sh_il_get_pure_param(0)));
	RzILOpEffect *shr = SETL("shr_", SHIFTR0(sh_il_get_pure_param(0), SH_U_REG(1)));
	RzILOpPure *msb = ITE(VARG(SH_SR_T), LOGOR(VARL("shr_"), SH_U_REG(0x80000000)), VARL("shr_"));
	RzILOpEffect *tbit = SETG(SH_SR_T, VARL("lsb_"));
	return SEQ4(lsb, shr, sh_il_set_pure_param(0, msb), tbit);
}

/**
 * SHAD  Rm, Rn
 * If Rn >= 0, Rn << Rm -> Rn
 * If Rn < 0, Rn >> Rm -> [MSB -> Rn]
 * MSB -> Rn
 * 0100nnnnmmmm1100
 */
static RzILOpEffect *sh_il_shad(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *shift_amount = SETL("shift_", UNSIGNED(5, sh_il_get_pure_param(0)));

	RzILOpPure *shl = SHIFTL0(sh_il_get_pure_param(1), VARL("shift_"));
	RzILOpPure *shr = SHIFTRA(sh_il_get_pure_param(1), NEG(VARL("shift_")));

	return SEQ2(shift_amount, BRANCH(SGE(sh_il_get_pure_param(0), SN(32, 0)), sh_il_set_pure_param(1, shl), sh_il_set_pure_param(1, shr)));
}

/**
 * SHAL  Rn
 * T <- Rn <- 0
 * 0100nnnn00100000
 */
static RzILOpEffect *sh_il_shal(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *msb = MSB(sh_il_get_pure_param(0));
	RzILOpPure *shl = SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(1));
	return SEQ2(SETG(SH_SR_T, msb), sh_il_set_pure_param(0, shl));
}

/**
 * SHAR  Rn
 * MSB -> Rn -> T
 * 0100nnnn00100001
 */
static RzILOpEffect *sh_il_shar(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *lsb = LSB(sh_il_get_pure_param(0));
	RzILOpPure *shl = SHIFTRA(sh_il_get_pure_param(0), SH_U_REG(1));
	return SEQ2(SETG(SH_SR_T, lsb), sh_il_set_pure_param(0, shl));
}

/**
 * SHLD  Rm, Rn
 * If Rn >= 0, Rn << Rm -> Rn
 * If Rn < 0, Rn >> Rm -> [0 -> Rn]
 * MSB -> Rn
 * 0100nnnnmmmm1101
 */
static RzILOpEffect *sh_il_shld(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpEffect *shift_amount = SETL("shift_", UNSIGNED(5, sh_il_get_pure_param(0)));

	RzILOpPure *shl = SHIFTL0(sh_il_get_pure_param(1), VARL("shift_"));
	RzILOpPure *shr = SHIFTR0(sh_il_get_pure_param(1), NEG(VARL("shift_")));

	return SEQ2(shift_amount, BRANCH(SGE(sh_il_get_pure_param(0), SN(32, 0)), sh_il_set_pure_param(1, shl), sh_il_set_pure_param(1, shr)));
	;
}

/**
 * SHLL  Rn
 * T <- Rn <- 0
 * 0100nnnn00000000
 */
static RzILOpEffect *sh_il_shll(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *msb = MSB(sh_il_get_pure_param(0));
	RzILOpPure *shl = SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(1));
	return SEQ2(SETG(SH_SR_T, msb), sh_il_set_pure_param(0, shl));
}

/**
 * SHLR  Rn
 * 0 -> Rn -> T
 * 0100nnnn00000001
 */
static RzILOpEffect *sh_il_shlr(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *lsb = LSB(sh_il_get_pure_param(0));
	RzILOpPure *shr = SHIFTR0(sh_il_get_pure_param(0), SH_U_REG(1));
	return SEQ2(SETG(SH_SR_T, lsb), sh_il_set_pure_param(0, shr));
}

/**
 * SHLL2  Rn
 * Rn << 2 -> Rn
 * 0100nnnn00001000
 */
static RzILOpEffect *sh_il_shll2(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(2)));
}

/**
 * SHLR2  Rn
 * Rn >> 2 -> Rn
 * 0100nnnn00001001
 */
static RzILOpEffect *sh_il_shlr2(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, SHIFTR0(sh_il_get_pure_param(0), SH_U_REG(2)));
}

/**
 * SHLL8  Rn
 * Rn << 8 -> Rn
 * 0100nnnn00011000
 */
static RzILOpEffect *sh_il_shll8(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(8)));
}

/**
 * SHLR8  Rn
 * Rn >> 8 -> Rn
 * 0100nnnn00011001
 */
static RzILOpEffect *sh_il_shlr8(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, SHIFTR0(sh_il_get_pure_param(0), SH_U_REG(8)));
}

/**
 * SHLL16  Rn
 * Rn << 16 -> Rn
 * 0100nnnn00101000
 */
static RzILOpEffect *sh_il_shll16(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, SHIFTL0(sh_il_get_pure_param(0), SH_U_REG(16)));
}

/**
 * SHLR16  Rn
 * Rn >> 16 -> Rn
 * 0100nnnn00101001
 */
static RzILOpEffect *sh_il_shlr16(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(0, SHIFTR0(sh_il_get_pure_param(0), SH_U_REG(16)));
}

/**
 * BF  label
 * if T = 0, disp * 2 + PC + 4 -> PC ; otherwise (T = 1) NOP
 * 10001011dddddddd
 */
static RzILOpEffect *sh_il_bf(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *new_pc = sh_il_get_effective_addr_param(0);
	return BRANCH(VARG(SH_SR_T), JMP(new_pc), NOP());
}

/**
 * BF/S  label
 * if T = 0, disp * 2 + PC + 4 -> PC ; otherwise (T = 1) NOP ; delayed branch
 * 10001111dddddddd
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_bfs(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *new_pc = sh_il_get_effective_addr_param(0);
	return BRANCH(VARG(SH_SR_T), JMP(new_pc), NOP());
}

/**
 * BT  label
 * if T = 1, disp * 2 + PC + 4 -> PC ; otherwise (T = 0) NOP
 * 10001001dddddddd
 */
static RzILOpEffect *sh_il_bt(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *new_pc = sh_il_get_effective_addr_param(0);
	return BRANCH(VARG(SH_SR_T), JMP(new_pc), NOP());
}

/**
 * BT/S  label
 * if T = 1, disp * 2 + PC + 4 -> PC ; otherwise (T = 0) NOP ; delayed branch
 * 10001101dddddddd
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_bts(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RzILOpPure *new_pc = sh_il_get_effective_addr_param(0);
	return BRANCH(VARG(SH_SR_T), JMP(new_pc), NOP());
}

/**
 * BRA  label
 * disp * 2 + PC + 4 -> PC ; delayed branch
 * 1010dddddddddddd
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_bra(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return JMP(sh_il_get_effective_addr_param(0));
}

/**
 * BRAF  Rn
 * Rn + PC + 4 -> PC ; delayed branch
 * 0000nnnn00100011
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_braf(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return JMP(sh_il_get_effective_addr_param(0));
}

/**
 * BSR  label
 * PC + 4 -> PR ; disp * 2 + PC + 4 -> PC ; delayed branch
 * 1011dddddddddddd
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_bsr(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SEQ2(SETG("pr", ADD(SH_U_ADDR(pc), SH_U_ADDR(4))), JMP(sh_il_get_effective_addr_param(0)));
}

/**
 * BSRF  Rn
 * PC + 4 -> PR ; Rn + PC + 4 -> PC ; delayed branch
 * 0000nnnn00000011
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_bsrf(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SEQ2(SETG("pr", ADD(SH_U_ADDR(pc), SH_U_ADDR(4))), JMP(sh_il_get_effective_addr_param(0)));
}

/**
 * JMP  @Rn
 * Rn -> PC ; delayed branch
 * 0100nnnn00101011
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_jmp(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return JMP(sh_il_get_effective_addr_param(0));
}

/**
 * JSR  @Rn
 * PC + 4 -> PR ; Rn -> PC ; delayed branch
 * 0100nnnn00001011
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_jsr(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SEQ2(SETG("pr", ADD(SH_U_ADDR(pc), SH_U_ADDR(4))), JMP(sh_il_get_effective_addr_param(0)));
}

/**
 * RTS
 * PR -> PC ; delayed branch
 * 0000000000001011
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_rts(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return JMP(VARG("pr"));
}

/**
 * CLRMAC
 * 0 -> MACH, MACL
 * 0000000000101000
 */
static RzILOpEffect *sh_il_clrmac(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SEQ2(SETG("mach", UN(SH_REG_SIZE, 0)), SETG("macl", UN(SH_REG_SIZE, 0)));
}

/**
 * CLRS
 * 0 -> S
 * 0000000001001000
 */
static RzILOpEffect *sh_il_clrs(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_S, IL_FALSE);
}

/**
 * CLRT
 * 0 -> T
 * 0000000000001000
 */
static RzILOpEffect *sh_il_clrt(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, IL_FALSE);
}

/**
 * LDC  Rm, REG
 * REG := SR/GBR/VBR/SSR/SPC/DBR/Rn_BANK
 * Rm -> REG
 * PRIVILEGED (Only GBR is not privileged)
 *
 * LDC.L  @Rm+, REG
 * REG := SR/GBR/VBR/SSR/SPC/DBR/Rn_BANK
 * (Rm) -> REG ; Rm + 4 -> Rm
 * PRIVILEGED (Only GBR is not privileged)
 */
static RzILOpEffect *sh_il_ldc(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	/* We won't be using banked registers for these instructions (except for unprivileged GBR) */
	// TODO: Check what the correct implementation is
	ctx->use_banked = false;
	if (op->param[1].param[0] == SH_REG_IND_GBR) {
		ctx->use_banked = true;
	}

	RzILOpEffect *eff = NULL;
	if (op->scaling == SH_SCALING_INVALID) {
		if (sh_valid_gpr(op->param[1].param[0])) {
			eff = SETG(sh_get_banked_reg(op->param[1].param[0], 1), sh_il_get_pure_param(0));
		} else {
			eff = sh_il_set_pure_param(1, sh_il_get_pure_param(0));
		}
	} else if (op->scaling == SH_SCALING_L) {
		SHParamHelper rm = sh_il_get_param(op->param[0], op->scaling);
		if (sh_valid_gpr(op->param[1].param[0])) {
			eff = SEQ2(SETG(sh_get_banked_reg(op->param[1].param[0], 1), rm.pure), rm.post);
		} else {
			eff = SEQ2(sh_il_set_pure_param(1, rm.pure), rm.post);
		}
	}

	if (op->param[1].param[0] != SH_REG_IND_GBR) {
		eff = BRANCH(sh_il_get_privilege(), eff, EMPTY());
	}
	return eff;
}

/**
 * LDS  Rm, REG
 * REG := MACH/MACL/PR
 * Rm -> REG
 *
 * LDS.L  @Rm+, REG
 * REG := MACH/MACL/PR
 * (Rm) -> REG ; Rm + 4 -> Rm
 */
static RzILOpEffect *sh_il_lds(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	if (op->scaling == SH_SCALING_INVALID) {
		return sh_il_set_pure_param(1, sh_il_get_pure_param(0));
	} else if (op->scaling == SH_SCALING_L) {
		SHParamHelper rm = sh_il_get_param(op->param[0], op->scaling);
		return SEQ2(sh_il_set_pure_param(1, rm.pure), rm.post);
	}
	return NOP();
}

/**
 * MOVCA.L  R0, @Rn
 * R0 -> (Rn) (without fetching cache block)
 * 0000nnnn11000011
 */
static RzILOpEffect *sh_il_movca(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, sh_il_get_pure_param(0));
}

/**
 * NOP
 * No operation
 * 0000000000001001
 */
static RzILOpEffect *sh_il_nop(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return NOP();
}

/**
 * RTE
 * SSR -> SR ; SPC -> PC ; delayed branch
 * 0000000000101011
 * PRIVILEGED
 * TODO: Implement delayed branch
 */
static RzILOpEffect *sh_il_rte(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return BRANCH(sh_il_get_privilege(), SEQ2(sh_il_set_status_reg(VARG("ssr")), JMP(VARG("spc"))), EMPTY());
}

/**
 * SETS
 * 1 -> S
 * 0000000001011000
 */
static RzILOpEffect *sh_il_sets(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_S, IL_TRUE);
}

/**
 * SETT
 * 1 -> T
 * 0000000000011000
 */
static RzILOpEffect *sh_il_sett(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return SETG(SH_SR_T, IL_TRUE);
}

/**
 * SLEEP
 * Sleep or standby (so effectively, just a NOP)
 * 0000000000011011
 * PRIVILEGED
 */
static RzILOpEffect *sh_il_sleep(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return BRANCH(sh_il_get_privilege(), NOP(), EMPTY());
}

/**
 * STC  REG, Rn
 * REG := SR/GBR/VBR/SSR/SPC/SGR/DBR/Rn_BANK
 * REG -> Rn
 * PRIVILEGED (Only GBR is not privileged)
 *
 * STC.L  REG, @-Rn
 * REG := SR/GBR/VBR/SSR/SPC/SGR/DBR/Rn_BANK
 * Rn - 4 -> Rn ; REG -> (Rn)
 * PRIVILEGED (Only GBR is not privileged)
 */
static RzILOpEffect *sh_il_stc(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	/* We won't be using banked registers for these instructions (except for unprivileged GBR) */
	// TODO: Check what the correct implementation is
	ctx->use_banked = false;
	if (op->param[0].param[0] == SH_REG_IND_GBR) {
		ctx->use_banked = true;
	}

	RzILOpEffect *eff = NULL;
	if (sh_valid_gpr(op->param[0].param[0])) { // REG = Rn_BANK
		eff = sh_il_set_pure_param(1, VARG(sh_get_banked_reg(op->param[0].param[0], 1)));
	} else {
		eff = sh_il_set_pure_param(1, sh_il_get_pure_param(0));
	}
	if (op->param[0].param[0] != SH_REG_IND_GBR) {
		eff = BRANCH(sh_il_get_privilege(), eff, EMPTY());
	}
	return eff;
}

/**
 * STS  REG, Rn
 * REG := MACH/MACL/PR
 * REG -> Rn
 *
 * STS.L  REG, @-Rn
 * REG := MACH/MACL/PR
 * Rn + 4 -> Rn ; REG -> (Rn)
 */
static RzILOpEffect *sh_il_sts(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	return sh_il_set_pure_param(1, sh_il_get_pure_param(0));
}

/**
 * \brief Unimplemented instruction/opcode
 * To be used for valid SuperH-4 instruction which yet haven't been lifted to the IL
 */
static RzILOpEffect *sh_il_unimpl(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx) {
	RZ_LOG_DEBUG("SuperH: Instruction with opcode 0x%04x is unimplemented\n", op->opcode);
	return EMPTY();
}

#include <rz_il/rz_il_opbuilder_end.h>

typedef RzILOpEffect *(*sh_il_op)(const SHOp *op, ut64 pc, RzAnalysis *analysis, SHILContext *ctx);

/**
 * \brief Lookup table for the IL lifting handlers for the various instructions
 */
static const sh_il_op sh_ops[SH_OP_SIZE] = {
	[SH_OP_INVALID] = sh_il_invalid,
	[SH_OP_MOV] = sh_il_mov,
	[SH_OP_MOVT] = sh_il_movt,
	[SH_OP_SWAP] = sh_il_swap,
	[SH_OP_XTRCT] = sh_il_xtrct,
	[SH_OP_ADD] = sh_il_add,
	[SH_OP_ADDC] = sh_il_addc,
	[SH_OP_ADDV] = sh_il_addv,
	[SH_OP_CMP_EQ] = sh_il_cmp_eq,
	[SH_OP_CMP_HS] = sh_il_cmp_hs,
	[SH_OP_CMP_GE] = sh_il_cmp_ge,
	[SH_OP_CMP_HI] = sh_il_cmp_hi,
	[SH_OP_CMP_GT] = sh_il_cmp_gt,
	[SH_OP_CMP_PZ] = sh_il_cmp_pz,
	[SH_OP_CMP_PL] = sh_il_cmp_pl,
	[SH_OP_CMP_STR] = sh_il_cmp_str,
	[SH_OP_DIV1] = sh_il_div1,
	[SH_OP_DIV0S] = sh_il_div0s,
	[SH_OP_DIV0U] = sh_il_div0u,
	[SH_OP_DMULS] = sh_il_dmuls,
	[SH_OP_DMULU] = sh_il_dmulu,
	[SH_OP_DT] = sh_il_dt,
	[SH_OP_EXTS] = sh_il_exts,
	[SH_OP_EXTU] = sh_il_extu,
	[SH_OP_MAC] = sh_il_mac,
	[SH_OP_MUL] = sh_il_mul,
	[SH_OP_MULS] = sh_il_muls,
	[SH_OP_MULU] = sh_il_mulu,
	[SH_OP_NEG] = sh_il_neg,
	[SH_OP_NEGC] = sh_il_negc,
	[SH_OP_SUB] = sh_il_sub,
	[SH_OP_SUBC] = sh_il_subc,
	[SH_OP_SUBV] = sh_il_subv,
	[SH_OP_AND] = sh_il_and,
	[SH_OP_NOT] = sh_il_not,
	[SH_OP_OR] = sh_il_or,
	[SH_OP_TAS] = sh_il_tas,
	[SH_OP_TST] = sh_il_tst,
	[SH_OP_XOR] = sh_il_xor,
	[SH_OP_ROTL] = sh_il_rotl,
	[SH_OP_ROTR] = sh_il_rotr,
	[SH_OP_ROTCL] = sh_il_rotcl,
	[SH_OP_ROTCR] = sh_il_rotcr,
	[SH_OP_SHAD] = sh_il_shad,
	[SH_OP_SHAL] = sh_il_shal,
	[SH_OP_SHAR] = sh_il_shar,
	[SH_OP_SHLD] = sh_il_shld,
	[SH_OP_SHLL] = sh_il_shll,
	[SH_OP_SHLR] = sh_il_shlr,
	[SH_OP_SHLL2] = sh_il_shll2,
	[SH_OP_SHLR2] = sh_il_shlr2,
	[SH_OP_SHLL8] = sh_il_shll8,
	[SH_OP_SHLR8] = sh_il_shlr8,
	[SH_OP_SHLL16] = sh_il_shll16,
	[SH_OP_SHLR16] = sh_il_shlr16,
	[SH_OP_BF] = sh_il_bf,
	[SH_OP_BFS] = sh_il_bfs,
	[SH_OP_BT] = sh_il_bt,
	[SH_OP_BTS] = sh_il_bts,
	[SH_OP_BRA] = sh_il_bra,
	[SH_OP_BRAF] = sh_il_braf,
	[SH_OP_BSR] = sh_il_bsr,
	[SH_OP_BSRF] = sh_il_bsrf,
	[SH_OP_JMP] = sh_il_jmp,
	[SH_OP_JSR] = sh_il_jsr,
	[SH_OP_RTS] = sh_il_rts,
	[SH_OP_CLRMAC] = sh_il_clrmac,
	[SH_OP_CLRS] = sh_il_clrs,
	[SH_OP_CLRT] = sh_il_clrt,
	[SH_OP_LDC] = sh_il_ldc,
	[SH_OP_LDS] = sh_il_lds,
	[SH_OP_MOVCA] = sh_il_movca,
	[SH_OP_NOP] = sh_il_nop,
	[SH_OP_RTE] = sh_il_rte,
	[SH_OP_SETS] = sh_il_sets,
	[SH_OP_SETT] = sh_il_sett,
	[SH_OP_SLEEP] = sh_il_sleep,
	[SH_OP_STC] = sh_il_stc,
	[SH_OP_STS] = sh_il_sts,
	[SH_OP_UNIMPL] = sh_il_unimpl
};

/**
 * \brief Store the lifted IL for \p op in \p aop
 * This function also takes care of initializing and adding the privilege mode local variable if required
 *
 * \param analysis RzAnalysis instance
 * \param aop
 * \param pc Program counter
 * \param op
 * \param ctx Context variables for the current IL lifting
 * \return bool True if successful ; false otherwise
 */
RZ_IPI bool rz_sh_il_opcode(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisOp *aop, ut64 pc, RZ_BORROW RZ_NONNULL const SHOp *op, RZ_NULLABLE SHILContext *ctx) {
	rz_return_val_if_fail(analysis && aop && op, false);
	if (op->mnemonic >= SH_OP_SIZE) {
		RZ_LOG_ERROR("RzIL: SuperH: out of bounds op\n");
		return false;
	}

	sh_il_op create_op = sh_ops[op->mnemonic];
	RzILOpEffect *lifted = create_op(op, pc, analysis, ctx);

	// If the privilege was checked, then we need to set the local variable before the IL lifting
	if (ctx && ctx->privilege_check) {
		lifted = sh_apply_effects(lifted, sh_il_initialize_privilege(), NULL);
	}

	aop->il_op = lifted;
	return true;
}

/**
 * \brief Initialize new config for the SuperH IL
 *
 * \param analysis RzAnalysis instance
 * \return RzAnalysisILConfig* RzIL config for SuperH ISA
 */
RZ_IPI RzAnalysisILConfig *rz_sh_il_config(RZ_NONNULL RzAnalysis *analysis) {
	rz_return_val_if_fail(analysis, NULL);

	RzAnalysisILConfig *r = rz_analysis_il_config_new(SH_ADDR_SIZE, analysis->big_endian, SH_ADDR_SIZE);
	return r;
}
