// SPDX-FileCopyrightText: 2022 Florian Märkl <info@florianmaerkl.de>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_analysis.h>
#include <capstone/capstone.h>

#include "arm_cs.h"
#include "arm_accessors32.h"

#include <rz_il/rz_il_opbuilder_begin.h>

#include "arm_il_common.inc"

/**
 * All regs available as global IL variables
 */
static const char *regs_bound_32[] = {
	"lr", "sp",
	"qf", "vf", "cf", "zf", "nf", "gef",
	"fpscr",
	"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12",
	"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15",
	"d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31",
	NULL
};

/**
 * Variable name for a register given by cs
 */
static const char *reg_var_name(arm_reg reg) {
	switch (reg) {
	case ARM_REG_LR: return "lr";
	case ARM_REG_SP: return "sp";
	case ARM_REG_D0: return "d0";
	case ARM_REG_D1: return "d1";
	case ARM_REG_D2: return "d2";
	case ARM_REG_D3: return "d3";
	case ARM_REG_D4: return "d4";
	case ARM_REG_D5: return "d5";
	case ARM_REG_D6: return "d6";
	case ARM_REG_D7: return "d7";
	case ARM_REG_D8: return "d8";
	case ARM_REG_D9: return "d9";
	case ARM_REG_D10: return "d10";
	case ARM_REG_D11: return "d11";
	case ARM_REG_D12: return "d12";
	case ARM_REG_D13: return "d13";
	case ARM_REG_D14: return "d14";
	case ARM_REG_D15: return "d15";
	case ARM_REG_D16: return "d16";
	case ARM_REG_D17: return "d17";
	case ARM_REG_D18: return "d18";
	case ARM_REG_D19: return "d19";
	case ARM_REG_D20: return "d20";
	case ARM_REG_D21: return "d21";
	case ARM_REG_D22: return "d22";
	case ARM_REG_D23: return "d23";
	case ARM_REG_D24: return "d24";
	case ARM_REG_D25: return "d25";
	case ARM_REG_D26: return "d26";
	case ARM_REG_D27: return "d27";
	case ARM_REG_D28: return "d28";
	case ARM_REG_D29: return "d29";
	case ARM_REG_D30: return "d30";
	case ARM_REG_D31: return "d31";
	case ARM_REG_R0: return "r0";
	case ARM_REG_R1: return "r1";
	case ARM_REG_R2: return "r2";
	case ARM_REG_R3: return "r3";
	case ARM_REG_R4: return "r4";
	case ARM_REG_R5: return "r5";
	case ARM_REG_R6: return "r6";
	case ARM_REG_R7: return "r7";
	case ARM_REG_R8: return "r8";
	case ARM_REG_R9: return "r9";
	case ARM_REG_R10: return "r10";
	case ARM_REG_R11: return "r11";
	case ARM_REG_R12: return "r12";
	default: return NULL;
	}
}

static ut32 reg_bits(arm_reg reg) {
	if (reg >= ARM_REG_D0 && reg <= ARM_REG_D31) {
		return 64;
	}
	if (reg >= ARM_REG_Q0 && reg <= ARM_REG_Q15) {
		return 128;
	}
	return 32;
}

static bool is_vec_signed(arm_vectordata_type vec_type) {
	switch (vec_type) {
	case ARM_VECTORDATA_S8:
	case ARM_VECTORDATA_S16:
	case ARM_VECTORDATA_S32:
	case ARM_VECTORDATA_S64:
	case ARM_VECTORDATA_I8:
	case ARM_VECTORDATA_I16:
	case ARM_VECTORDATA_I32:
	case ARM_VECTORDATA_I64:
		return true;
	case ARM_VECTORDATA_U8:
	case ARM_VECTORDATA_U16:
	case ARM_VECTORDATA_U32:
	case ARM_VECTORDATA_U64:
		return false;
	default:
		rz_warn_if_reached();
		return 0;
	}
}

static bool is_core_reg(arm_reg reg) {
	if (reg >= ARM_REG_S0 && reg <= ARM_REG_S31) {
		return false;
	}
	if (reg >= ARM_REG_D0 && reg <= ARM_REG_D31) {
		return false;
	}
	if (reg >= ARM_REG_Q0 && reg <= ARM_REG_Q15) {
		return false;
	}
	return true;
}

/**
 * IL to read the given capstone reg
 */
static RzILOpBitVector *read_reg(ut64 pc, arm_reg reg) {
	if (reg == ARM_REG_PC) {
		return U32(pc);
	}
	if (reg >= ARM_REG_S0 && reg <= ARM_REG_S31) {
		ut32 idx = reg - ARM_REG_S0;
		RzILOpBitVector *var = VARG(reg_var_name(ARM_REG_D0 + idx / 2));
		return UNSIGNED(32, idx % 2 ? SHIFTR0(var, UN(7, 32)) : var);
	}
	if (reg >= ARM_REG_Q0 && reg <= ARM_REG_Q15) {
		ut32 low_dr_idx = (reg - ARM_REG_Q0) << 1;
		ut32 high_dr_idx = low_dr_idx + 1;
		RzILOpBitVector *low_var = VARG(reg_var_name(ARM_REG_D0 + low_dr_idx));
		RzILOpBitVector *high_var = VARG(reg_var_name(ARM_REG_D0 + high_dr_idx));
		return APPEND(high_var, low_var);
	}

	const char *var = reg_var_name(reg);
	return var ? VARG(var) : NULL;
}

/**
 * Return IL of bitvector store in register lane
 * The length of such bitv is `data_size`
 */
static RzILOpBitVector *read_reg_lane(arm_reg reg, ut32 lane, ut32 data_size) {
	if (is_core_reg(reg)) {
		rz_warn_if_reached();
		return NULL;
	}

	ut32 shift_dist = lane * data_size;
	RzILOpBitVector *reg_val = read_reg(0, reg);
	return UNSIGNED(data_size, SHIFTR0(reg_val, UN(8, shift_dist)));
}

/**
 * Return the data width of given data type
 * note: Those data_type which contains 2 type (F16.F64, F32.F16)
 * is out of the scope of this function
 */
static inline ut32 arm_data_width(arm_vectordata_type vec_type) {
	switch (vec_type) {
	case ARM_VECTORDATA_I32:
	case ARM_VECTORDATA_U32:
	case ARM_VECTORDATA_S32:
	case ARM_VECTORDATA_F32:
		return 32;
	case ARM_VECTORDATA_I8:
	case ARM_VECTORDATA_U8:
	case ARM_VECTORDATA_S8:
		return 8;
	case ARM_VECTORDATA_I16:
	case ARM_VECTORDATA_S16:
	case ARM_VECTORDATA_U16:
		return 16;
	case ARM_VECTORDATA_I64:
	case ARM_VECTORDATA_F64:
	case ARM_VECTORDATA_U64:
	case ARM_VECTORDATA_S64:
		return 64;
	case ARM_VECTORDATA_INVALID:
	default:
		rz_warn_if_reached();
		return 0;
	}
}

static inline RzFloatFormat dt2fmt(arm_vectordata_type type) {
	switch (type) {
#if CS_API_MAJOR > 4
	case ARM_VECTORDATA_F16:
		return RZ_FLOAT_IEEE754_BIN_16;
#endif
	case ARM_VECTORDATA_F32:
		return RZ_FLOAT_IEEE754_BIN_32;
	case ARM_VECTORDATA_F64:
		return RZ_FLOAT_IEEE754_BIN_64;
	default:
		return RZ_FLOAT_UNK;
	}
}

static inline RzFloatFormat cvtdt2fmt(arm_vectordata_type type, bool choose_src) {
	switch (type) {
	case ARM_VECTORDATA_F16F64:
		return choose_src ? RZ_FLOAT_IEEE754_BIN_64 : RZ_FLOAT_IEEE754_BIN_16;
	case ARM_VECTORDATA_F64F16:
		return choose_src ? RZ_FLOAT_IEEE754_BIN_16 : RZ_FLOAT_IEEE754_BIN_64;
	case ARM_VECTORDATA_F32F16:
		return choose_src ? RZ_FLOAT_IEEE754_BIN_16 : RZ_FLOAT_IEEE754_BIN_32;
	case ARM_VECTORDATA_F16F32:
		return choose_src ? RZ_FLOAT_IEEE754_BIN_32 : RZ_FLOAT_IEEE754_BIN_16;
	case ARM_VECTORDATA_F64F32:
		return choose_src ? RZ_FLOAT_IEEE754_BIN_32 : RZ_FLOAT_IEEE754_BIN_64;
	case ARM_VECTORDATA_F32F64:
		return choose_src ? RZ_FLOAT_IEEE754_BIN_64 : RZ_FLOAT_IEEE754_BIN_32;
	default:
		return RZ_FLOAT_UNK;
	}
}

#define PC(addr, is_thumb)      (addr + (is_thumb ? 4 : 8))
#define PCALIGN(addr, is_thumb) (PC(addr, is_thumb) & ~3ul)
#define REG_VAL(id)             read_reg(PC(insn->address, is_thumb), id)
#define REG(n)                  REG_VAL(REGID(n))
#define MEMBASE(x)              REG_VAL(insn->detail->arm.operands[x].mem.base)
#define DT_WIDTH(insn)          arm_data_width(insn->detail->arm.vector_data)
#define REG_WIDTH(n)            reg_bits(REGID(n))
#define VVEC_SIZE(insn)         insn->detail->arm.vector_size
#define VVEC_DT(insn)           insn->detail->arm.vector_data
#define FROM_FMT(dt)            cvtdt2fmt(dt, true)
#define TO_FMT(dt)              cvtdt2fmt(dt, false)
#if CS_API_MAJOR > 3
// clang-format off
#define NEON_LANE(n)            insn->detail->arm.operands[n].neon_lane
// clang-format on
#endif

/**
 * IL to write the given capstone reg
 */
static RzILOpEffect *write_reg(arm_reg reg, RZ_OWN RZ_NONNULL RzILOpBitVector *v) {
	rz_return_val_if_fail(v, NULL);
	if (reg >= ARM_REG_S0 && reg <= ARM_REG_S31) {
		ut32 idx = reg - ARM_REG_S0;
		arm_reg dreg = ARM_REG_D0 + idx / 2;
		RzILOpBitVector *masked = LOGAND(read_reg(0, dreg), U64(idx % 2 ? 0xffffffffull : 0xffffffff00000000ull));
		v = UNSIGNED(64, v);
		if (idx % 2) {
			v = SHIFTL0(v, UN(6, 32));
		}
		return SETG(reg_var_name(dreg), LOGOR(masked, v));
	}
	if (reg >= ARM_REG_Q0 && reg <= ARM_REG_Q15) {
		arm_reg low_reg = ARM_REG_D0 + ((reg - ARM_REG_Q0) << 1);
		arm_reg high_reg = low_reg + 1;
		RzILOpBitVector *low_val = UNSIGNED(64, v);
		RzILOpBitVector *high_val = UNSIGNED(64, SHIFTR0(DUP(v), UN(8, 64)));
		return SEQ2(
			SETG(reg_var_name(low_reg), low_val),
			SETG(reg_var_name(high_reg), high_val));
	}
	const char *var = reg_var_name(reg);
	if (!var) {
		rz_il_op_pure_free(v);
		return NULL;
	}
	return SETG(var, v);
}

/**
 * IL for arm condition
 * unconditional is returned as NULL (rather than true), for simpler code
 */
static RZ_NULLABLE RzILOpBool *cond(arm_cc c) {
	switch (c) {
	case ARM_CC_EQ:
		return VARG("zf");
	case ARM_CC_NE:
		return INV(VARG("zf"));
	case ARM_CC_HS:
		return VARG("cf");
	case ARM_CC_LO:
		return INV(VARG("cf"));
	case ARM_CC_MI:
		return VARG("nf");
	case ARM_CC_PL:
		return INV(VARG("nf"));
	case ARM_CC_VS:
		return VARG("vf");
	case ARM_CC_VC:
		return INV(VARG("vf"));
	case ARM_CC_HI:
		return AND(VARG("cf"), INV(VARG("zf")));
	case ARM_CC_LS:
		return OR(INV(VARG("cf")), VARG("zf"));
	case ARM_CC_GE:
		return INV(XOR(VARG("nf"), VARG("vf")));
	case ARM_CC_LT:
		return XOR(VARG("nf"), VARG("vf"));
	case ARM_CC_GT:
		return AND(INV(VARG("zf")), INV(XOR(VARG("nf"), VARG("vf"))));
	case ARM_CC_LE:
		return OR(VARG("zf"), XOR(VARG("nf"), VARG("vf")));
	case ARM_CC_AL:
	default:
		return NULL;
	}
}

static bool is_reg_shift(arm_shifter type) {
	switch (type) {
	case ARM_SFT_ASR_REG:
	case ARM_SFT_LSL_REG:
	case ARM_SFT_LSR_REG:
	case ARM_SFT_ROR_REG:
	case ARM_SFT_RRX_REG:
		return true;
	default:
		return false;
	}
}

static RZ_NULLABLE RzILOpBitVector *
shift(RzILOpBitVector *val, RZ_NULLABLE RzILOpBool **carry_out, arm_shifter type, RZ_OWN RzILOpBitVector *dist) {
	switch (type) {
	case ARM_SFT_ASR:
	case ARM_SFT_ASR_REG:
		if (!dist) {
			return val;
		}
		if (carry_out) {
			*carry_out = LSB(SHIFTRA(APPEND(DUP(val), ITE(VARG("cf"), UN(1, 1), UN(1, 0))), DUP(dist)));
		}
		return SHIFTRA(val, dist);
	case ARM_SFT_LSL:
	case ARM_SFT_LSL_REG:
		if (!dist) {
			return val;
		}
		if (carry_out) {
			*carry_out = MSB(SHIFTL0(APPEND(ITE(VARG("cf"), UN(1, 1), UN(1, 0)), DUP(val)), DUP(dist)));
		}
		return SHIFTL0(val, dist);
	case ARM_SFT_LSR:
	case ARM_SFT_LSR_REG:
		if (!dist) {
			return val;
		}
		if (carry_out) {
			*carry_out = LSB(SHIFTR0(APPEND(DUP(val), ITE(VARG("cf"), UN(1, 1), UN(1, 0))), DUP(dist)));
		}
		return SHIFTR0(val, dist);
	case ARM_SFT_ROR:
	case ARM_SFT_ROR_REG:
		if (!dist) {
			return val;
		}
		if (dist->code == RZ_IL_OP_CAST) {
			// this takes care of the mod 32 for register-based shifts which originally have 8 bits:
			dist->op.cast.length = 5;
		}
		if (carry_out) {
			*carry_out = ITE(IS_ZERO(DUP(dist)), VARG("cf"), MSB(SHIFTL0(DUP(val), NEG(DUP(dist)))));
		}
		return LOGOR(
			SHIFTR0(val, dist),
			SHIFTL0(DUP(val), NEG(DUP(dist))));
	case ARM_SFT_RRX:
	case ARM_SFT_RRX_REG:
		if (carry_out) {
			*carry_out = LSB(DUP(val));
		}
		rz_il_op_pure_free(dist);
		return SHIFTR(VARG("cf"), val, UN(5, 1));
	default:
		rz_il_op_pure_free(dist);
		return val;
	}
}

static RzILOpBitVector *arg_mem(RzILOpBitVector *base_plus_disp, cs_arm_op *op, RZ_NULLABLE RzILOpBool **carry_out) {
	if (op->mem.index != ARM_REG_INVALID && op->mem.index != ARM_REG_PC) {
		RzILOpBitVector *index = read_reg(0, op->mem.index);
		return ADD(base_plus_disp, shift(index, carry_out, op->shift.type, UN(5, op->shift.value)));
	}
	return base_plus_disp;
}

/**
 * Replicate given value to `dreg_width` length
 * Note the ownership of `val` will be transfered
 */
static RzILOpBitVector *replicated_val(ut32 val_width, ut32 dreg_width, RZ_OWN RzILOpBitVector *val) {
	ut32 repeat_times = dreg_width / val_width;
	if (dreg_width % val_width != 0) {
		rz_warn_if_reached();
		return NULL;
	}

	RzILOpBitVector *ext_val = UNSIGNED(dreg_width, val);
	RzILOpBitVector *rep_val = ext_val;
	for (int i = 0; i < repeat_times - 1; ++i) {
		rep_val = LOGOR(rep_val, SHIFTL0(DUP(ext_val), UN(8, val_width * i)));
	}

	return rep_val;
}

/**
 * For VFP/NEON instruction immediate value
 * <imm> in Arm ref manual: "A constant of the type specified by <dt>.
 * This constant is replicated enough times to fill the destination register.
 */
static RzILOpBitVector *repeated_imm(ut32 imm_width, ut32 dreg_width, ut32 imm) {
	ut64 final_imm = 0;
	ut32 repeat_times = dreg_width / imm_width;
	ut64 tmp = imm;

	if (dreg_width == 128) {
		// for <Qd> registers
		ut64 imm_low = tmp;
		ut64 imm_high = tmp;

		for (int i = 0; i < repeat_times / 2 - 1; ++i) {
			imm_low += tmp;
			imm_high += tmp;
			tmp <<= imm_width;
		}

		return (APPEND(UN(64, imm_high), UN(64, imm_low)));
	}

	// for <Dd> and <Sd>
	final_imm = tmp;
	for (int i = 0; i < repeat_times - 1; ++i) {
		final_imm += tmp;
		tmp <<= imm_width;
	}

	return UN(dreg_width, final_imm);
}

/**
 * Get immediate value operand
 * \param insn instruction
 * \param n operand number
 * \param carry_out carryout value, NULL if ignore
 * \return return immediate value as ut32
 */
static ut32 get_imm(cs_insn *insn, int n, RZ_NULLABLE RzILOpBool **carry_out) {
	if (carry_out) {
		*carry_out = NULL;
	}
	if (ISFPIMM(n)) {
		float fpimm = FPIMM(n);
		RzFloat *f = rz_float_new_from_f32(fpimm);
		ut32 hex_imm = rz_bv_to_ut32(f->s);
		rz_float_free(f);
		return hex_imm;
	}
	cs_arm_op *op = &insn->detail->arm.operands[n];
	ut32 imm = IMM(n);
	if (op->shift.type == ARM_SFT_INVALID && ISIMM(n + 1)) {
		// sometimes capstone encoded the shift like this, see also comment below
		ut32 ror = IMM(n + 1);
		imm = (imm >> ror) | (imm << (32 - ror));
	}

	if (carry_out) {
		// Some "movs"s leave c alone, some set it to the highest bit of the result.
		// Determining which one it is from capstone's info is tricky:
		// Arm defines that it is set when the imm12's rotate value is not 0.
		// This is the case when:
		// * capstone disassembles to something like "movs r0, 0, 2", giving us an explicit third operand
		// * capstone disassembles to something like "movs r0, 0x4000000" without the third operand,
		//   but we can see that the value is larger than 8 bits, so there must be a shift.
		if (ISIMM(n + 1) || imm > 0xff) {
			*carry_out = (imm & (1ul << 31)) ? IL_TRUE : IL_FALSE;
		}
	}

	return imm;
}

/**
 * IL to retrieve the value of the \p n -th arg of \p insn
 * \p carry_out filled with the carry value of NULL if it does not change
 */
static RzILOpBitVector *arg(cs_insn *insn, bool is_thumb, int n, RZ_NULLABLE RzILOpBool **carry_out) {
	if (carry_out) {
		*carry_out = NULL;
	}
	cs_arm_op *op = &insn->detail->arm.operands[n];
	switch (op->type) {
	case ARM_OP_REG: {
		RzILOpBitVector *r = REG(n);
		if (!r) {
			return NULL;
		}
		RzILOpBitVector *dist = NULL;
		if (is_reg_shift(op->shift.type)) {
			dist = read_reg(PC(insn->address, is_thumb), op->shift.value);
			if (dist) {
				dist = UNSIGNED(8, dist);
			}
		} else if (op->shift.type != ARM_SFT_INVALID) {
			dist = UN(5, op->shift.value);
		}
		return r ? shift(r, carry_out, op->shift.type, dist) : NULL;
	}
	case ARM_OP_IMM: {
		ut32 imm = get_imm(insn, n, carry_out);
		return U32(imm);
	}
	case ARM_OP_MEM: {
		RzILOpBitVector *addr = MEMBASE(n);
		int disp = MEMDISP(n);
		if (disp > 0) {
			addr = ADD(addr, U32(disp));
		} else if (disp < 0) {
			addr = SUB(addr, U32(-disp));
		}
		return arg_mem(addr, &insn->detail->arm.operands[n], carry_out);
	}
	default:
		break;
	}
	return NULL;
}

#define ARG_C(n, carry) arg(insn, is_thumb, n, carry)
#define ARG(n)          ARG_C(n, NULL)

/**
 * zf := v == 0
 * nf := msb v
 */
static RzILOpEffect *update_flags_zn(RzILOpBitVector *v) {
	return SEQ2(
		SETG("zf", IS_ZERO(v)),
		SETG("nf", MSB(DUP(v))));
}

/**
 * \p f set bits [24, 31] (nzcvq)
 * \p s set bits [16: 23] (ge)
 */
static RzILOpEffect *update_flags_from_cpsr(RzILOpBitVector *val, bool f, bool s) {
	RzILOpEffect *setf = f ? SEQ5(
					 SETG("nf", INV(IS_ZERO(LOGAND(val, U32(1ul << 31))))),
					 SETG("zf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 30))))),
					 SETG("cf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 29))))),
					 SETG("vf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 28))))),
					 SETG("qf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 27))))))
			       : NULL;
	RzILOpEffect *sets = s ? SETG("gef", UNSIGNED(4, SHIFTR0(setf ? DUP(val) : val, UN(5, 16)))) : NULL;
	return setf && sets ? SEQ2(sets, setf) : (setf ? setf : sets);
}

/**
 * Capstone: ARM_INS_MOV, ARM_INS_MOVW, ARM_INS_LSL, ARM_INS_LSR, ARM_INS_ASR, ARM_INS_RRX, ARM_INS_ROR, ARM_INS_MVN
 * ARM: mov, movs, movw, lsl, lsls, lsr, lsrs, asr, asrs, rrx, rrxs, ror, rors, mvn, mvns
 */
static RzILOpEffect *mov(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || (!ISIMM(1) && !ISREG(1)) || OPCOUNT() < 2) {
		return NULL;
	}
	size_t base_op = 1;
	// All of lsl, lsr, etc. are really just mov/movs, but capstone encodes them differently,
	// with the shift distance as extra (third) operand. But it doesn't do that always, sometimes the shift is also still
	// embedded in the second operand.
	arm_shifter shift_alias = ARM_SFT_INVALID;
	if (insn->detail->arm.operands[base_op].shift.type == ARM_SFT_INVALID) {
		base_op = OPCOUNT() < 3 ? 0 : 1;
		switch (insn->id) {
		case ARM_INS_LSL:
			shift_alias = ARM_SFT_LSL;
			break;
		case ARM_INS_LSR:
			shift_alias = ARM_SFT_LSR;
			break;
		case ARM_INS_ASR:
			shift_alias = ARM_SFT_ASR;
			break;
		case ARM_INS_RRX:
			shift_alias = ARM_SFT_RRX;
			base_op = 1;
			break;
		case ARM_INS_ROR:
			shift_alias = ARM_SFT_ROR;
			break;
		default:
			base_op = 1;
			break;
		}
	}
	bool update_flags = insn->detail->arm.update_flags;
	RzILOpBool *carry = NULL;
	RzILOpPure *val = ARG_C(base_op, update_flags && shift_alias == ARM_SFT_INVALID ? &carry : NULL);
	if (!val) {
		return NULL;
	}
	if (shift_alias != ARM_SFT_INVALID) {
		RzILOpPure *dist = NULL;
		if (shift_alias != ARM_SFT_RRX) {
			dist = ARG(base_op + 1);
			if (!dist) {
				rz_il_op_pure_free(val);
				return NULL;
			}
		}
		val = shift(val, update_flags ? &carry : NULL, shift_alias, dist ? UNSIGNED(8, dist) : NULL);
	}
	if (insn->id == ARM_INS_MVN) {
		val = LOGNOT(val);
	}
	if (REGID(0) == ARM_REG_PC) {
		if (update_flags) {
			// ALUExceptionReturn()
			goto err;
		} else {
			return JMP(val);
		}
	}
	RzILOpEffect *eff = write_reg(REGID(0), val);
	if (!eff) {
		goto err;
	}
	if (update_flags) {
		RzILOpEffect *zn = update_flags_zn(REG(0));
		return carry
			? SEQ4(SETL("cf_tmp", carry), eff, SETG("cf", VARL("cf_tmp")), zn) // rrxs still needs the old carry
			: SEQ2(eff, zn);
	}
	return eff;
err:
	rz_il_op_pure_free(carry);
	rz_il_op_pure_free(val);
	return NULL;
}

/**
 * Capstone: ARM_INS_MOVT
 * ARM: movt
 */
static RzILOpEffect *movt(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISIMM(1)) {
		return NULL;
	}
	RzILOpPure *regval = REG(0);
	if (!regval) {
		return NULL;
	}
	return write_reg(REGID(0), APPEND(U16(IMM(1)), UNSIGNED(16, regval)));
}

/**
 * Capstone: ARM_INS_ADR,
 *           if base is pc: ARM_INS_ADD, ARM_INS_ADDW, ARM_INS_SUB, ARM_INS_SUBW
 * ARM: adr, add pc, addw pc, sub pc, subw pc
 */
static RzILOpEffect *adr(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	st32 offset;
	switch (insn->id) {
	case ARM_INS_ADR:
		offset = IMM(1);
		break;
	case ARM_INS_ADD:
	case ARM_INS_ADDW:
		offset = IMM(2);
		break;
	case ARM_INS_SUB:
	case ARM_INS_SUBW:
		offset = -IMM(2);
		break;
	default:
		return NULL;
	}
	return write_reg(REGID(0), U32(PCALIGN(insn->address, is_thumb) + offset));
}

/**
 * Capstone: ARM_INS_ADD, ARM_INS_ADDW, ARM_INS_ADC, ARM_INS_SUB, ARM_INS_SUBW, ARM_INS_RSB, ARM_INS_RSC, ARM_INS_SBC
 * ARM: add, adds, adc, adcs, sub, subs, rsb, rsbs, rsc, rscs, sbc
 */
static RzILOpEffect *add_sub(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	if ((insn->id == ARM_INS_ADD || insn->id == ARM_INS_ADDW || insn->id == ARM_INS_SUB || insn->id == ARM_INS_SUBW) &&
		!insn->detail->arm.update_flags && OPCOUNT() == 3 && REGID(1) == ARM_REG_PC && ISIMM(2)) {
		// alias for adr
		return adr(insn, is_thumb);
	}
	bool is_sub =
		insn->id == ARM_INS_SUB || insn->id == ARM_INS_SUBW || insn->id == ARM_INS_RSB || insn->id == ARM_INS_RSC ||
		insn->id == ARM_INS_SBC;
	RzILOpBitVector *a = ARG(OPCOUNT() > 2 ? 1 : 0);
	RzILOpBitVector *b = ARG(OPCOUNT() > 2 ? 2 : 1);
	if (insn->id == ARM_INS_RSB || insn->id == ARM_INS_RSC) {
		RzILOpBitVector *tmp = b;
		b = a;
		a = tmp;
	}
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpBitVector *res = is_sub ? SUB(a, b) : ADD(a, b);
	bool with_carry = false;
	if (insn->id == ARM_INS_ADC) {
		res = ADD(res, ITE(VARG("cf"), U32(1), U32(0)));
		with_carry = true;
	} else if (insn->id == ARM_INS_RSC || insn->id == ARM_INS_SBC) {
		res = SUB(res, ITE(VARG("cf"), U32(0), U32(1)));
		with_carry = true;
	}
	if (REGID(0) == ARM_REG_PC) {
		if (insn->detail->arm.update_flags) {
			// ALUExceptionReturn()
			rz_il_op_pure_free(res);
			return NULL;
		} else {
			return JMP(res);
		}
	}
	RzILOpEffect *set = write_reg(REGID(0), res);
	bool update_flags = insn->detail->arm.update_flags;
	if (!strcmp(insn->mnemonic, "adc") || !strcmp(insn->mnemonic, "rsc") || !strcmp(insn->mnemonic, "sbc") ||
		!strcmp(insn->mnemonic, "adc.w") || !strcmp(insn->mnemonic, "sbc.w")) {
		// capstone is wrong about this, only the <...>s variants set flags
		update_flags = false;
	}
	if (update_flags) {
		return SEQ6(
			SETL("a", DUP(a)),
			SETL("b", DUP(b)),
			set,
			SETG("cf", (is_sub ? sub_carry : add_carry)(VARL("a"), VARL("b"), with_carry, 32)),
			SETG("vf", (is_sub ? sub_overflow : add_overflow)(VARL("a"), VARL("b"), REG(0))),
			update_flags_zn(REG(0)));
	}
	return set;
}

/**
 * Capstone: ARM_INS_MUL
 * ARM: mul, muls
 */
static RzILOpEffect *mul(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(OPCOUNT() > 2 ? 1 : 0);
	RzILOpBitVector *b = ARG(OPCOUNT() > 2 ? 2 : 1);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpEffect *eff = write_reg(REGID(0), MUL(a, b));
	if (!eff) {
		return NULL;
	}
	return insn->detail->arm.update_flags ? SEQ2(eff, update_flags_zn(REG(0))) : eff;
}

/**
 * Capstone: ARM_INS_LDR, ARM_INS_LDRB, ARM_INS_LDRH, ARM_INS_LDRT, ARM_INS_LDRBT, ARM_INS_LDRHT,
 *           ARM_INS_LDA, ARM_INS_LDAB, ARM_INS_LDAH, ARM_INS_LDAEX, ARM_INS_LDAEXB, ARM_INS_LDAEXH,
 *           ARM_INS_LDRD, ARM_INS_LDREX, ARM_INS_LDREXD,
 *           ARM_INS_LDRSB, ARM_INS_LDRSBT, ARM_INS_LDRSH, ARM_INS_LDRSHT
 * ARM: ldr, ldrb, ldrh, ldrt, ldrbt, ldrht, lda, ldab, ldah, ldaex, ldaexb, ldaexh, ldrd, ldrexd
 */
static RzILOpEffect *ldr(cs_insn *insn, bool is_thumb) {
	bool is_double = insn->id == ARM_INS_LDRD || insn->id == ARM_INS_LDREXD;
	size_t mem_idx = is_double ? 2 : 1;
	if (!ISREG(0) || !ISMEM(mem_idx) ||
		(is_double && (!ISREG(1) || REGID(0) == ARM_REG_PC || REGID(1) == ARM_REG_PC))) {
		return NULL;
	}
	RzILOpBitVector *addr;
	cs_arm_op *memop = &insn->detail->arm.operands[mem_idx];
	if (memop->mem.base == ARM_REG_PC) {
		// LDR (literal) is different in the sense that it aligns the pc value:
		addr = arg_mem(U32(PCALIGN(insn->address, is_thumb) + MEMDISP(mem_idx)), memop, NULL);
	} else {
		addr = ARG(mem_idx);
	}
	if (!addr) {
		return NULL;
	}
	bool writeback = insn->detail->arm.writeback;
	if (ISIMM(mem_idx + 1)) {
		// capstone incorrectly sets writeback to false for e.g. 0400b1e4 ldrt r0, [r1], 4
		writeback = true;
	}
	RzILOpEffect *writeback_eff = NULL;
	bool writeback_post = false;
	if (writeback) {
		arm_reg base = insn->detail->arm.operands[mem_idx].mem.base;
		if (ISIMM(mem_idx + 1)) {
			// "ldr r0, [r1], 4" is treated as an extra operand after the mem
			addr = insn->detail->arm.operands[mem_idx + 1].subtracted
				? SUB(addr, ARG(mem_idx + 1))
				: ADD(addr, ARG(mem_idx + 1));
			writeback_post = true;
		}
		writeback_eff = write_reg(base, addr);
		if (!writeback_eff) {
			// 'ldrb r0, [pc, 0x104]!' (0401ffe5) for example is unpredictable. write_reg will return NULL for pc.
			return NULL;
		}
		addr = MEMBASE(mem_idx);
	}
	RzILOpEffect *eff;
	if (is_double) {
		eff = SEQ2(
			write_reg(REGID(0), LOADW(32, addr)),
			write_reg(REGID(1), LOADW(32, ADD(DUP(addr), U32(4)))));
	} else {
		RzILOpBitVector *data;
		switch (insn->id) {
		case ARM_INS_LDRB:
		case ARM_INS_LDRBT:
		case ARM_INS_LDAB:
		case ARM_INS_LDAEXB:
			data = UNSIGNED(32, LOAD(addr));
			break;
		case ARM_INS_LDRH:
		case ARM_INS_LDRHT:
		case ARM_INS_LDAH:
		case ARM_INS_LDAEXH:
			data = UNSIGNED(32, LOADW(16, addr));
			break;
		case ARM_INS_LDRSB:
		case ARM_INS_LDRSBT:
			data = SIGNED(32, LOAD(addr));
			break;
		case ARM_INS_LDRSH:
		case ARM_INS_LDRSHT:
			data = SIGNED(32, LOADW(16, addr));
			break;
		default: // ARM_INS_LDR, ARM_INS_LDRT, ARM_INS_LDA, ARM_INS_LDAEX
			data = LOADW(32, addr);
			break;
		}
		if (REGID(0) == ARM_REG_PC) {
			if (writeback_post) {
				// can't have writeback after the jmp, so need to handle this special case with a local var
				return SEQ3(
					SETL("tgt", data),
					writeback_eff,
					JMP(VARL("tgt")));
			} else {
				eff = JMP(data);
			}
		} else {
			eff = write_reg(REGID(0), data);
		}
	}
	if (writeback_eff) {
		return writeback_post ? SEQ2(eff, writeback_eff) : SEQ2(writeback_eff, eff);
	}
	return eff;
}

/**
 * Capstone: ARM_INS_STR, ARM_INS_STRB, ARM_INS_STRH, ARM_INS_STRT, ARM_INS_STRBT, ARM_INS_STRHT,
 *           ARM_INS_STL, ARM_INS_STLB, ARM_INS_STLH, ARM_INS_STRD
 * ARM: str, strb, strh, strt, strbt, strht, stl, stlb, stlh, strd
 */
static RzILOpEffect *str(cs_insn *insn, bool is_thumb) {
	size_t mem_idx = insn->id == ARM_INS_STRD ? 2 : 1;
	if (!ISREG(0) || !ISMEM(mem_idx)) {
		return NULL;
	}
	RzILOpBitVector *addr = ARG(mem_idx);
	if (!addr) {
		return NULL;
	}
	bool writeback = insn->detail->arm.writeback;
	if (ISIMM(mem_idx + 1)) {
		// capstone incorrectly sets writeback to false for e.g. 04b0ade4 strt fp, [sp], 4
		writeback = true;
	}
	RzILOpEffect *writeback_eff = NULL;
	bool writeback_post = false;
	if (writeback) {
		arm_reg base = insn->detail->arm.operands[mem_idx].mem.base;
		if (ISIMM(mem_idx + 1)) {
			// "str r0, [r1], 4" is treated as an extra operand after the mem
			addr = insn->detail->arm.operands[mem_idx + 1].subtracted
				? SUB(addr, ARG(mem_idx + 1))
				: ADD(addr, ARG(mem_idx + 1));
			writeback_post = true;
		}
		writeback_eff = write_reg(base, addr);
		if (!writeback_eff) {
			return NULL;
		}
		addr = MEMBASE(mem_idx);
	}
	RzILOpBitVector *val = ARG(0);
	if (!val) {
		rz_il_op_pure_free(addr);
		return NULL;
	}
	RzILOpEffect *eff;
	switch (insn->id) {
	case ARM_INS_STRB:
	case ARM_INS_STRBT:
	case ARM_INS_STLB:
		eff = STORE(addr, UNSIGNED(8, val));
		break;
	case ARM_INS_STRH:
	case ARM_INS_STRHT:
	case ARM_INS_STLH:
		eff = STOREW(addr, UNSIGNED(16, val));
		break;
	case ARM_INS_STRD: {
		RzILOpBitVector *val2 = ARG(1);
		if (!val2) {
			rz_il_op_pure_free(val);
			rz_il_op_pure_free(addr);
			return NULL;
		}
		eff = SEQ2(
			STOREW(addr, val),
			STOREW(ADD(DUP(addr), U32(4)), val2));
		break;
	}
	default: // ARM_INS_STR, ARM_INS_STRT, ARM_INS_STL
		eff = STOREW(addr, val);
		break;
	}
	if (writeback_eff) {
		return writeback_post ? SEQ2(eff, writeback_eff) : SEQ2(writeback_eff, eff);
	}
	return eff;
}

/**
 * Capstone: ARM_INS_STREX, ARM_INS_STREXB, ARM_INS_STREXD, ARM_INS_STREXH,
 *           ARM_INS_STLEX, ARM_INS_STLEXB, ARM_INS_STLEXD, ARM_INS_STLEXH
 * ARM: strex, strexb, strexd, strexh, stlex, stlexb, stlexd, stlexh
 */
static RzILOpEffect *strex(cs_insn *insn, bool is_thumb) {
	size_t mem_idx = insn->id == ARM_INS_STREXD || insn->id == ARM_INS_STLEXD ? 3 : 2;
	if (!ISREG(0) || !ISMEM(mem_idx)) {
		return NULL;
	}
	RzILOpBitVector *addr = ARG(mem_idx);
	RzILOpBitVector *val = ARG(1);
	// always return success of exclusive access while it's not represented in IL:
	RzILOpEffect *ret_eff = write_reg(REGID(0), U32(0));
	if (!addr || !val || !ret_eff) {
	err:
		rz_il_op_pure_free(addr);
		rz_il_op_pure_free(val);
		rz_il_op_effect_free(ret_eff);
		return NULL;
	}
	RzILOpEffect *eff;
	switch (insn->id) {
	case ARM_INS_STREXB:
	case ARM_INS_STLEXB:
		eff = STORE(addr, UNSIGNED(8, val));
		break;
	case ARM_INS_STREXH:
	case ARM_INS_STLEXH:
		eff = STOREW(addr, UNSIGNED(16, val));
		break;
	case ARM_INS_STREXD:
	case ARM_INS_STLEXD: {
		RzILOpBitVector *val2 = ARG(2);
		if (!val2) {
			goto err;
		}
		eff = SEQ2(
			STOREW(addr, val),
			STOREW(ADD(DUP(addr), U32(4)), val2));
		break;
	}
	default: // ARM_INS_STREX, ARM_INS_STLEX
		eff = STOREW(addr, val);
		break;
	}
	return SEQ2(eff, ret_eff);
}

/**
 * Capstone: ARM_INS_AND, ARM_INS_ORR, ARM_INS_EOR, ARM_INS_BIC
 * ARM: and, ands, orr, orrs, orn, orns, eor, eors, bic, bics
 */
static RzILOpEffect *bitwise(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || OPCOUNT() < 2) {
		return NULL;
	}
	bool update_flags = insn->detail->arm.update_flags;
	RzILOpBitVector *a = ARG(OPCOUNT() - 2);
	RzILOpBool *carry = NULL;
	RzILOpBitVector *b = ARG_C(OPCOUNT() - 1, update_flags ? &carry : NULL);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_pure_free(carry);
		return NULL;
	}
	RzILOpBitVector *res;
	switch (insn->id) {
	case ARM_INS_AND:
		res = LOGAND(a, b);
		break;
	case ARM_INS_ORR:
		res = LOGOR(a, b);
		break;
	case ARM_INS_ORN:
		res = LOGOR(a, LOGNOT(b));
		break;
	case ARM_INS_EOR:
		res = LOGXOR(a, b);
		break;
	case ARM_INS_BIC:
		res = LOGAND(a, LOGNOT(b));
		break;
	default:
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_pure_free(carry);
		return NULL;
	}
	if (REGID(0) == ARM_REG_PC) {
		if (insn->detail->arm.update_flags) {
			// ALUExceptionReturn()
			rz_il_op_pure_free(res);
			rz_il_op_pure_free(carry);
			return NULL;
		} else {
			return JMP(res);
		}
	}
	RzILOpEffect *eff = write_reg(REGID(0), res);
	if (update_flags) {
		if (carry) {
			return SEQ3(
				eff,
				SETG("cf", carry),
				update_flags_zn(REG(0)));
		} else {
			return SEQ2(eff, update_flags_zn(REG(0)));
		}
	}
	return eff;
}

/**
 * Capstone: ARM_INS_TST, ARM_INS_TEQ
 * ARM: tst, teq
 */
static RzILOpEffect *tst(cs_insn *insn, bool is_thumb) {
	RzILOpBitVector *a = ARG(0);
	RzILOpBool *carry = NULL;
	RzILOpBitVector *b = ARG_C(1, &carry);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_pure_free(carry);
		return NULL;
	}
	RzILOpBitVector *res = insn->id == ARM_INS_TST ? LOGAND(a, b) : LOGOR(a, b);
	if (carry) {
		return SEQ2(
			SETG("cf", carry),
			update_flags_zn(res));
	} else {
		return update_flags_zn(res);
	}
}

/**
 * Capstone: ARM_INS_UXTB, ARM_INS_UXTH, ARM_INS_UXTAB, ARM_INS_UXTAH
 *           ARM_INS_SXTB, ARM_INS_SXTH, ARM_INS_SXTAB, ARM_INS_SXTAH
 * ARM: uxtb, uxth, uxtab, uxtah, sxtb, sxth, sxtab, sxtah
 */
static RzILOpEffect *uxt(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	bool is_add = insn->id == ARM_INS_UXTAB || insn->id == ARM_INS_UXTAH || insn->id == ARM_INS_SXTAB ||
		insn->id == ARM_INS_SXTAH;
	RzILOpBitVector *src = ARG(is_add ? 2 : 1);
	if (!src) {
		return NULL;
	}
	ut32 src_bits =
		insn->id == ARM_INS_UXTH || insn->id == ARM_INS_UXTAH || insn->id == ARM_INS_SXTH ||
			insn->id == ARM_INS_SXTAH
		? 16
		: 8;
	RzILOpBitVector *val = UNSIGNED(src_bits, src);
	val = insn->id == ARM_INS_SXTB || insn->id == ARM_INS_SXTH || insn->id == ARM_INS_SXTAB || insn->id == ARM_INS_SXTAH
		? SIGNED(32, val)
		: UNSIGNED(32, val);
	if (is_add) {
		RzILOpBitVector *b = ARG(1);
		if (!b) {
			rz_il_op_pure_free(val);
			return NULL;
		}
		val = ADD(b, val);
	}
	return write_reg(REGID(0), val);
}

/**
 * Capstone: ARM_INS_UXTB16, ARM_INS_UXTAB16, ARM_INS_SXTB16, ARM_INS_SXTAB16
 * ARM: uxtb16, uxtab16, stxb16, sxtab16
 */
static RzILOpEffect *uxt16(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	bool is_add = insn->id == ARM_INS_UXTAB16 || insn->id == ARM_INS_SXTAB16;
	RzILOpBitVector *src = ARG(is_add ? 2 : 1);
	if (!src) {
		return NULL;
	}
	RzILOpBitVector *l = UNSIGNED(8, VARLP("x"));
	RzILOpBitVector *h = UNSIGNED(8, SHIFTR0(VARLP("x"), UN(5, 16)));
	if (insn->id == ARM_INS_SXTB16 || insn->id == ARM_INS_SXTAB16) {
		l = SIGNED(16, l);
		h = SIGNED(16, h);
	} else {
		l = UNSIGNED(16, l);
		h = UNSIGNED(16, h);
	}
	if (is_add) {
		RzILOpBitVector *b = ARG(1);
		if (!b) {
			rz_il_op_pure_free(src);
			rz_il_op_pure_free(l);
			rz_il_op_pure_free(h);
			return NULL;
		}
		l = ADD(UNSIGNED(16, b), l);
		h = ADD(UNSIGNED(16, SHIFTR0(DUP(b), UN(5, 16))), h);
	}
	return write_reg(REGID(0), LET("x", src, APPEND(h, l)));
}

/**
 * Capstone: ARM_INS_CMP, ARM_INS_CMN
 * ARM: cmp, cmn
 */
static RzILOpEffect *cmp(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	bool is_sub = insn->id == ARM_INS_CMP;
	RzILOpBitVector *a = ARG(0);
	RzILOpBitVector *b = ARG(1);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	return SEQ6(
		SETL("a", a),
		SETL("b", b),
		SETL("res", is_sub ? SUB(VARL("a"), VARL("b")) : ADD(VARL("a"), VARL("b"))),
		SETG("cf", (is_sub ? sub_carry : add_carry)(VARL("a"), VARL("b"), false, 32)),
		SETG("vf", (is_sub ? sub_overflow : add_overflow)(VARL("a"), VARL("b"), VARL("res"))),
		update_flags_zn(VARL("res")));
}

/**
 * Capstone: ARM_INS_STM, ARM_INS_STMDA, ARM_INS_STMDB, ARM_INS_PUSH, ARM_INS_STMIB,
 *           ARM_INS_VSTMIA, ARM_INS_VSTMDB, ARM_INS_VPUSH
 * ARM: stm (stmia, stmea), stmdb (stmfb), push
 */
static RzILOpEffect *stm(cs_insn *insn, bool is_thumb) {
	size_t op_first;
	arm_reg ptr_reg;
	bool writeback;
	if (insn->id == ARM_INS_PUSH || insn->id == ARM_INS_VPUSH) {
		op_first = 0;
		ptr_reg = ARM_REG_SP;
		writeback = true;
	} else { // ARM_INS_STMDB.*
		if (!ISREG(0)) {
			return NULL;
		}
		op_first = 1;
		ptr_reg = REGID(0);
		writeback = insn->detail->arm.writeback;
	}
	size_t op_count = OPCOUNT() - op_first;
	if (!op_count) {
		return NOP();
	}
	RzILOpBitVector *ptr = REG_VAL(ptr_reg);
	if (!ptr) {
		return NULL;
	}
	bool decrement = insn->id == ARM_INS_STMDA || insn->id == ARM_INS_STMDB || insn->id == ARM_INS_PUSH ||
		insn->id == ARM_INS_VSTMDB || insn->id == ARM_INS_VPUSH;
	bool before = insn->id == ARM_INS_STMDB || insn->id == ARM_INS_PUSH || insn->id == ARM_INS_VSTMDB ||
		insn->id == ARM_INS_STMIB || insn->id == ARM_INS_VPUSH;
	ut32 regsize = reg_bits(REGID(op_first)) / 8;
	RzILOpEffect *eff = NULL;
	// build up in reverse order so the result recurses in the second arg of seq (for tail-call optimization)
	if (writeback) {
		eff = write_reg(ptr_reg,
			decrement
				? SUB(DUP(ptr), U32(op_count * regsize))
				: ADD(DUP(ptr), U32(op_count * regsize)));
	}
	for (size_t i = 0; i < op_count; i++) {
		size_t idx = op_first + (op_count - 1 - i);
		RzILOpPure *val;
		if (!ISREG(idx) || !(val = REG(idx))) {
			rz_il_op_pure_free(ptr);
			rz_il_op_effect_free(eff);
			return NULL;
		}
		RzILOpEffect *store = STOREW(
			decrement
				? SUB(DUP(ptr), U32((i + (before ? 1 : 0)) * regsize))
				: ADD(DUP(ptr), U32((op_count - i - (before ? 0 : 1)) * regsize)),
			val);
		eff = eff ? SEQ2(store, eff) : store;
	}
	rz_il_op_pure_free(ptr);
	return eff;
}

/**
 * Capstone: ARM_INS_LDM, ARM_INS_POP, ARM_INS_LDMDA, ARM_INS_LDMDB, ARM_INS_LDMIB,
 *           ARM_INS_VLDMIA, ARM_INS_VLDMDB, ARM_INS_VPOP
 * ARM: ldm (ldmia, ldmfd), pop, ldmda (ldmfa), ldmdb (ldmea), ldmib (ldmed)
 */
static RzILOpEffect *ldm(cs_insn *insn, bool is_thumb) {
	size_t op_first;
	arm_reg ptr_reg;
	bool writeback;
	if (insn->id == ARM_INS_POP || insn->id == ARM_INS_VPOP) {
		op_first = 0;
		ptr_reg = ARM_REG_SP;
		writeback = true;
	} else { // ARM_INS_LDM.*
		if (!ISREG(0)) {
			return NULL;
		}
		op_first = 1;
		ptr_reg = REGID(0);
		writeback = insn->detail->arm.writeback;
	}
	size_t op_count = OPCOUNT() - op_first;
	if (!op_count) {
		return NOP();
	}
	RzILOpBitVector *ptr_initial = REG_VAL(ptr_reg);
	if (!ptr_initial) {
		return NULL;
	}
	RzILOpEffect *eff = NULL;
	// build up in reverse order so the result recurses in the second arg of seq (for tail-call optimization)
	for (size_t i = 0; i < op_count; i++) {
		size_t idx = op_first + (op_count - 1 - i);
		if (ISREG(idx) && REGID(idx) == ARM_REG_PC) {
			// jmp goes last
			eff = JMP(VARL("tgt"));
		}
	}
	bool decrement = insn->id == ARM_INS_LDMDA || insn->id == ARM_INS_LDMDB || insn->id == ARM_INS_VLDMDB;
	bool before = insn->id == ARM_INS_LDMDB || insn->id == ARM_INS_LDMIB || insn->id == ARM_INS_VLDMIA;
	ut32 regsize = reg_bits(REGID(op_first)) / 8;
	if (writeback) {
		RzILOpEffect *wb = write_reg(ptr_reg,
			decrement
				? SUB(VARL("base"), U32(op_count * regsize))
				: ADD(VARL("base"), U32(op_count * regsize)));
		eff = eff ? SEQ2(wb, eff) : wb;
	}
	for (size_t i = 0; i < op_count; i++) {
		size_t idx = op_first + (op_count - 1 - i);
		if (!ISREG(idx)) {
			rz_il_op_pure_free(ptr_initial);
			rz_il_op_effect_free(eff);
			return NULL;
		}
		RzILOpPure *val = LOADW(regsize * 8,
			decrement
				? SUB(VARL("base"), U32((i + (before ? 1 : 0)) * regsize))
				: ADD(VARL("base"), U32((op_count - i - (before ? 0 : 1)) * regsize)));
		RzILOpEffect *load;
		if (REGID(idx) == ARM_REG_PC) {
			load = SETL("tgt", val);
		} else {
			load = write_reg(REGID(idx), val);
		}
		eff = eff ? SEQ2(load, eff) : load;
	}
	return SEQ2(SETL("base", ptr_initial), eff);
}

/**
 * Capstone: ARM_INS_BL, ARM_INS_BLX
 * ARM: bl, blx
 */
static RzILOpEffect *bl(cs_insn *insn, bool is_thumb) {
	RzILOpBitVector *tgt = ARG(0);
	if (!tgt) {
		return NULL;
	}
	return SEQ2(
		SETG("lr", U32(((insn->address + insn->size) & ~1ul) | (is_thumb ? 1 : 0))),
		JMP(tgt));
}

/**
 * Capstone: ARM_INS_CLZ
 * ARM: clz
 */
static RzILOpEffect *clz(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *v = ARG(1);
	if (!v) {
		return NULL;
	}
	return SEQ4(
		SETL("v", v),
		SETL("i", U32(0x20)),
		REPEAT(INV(IS_ZERO(VARL("v"))),
			SEQ2(
				SETL("v", SHIFTR0(VARL("v"), UN(5, 1))),
				SETL("i", SUB(VARL("i"), U32(1))))),
		write_reg(REGID(0), VARL("i")));
}

/**
 * Capstone: ARM_INS_SVC
 * ARM: svc
 */
static RzILOpEffect *svc(cs_insn *insn, bool is_thumb) {
	return GOTO("svc");
}

static void label_svc(RzILVM *vm, RzILOpEffect *op) {
	// stub, nothing to do here
}

#if CS_API_MAJOR > 3

/**
 * Capstone: ARM_INS_HVC
 * ARM: hvc
 */
static RzILOpEffect *hvc(cs_insn *insn, bool is_thumb) {
	return GOTO("hvc");
}

#endif

static void label_hvc(RzILVM *vm, RzILOpEffect *op) {
	// stub, nothing to do here
}

/**
 * Capstone: ARM_INS_BFC
 * ARM: bfc
 */
static RzILOpEffect *bfc(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISIMM(1) || !ISIMM(2)) {
		return NULL;
	}
	RzILOpBitVector *val = REG(0);
	if (!val) {
		return NULL;
	}
	return write_reg(REGID(0), LOGAND(val, U32(~(rz_num_bitmask(IMM(2)) << IMM(1)))));
}

/**
 * Capstone: ARM_INS_BFI
 * ARM: bfi
 */
static RzILOpEffect *bfi(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISIMM(2) || !ISIMM(3)) {
		return NULL;
	}
	RzILOpBitVector *dval = REG(0);
	RzILOpBitVector *nval = ARG(1);
	if (!dval || !nval) {
		rz_il_op_pure_free(dval);
		rz_il_op_pure_free(nval);
		return NULL;
	}
	ut32 lsb = IMM(2);
	ut32 mask = rz_num_bitmask(IMM(3));
	return write_reg(REGID(0),
		LOGOR(
			LOGAND(dval, U32(~(mask << lsb))),
			SHIFTL0(LOGAND(nval, U32(mask)), UN(5, lsb))));
}

/**
 * Capstone: ARM_INS_CBZ, ARM_INS_CBNZ
 * ARM: cbz, cbnz
 */
static RzILOpEffect *cbz(cs_insn *insn, bool is_thumb) {
	RzILOpBitVector *val = ARG(0);
	RzILOpBitVector *dst = ARG(1);
	if (!val || !dst) {
		rz_il_op_pure_free(val);
		rz_il_op_pure_free(dst);
		return NULL;
	}
	RzILOpBool *cond = IS_ZERO(val);
	if (insn->id == ARM_INS_CBNZ) {
		cond = INV(cond);
	}
	return BRANCH(cond, JMP(dst), NULL);
}

/**
 * Capstone: ARM_INS_MLA, ARM_INS_MLS
 * ARM: mla, mlas, mls
 */
static RzILOpEffect *mla(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *op0 = ARG(1);
	RzILOpBitVector *op1 = ARG(2);
	RzILOpBitVector *addend = ARG(3);
	if (!op0 || !op1 || !addend) {
		rz_il_op_pure_free(op0);
		rz_il_op_pure_free(op1);
		rz_il_op_pure_free(addend);
		return NULL;
	}
	RzILOpBitVector *val = insn->id == ARM_INS_MLS
		? SUB(addend, MUL(op0, op1))
		: ADD(MUL(op0, op1), addend);
	RzILOpEffect *eff = write_reg(REGID(0), val);
	if (!eff) {
		return NULL;
	}
	return insn->detail->arm.update_flags
		? SEQ2(eff, update_flags_zn(REG(0)))
		: eff;
}

/**
 * Capstone: ARM_INS_MRS
 * ARM: mrs
 */
static RzILOpEffect *mrs(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISREG(1)) {
		return NULL;
	}
	if (REGID(1) != ARM_REG_CPSR && REGID(1) != ARM_REG_SPSR && REGID(1) != ARM_REG_APSR) {
		// only these regs supported
		return NULL;
	}
	// There are more bits in ARM, but this is all we have:
	return write_reg(REGID(0),
		LOGOR(ITE(VARG("nf"), U32(1ul << 31), U32(0)),
			LOGOR(ITE(VARG("zf"), U32(1ul << 30), U32(0)),
				LOGOR(ITE(VARG("cf"), U32(1ul << 29), U32(0)),
					LOGOR(ITE(VARG("vf"), U32(1ul << 28), U32(0)),
						LOGOR(ITE(VARG("qf"), U32(1ul << 27), U32(0)),
							SHIFTL0(UNSIGNED(32, VARG("gef")), UN(5, 16))))))));
}

/**
 * Capstone: ARM_INS_MSR
 * ARM: msr
 */
static RzILOpEffect *msr(cs_insn *insn, bool is_thumb) {
	cs_arm_op *dst = &insn->detail->arm.operands[0];
	if (dst->type != ARM_OP_SYSREG) {
		return NULL;
	}
	// check if the reg+mask contains any of the flags we have:
	bool update_f = false;
	bool update_s = false;
	switch (dst->reg) {
	case ARM_SYSREG_APSR_NZCVQ:
		update_f = true;
		break;
	case ARM_SYSREG_APSR_G:
		update_s = true;
		break;
	case ARM_SYSREG_APSR_NZCVQG:
		update_f = true;
		update_s = true;
		break;
	default:
		update_f = (dst->reg & ARM_SYSREG_CPSR_F) || (dst->reg & ARM_SYSREG_SPSR_F);
		update_s = (dst->reg & ARM_SYSREG_CPSR_S) || (dst->reg & ARM_SYSREG_SPSR_S);
		break;
	}
	if (!update_f && !update_s) {
		// no flags we know
		return NULL;
	}
	RzILOpBitVector *val = ARG(1);
	if (!val) {
		return NULL;
	}
	return update_flags_from_cpsr(val, update_f, update_s);
}

/**
 * Capstone: ARM_INS_PKHBT, ARM_INS_PKHTB
 * ARM: pkhbt, pkhtb
 */
static RzILOpEffect *pkhbt(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *hv = ARG(1);
	RzILOpBitVector *lv = ARG(2);
	if (!hv || !lv) {
		rz_il_op_pure_free(hv);
		rz_il_op_pure_free(lv);
		return NULL;
	}
	hv = UNSIGNED(16, SHIFTR0(hv, UN(5, 16)));
	lv = UNSIGNED(16, lv);
	bool tbform = insn->id == ARM_INS_PKHTB;
	return write_reg(REGID(0), tbform ? APPEND(hv, lv) : APPEND(lv, hv));
}

/**
 * Saturate the signed value \p val into the local variable \p dst
 * \p bits how many bits the result should have
 * \p val value to saturate, of \p ext_bits bits
 * \p set_q whether to set the q flag on saturation
 * \p min minimal value of the range to saturate into
 * \p min maximal value of the range to saturate into
 */
static RzILOpEffect *
saturate_signed_to_range(const char *dst, ut32 bits, RzILOpBitVector *val, ut32 ext_bits, bool set_q, st64 min,
	st64 max) {
	return SEQ2(
		SETL("er", val),
		BRANCH(SGT(VARL("er"), SN(ext_bits, max)),
			set_q ? SEQ2(SETL(dst, SN(bits, max)), SETG("qf", IL_TRUE)) : SETL(dst, SN(bits, max)),
			BRANCH(SLT(VARL("er"), SN(ext_bits, min)),
				set_q ? SEQ2(SETL(dst, SN(bits, min)), SETG("qf", IL_TRUE)) : SETL(dst, SN(bits, min)),
				SETL(dst, UNSIGNED(bits, VARL("er"))))));
}

static RzILOpEffect *
saturate_signed(bool to_signed, const char *dst, ut32 bits, RzILOpBitVector *val, ut32 ext_bits, bool set_q) {
	st64 max = to_signed ? (1ull << (bits - 1)) - 1 : (1ull << bits) - 1;
	st64 min = to_signed ? -max - 1 : 0;
	return saturate_signed_to_range(dst, bits, val, ext_bits, set_q, min, max);
}

/**
 * Saturate the unsigned value \p val into the local variable \p dst
 * \p is_sub whether the value came from addition or subtraction, to differenciate between underflow and overflow
 * \p bits how many bits the result should have
 * \p val value to saturate, of \p ext_bits bits
 * \p set_q whether to set the q flag on saturation
 */
static RzILOpEffect *
saturate_unsigned(bool is_sub, const char *dst, ut32 bits, RzILOpBitVector *val, ut32 ext_bits, bool set_q) {
	ut64 max = (1ull << bits) - 1;
	ut64 min = 0;
	return SEQ2(
		SETL("er", val),
		BRANCH(UGT(VARL("er"), UN(ext_bits, max)),
			set_q ? SEQ2(SETL(dst, UN(bits, max)), SETG("qf", IL_TRUE)) : SETL(dst, UN(bits, is_sub ? min : max)),
			SETL(dst, UNSIGNED(bits, VARL("er")))));
}

static RzILOpEffect *
saturate(bool sign, bool is_sub, const char *dst, ut32 bits, RzILOpBitVector *val, ut32 ext_bits, bool set_q) {
	return sign
		? saturate_signed(true, dst, bits, val, ext_bits, set_q)
		: saturate_unsigned(is_sub, dst, bits, val, ext_bits, set_q);
}

/**
 * Capstone: ARM_INS_SSAT, ARM_INS_USAT
 * ARM: ssat
 */
static RzILOpEffect *ssat(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISIMM(1)) {
		return NULL;
	}
	RzILOpPure *src = ARG(2);
	bool is_signed = insn->id == ARM_INS_SSAT;
	RzILOpEffect *eff = write_reg(REGID(0), is_signed ? SIGNED(32, VARL("r")) : UNSIGNED(32, VARL("r")));
	if (!src || !eff) {
		rz_il_op_pure_free(src);
		rz_il_op_effect_free(eff);
		return NULL;
	}
	return SEQ2(
		saturate_signed(is_signed, "r", IMM(1), src, 32, true),
		eff);
}

/**
 * Capstone: ARM_INS_SSAT16, ARM_INS_USAT16
 * ARM: ssat16
 */
static RzILOpEffect *ssat16(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISIMM(1)) {
		return NULL;
	}
	RzILOpPure *src = ARG(2);
	bool is_signed = insn->id == ARM_INS_SSAT16;
	RzILOpEffect *eff = write_reg(REGID(0),
		APPEND(
			is_signed ? SIGNED(16, VARL("rh")) : UNSIGNED(16, VARL("rh")),
			is_signed ? SIGNED(16, VARL("rl")) : UNSIGNED(16, VARL("rl"))));
	if (!src || !eff) {
		rz_il_op_pure_free(src);
		rz_il_op_effect_free(eff);
		return NULL;
	}
	return SEQ3(
		saturate_signed(is_signed, "rl", IMM(1), UNSIGNED(16, src), 16, true),
		saturate_signed(is_signed, "rh", IMM(1), UNSIGNED(16, SHIFTR0(DUP(src), UN(5, 16))), 16, true),
		eff);
}

/**
 * Capstone: ARM_INS_QADD, ARM_INS_QSUB, ARM_INS_QDADD, ARM_INS_QDSUB
 * ARM: qadd, qsub, qdadd, qdsub
 */
static RzILOpEffect *qadd(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpEffect *eff = write_reg(REGID(0), VARL("r"));
	if (!eff) {
		return NULL;
	}
	RzILOpEffect *dbl = NULL;
	if (insn->id == ARM_INS_QDADD || insn->id == ARM_INS_QDSUB) {
		b = SIGNED(33, b);
		dbl = saturate_signed(true, "dbl", 32, ADD(b, DUP(b)), 33, true);
		b = VARL("dbl");
	}
	eff = SEQ2(
		saturate_signed(true, "r", 32,
			(insn->id == ARM_INS_QSUB || insn->id == ARM_INS_QDSUB)
				? SUB(SIGNED(33, a), SIGNED(33, b))
				: ADD(SIGNED(33, a), SIGNED(33, b)),
			33, true),
		eff);
	return dbl ? SEQ2(dbl, eff) : eff;
}

/**
 * Capstone: ARM_INS_QADD16, ARM_INS_QSUB16, ARM_INS_QASX, ARM_INS_QSAX,
 *           ARM_INS_UQADD16, ARM_INS_UQSUB16, ARM_INS_UQASX, ARM_INS_UQSAX
 * ARM: qadd16, qsub16, qasx, qsax, uqadd16, uqasx, uqsax
 */
static RzILOpEffect *qadd16(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpEffect *eff = write_reg(REGID(0), APPEND(VARL("rh"), VARL("rl")));
	if (!eff) {
		return NULL;
	}
	bool is_signed = insn->id == ARM_INS_QADD16 || insn->id == ARM_INS_QSUB16 || insn->id == ARM_INS_QASX ||
		insn->id == ARM_INS_QSAX;
	RzILOpBitVector *(*cast)(ut32 length, RzILOpBitVector *val) = is_signed ? rz_il_op_new_signed : rz_il_op_new_unsigned;
	RzILOpBitVector *al = cast(17, UNSIGNED(16, a));
	RzILOpBitVector *ah = cast(17, UNSIGNED(16, SHIFTR0(DUP(a), UN(5, 16))));
	RzILOpBitVector *bl = cast(17, UNSIGNED(16, b));
	RzILOpBitVector *bh = cast(17, UNSIGNED(16, SHIFTR0(DUP(b), UN(5, 16))));
	bool l_sub, h_sub;
	RzILOpBitVector *l, *h;
	switch (insn->id) {
	case ARM_INS_QSUB16:
	case ARM_INS_UQSUB16:
		l_sub = true;
		h_sub = true;
		l = SUB(al, bl);
		h = SUB(ah, bh);
		break;
	case ARM_INS_QASX:
	case ARM_INS_UQASX:
		l_sub = true;
		h_sub = false;
		l = SUB(al, bh);
		h = ADD(ah, bl);
		break;
	case ARM_INS_QSAX:
	case ARM_INS_UQSAX:
		l_sub = false;
		h_sub = true;
		l = ADD(al, bh);
		h = SUB(ah, bl);
		break;
	default: // ARM_INS_QADD16, ARM_INS_UQADD16
		l_sub = false;
		h_sub = false;
		l = ADD(al, bl);
		h = ADD(ah, bh);
		break;
	}
	return SEQ3(saturate(is_signed, l_sub, "rl", 16, l, 17, false), saturate(is_signed, h_sub, "rh", 16, h, 17, false),
		eff);
}

/**
 * Capstone: ARM_INS_QADD8, ARM_INS_QSUB8, ARM_INS_UQADD8, ARM_INS_UQSUB8
 * ARM: qadd8, qsub8, uqadd8, uqsub8
 */
static RzILOpEffect *qadd8(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpEffect *eff = write_reg(REGID(0), APPEND(APPEND(VARL("rb3"), VARL("rb2")), APPEND(VARL("rb1"), VARL("rb0"))));
	if (!eff) {
		return NULL;
	}
	bool is_signed = insn->id == ARM_INS_QADD8 || insn->id == ARM_INS_QSUB8;
	bool is_sub = insn->id == ARM_INS_QSUB8 || insn->id == ARM_INS_UQSUB8;
	RzILOpBitVector *(*cast)(ut32 length, RzILOpBitVector *val) = is_signed ? rz_il_op_new_signed : rz_il_op_new_unsigned;
	return SEQ5(
		saturate(is_signed, is_sub, "rb0", 8,
			is_sub
				? SUB(cast(9, UNSIGNED(8, a)), cast(9, UNSIGNED(8, b)))
				: ADD(cast(9, UNSIGNED(8, a)), cast(9, UNSIGNED(8, b))),
			9, false),
		saturate(is_signed, is_sub, "rb1", 8,
			is_sub
				? SUB(cast(9, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 8)))),
					  cast(9, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 8)))))
				: ADD(cast(9, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 8)))),
					  cast(9, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 8))))),
			9, false),
		saturate(is_signed, is_sub, "rb2", 8,
			is_sub
				? SUB(cast(9, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 16)))),
					  cast(9, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 16)))))
				: ADD(cast(9, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 16)))),
					  cast(9, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 16))))),
			9, false),
		saturate(is_signed, is_sub, "rb3", 8,
			is_sub
				? SUB(cast(9, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 24)))),
					  cast(9, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 24)))))
				: ADD(cast(9, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 24)))),
					  cast(9, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 24))))),
			9, false),
		eff);
}

/**
 * Capstone: ARM_INS_RBIT
 * ARM: rbit
 */
static RzILOpEffect *rbit(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *v = ARG(1);
	if (!v) {
		return NULL;
	}
	RzILOpEffect *eff = write_reg(REGID(0), VARL("r"));
	if (!eff) {
		return NULL;
	}
	return SEQ5(
		SETL("v", v),
		SETL("i", U32(0x20)),
		SETL("r", U32(0x0)),
		REPEAT(INV(IS_ZERO(VARL("v"))),
			SEQ3(
				SETL("i", SUB(VARL("i"), U32(1))),
				SETL("r", LOGOR(VARL("r"), ITE(LSB(VARL("v")), SHIFTL0(U32(1), VARL("i")), U32(0)))),
				SETL("v", SHIFTR0(VARL("v"), UN(5, 1))))),
		eff);
}

/**
 * Capstone: ARM_INS_REV, ARM_INS_REV16
 * ARM: rev, rev16
 */
static RzILOpEffect *rev(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *v = ARG(1);
	if (!v) {
		return NULL;
	}
	RzILOpBitVector *l = APPEND(UNSIGNED(8, v), UNSIGNED(8, SHIFTR0(DUP(v), UN(5, 8))));
	RzILOpBitVector *h = APPEND(UNSIGNED(8, SHIFTR0(DUP(v), UN(5, 16))), UNSIGNED(8, SHIFTR0(DUP(v), UN(5, 24))));
	return write_reg(REGID(0),
		insn->id == ARM_INS_REV
			? APPEND(l, h)
			: APPEND(h, l));
}

/**
 * Capstone: ARM_INS_REVSH
 * ARM: revsh
 */
static RzILOpEffect *revsh(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *v = ARG(1);
	if (!v) {
		return NULL;
	}
	return write_reg(REGID(0),
		LET("r", APPEND(UNSIGNED(8, v), UNSIGNED(8, SHIFTR0(DUP(v), UN(5, 8)))), SIGNED(32, VARLP("r"))));
}

/**
 * Capstone: ARM_INS_RFEDA, ARM_INS_RFEDB, ARM_INS_RFEIA, ARM_INS_RFEIB
 * ARM: rfeda, rfedb, rfaia, rfeib
 */
static RzILOpEffect *rfe(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *base = REG(0);
	if (!base) {
		return NULL;
	}
	RzILOpEffect *wb = NULL;
	bool wordhigher = insn->id == ARM_INS_RFEDA || insn->id == ARM_INS_RFEIB;
	bool increment = insn->id == ARM_INS_RFEIA || insn->id == ARM_INS_RFEIB;
	if (insn->detail->arm.writeback) {
		wb = write_reg(REGID(0),
			increment ? ADD(DUP(base), U32(8)) : SUB(DUP(base), U32(8)));
		if (!wb) {
			rz_il_op_pure_free(base);
			return NULL;
		}
	}
	RzILOpBitVector *addr = increment ? base : SUB(base, U32(8));
	if (wordhigher) {
		addr = ADD(addr, U32(4));
	}
	return SEQ5(
		SETL("addr", addr),
		SETL("tgt", LOADW(32, VARL("addr"))),
		SETL("spsr", LOADW(32, ADD(VARL("addr"), U32(4)))),
		update_flags_from_cpsr(VARL("spsr"), true, true),
		wb ? SEQ2(wb, JMP(VARL("tgt"))) : JMP(VARL("tgt")));
}

/**
 * Capstone: ARM_INS_SADD16, ARM_INS_SHADD16, ARM_INS_SASX, ARM_INS_SSAX, ARM_INS_SHASX, ARM_INS_SHSAX,
 *           ARM_INS_SSUB16, ARM_INS_SHSUB16
 *           ARM_INS_UADD16, ARM_INS_UHADD16, ARM_INS_UASX, ARM_INS_USAX, ARM_INS_UHASX, ARM_INS_UHSAX,
 *           ARM_INS_USUB16, ARM_INS_UHSUB16
 * ARM: sadd16, shadd16, sasx, ssax, shasx, shsax, ssub16, shsub16
 *      uadd16, uhadd16, uasx, usax, uhasx, uhsax, usub16, uhsub16
 */
static RzILOpEffect *sadd16(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpBitVector *al = UNSIGNED(16, a);
	RzILOpBitVector *ah = UNSIGNED(16, SHIFTR0(DUP(a), UN(5, 16)));
	RzILOpBitVector *bl = UNSIGNED(16, b);
	RzILOpBitVector *bh = UNSIGNED(16, SHIFTR0(DUP(b), UN(5, 16)));
	bool is_signed = insn->id == ARM_INS_SADD16 || insn->id == ARM_INS_SHADD16 || insn->id == ARM_INS_SASX ||
		insn->id == ARM_INS_SSAX || insn->id == ARM_INS_SHASX || insn->id == ARM_INS_SHSAX ||
		insn->id == ARM_INS_SSUB16 || insn->id == ARM_INS_SHSUB16;
	RzILOpBitVector *(*cast)(ut32 length, RzILOpBitVector *val) = is_signed ? rz_il_op_new_signed : rz_il_op_new_unsigned;
	al = cast(17, al);
	ah = cast(17, ah);
	bl = cast(17, bl);
	bh = cast(17, bh);
	RzILOpBitVector *l, *h;
	bool halve = false;
	switch (insn->id) {
	case ARM_INS_SHSAX:
	case ARM_INS_UHSAX:
		halve = true;
		// fallthrough
	case ARM_INS_SASX:
	case ARM_INS_UASX:
		l = SUB(al, bh);
		h = ADD(ah, bl);
		break;

	case ARM_INS_SHASX:
	case ARM_INS_UHASX:
		halve = true;
		// fallthrough
	case ARM_INS_SSAX:
	case ARM_INS_USAX:
		l = ADD(al, bh);
		h = SUB(ah, bl);
		break;

	case ARM_INS_SHSUB16:
	case ARM_INS_UHSUB16:
		halve = true;
		// fallthrough
	case ARM_INS_SSUB16:
	case ARM_INS_USUB16:
		l = SUB(al, bl);
		h = SUB(ah, bh);
		break;

	case ARM_INS_SHADD16:
	case ARM_INS_UHADD16:
		halve = true;
		// fallthrough
	default: // ARM_INS_SADD16, ARM_INS_SHADD16, ARM_INS_UADD16, ARM_INS_UHADD16
		l = ADD(al, bl);
		h = ADD(ah, bh);
		break;
	}
	bool set_ge = !halve;
	RzILOpBitVector *res = halve
		? APPEND(UNSIGNED(16, SHIFTRA(VARL("res1"), UN(4, 1))),
			  UNSIGNED(16, SHIFTRA(VARL("res0"), UN(4, 1))))
		: APPEND(UNSIGNED(16, VARL("res1")), UNSIGNED(16, VARL("res0")));
	RzILOpEffect *eff = write_reg(REGID(0), res);
	if (!eff) {
		rz_il_op_pure_free(l);
		rz_il_op_pure_free(h);
		return NULL;
	}
	if (set_ge) {
		ut64 tval = is_signed ? 0 : 3;
		ut64 fval = 3 - tval;
		eff = SEQ2(
			SETL("gef",
				APPEND(
					ITE(MSB(VARL("res1")), UN(2, tval), UN(2, fval)),
					ITE(MSB(VARL("res0")), UN(2, tval), UN(2, fval)))),
			eff);
	}
	return SEQ3(SETL("res0", l), SETL("res1", h), eff);
}

/**
 * Capstone: ARM_INS_SADD8, ARM_INS_SHADD8, ARM_INS_SSUB8, ARM_INS_SHSUB8
 *           ARM_INS_UADD8, ARM_INS_UHADD8, ARM_INS_USUB8, ARM_INS_UHSUB8
 * ARM: sadd8, shadd8, ssub8, shsub8, uadd8, uhadd8, usub8, uhsub8
 */
static RzILOpEffect *sadd8(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpBitVector *a0 = UNSIGNED(8, a);
	RzILOpBitVector *a1 = UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 8)));
	RzILOpBitVector *a2 = UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 16)));
	RzILOpBitVector *a3 = UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 24)));
	RzILOpBitVector *b0 = UNSIGNED(8, b);
	RzILOpBitVector *b1 = UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 8)));
	RzILOpBitVector *b2 = UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 16)));
	RzILOpBitVector *b3 = UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 24)));
	RzILOpBitVector *r0, *r1, *r2, *r3;
	bool halve = false;
	switch (insn->id) {
	case ARM_INS_SHSUB8:
	case ARM_INS_UHSUB8:
		halve = true;
		// fallthrough
	case ARM_INS_SSUB8:
	case ARM_INS_USUB8:
		r0 = SUB(a0, b0);
		r1 = SUB(a1, b1);
		r2 = SUB(a2, b2);
		r3 = SUB(a3, b3);
		break;

	case ARM_INS_SHADD8:
	case ARM_INS_UHADD8:
		halve = true;
		// fallthrough
	default: // ARM_INS_SADD8, ARM_INS_UADD8
		r0 = ADD(a0, b0);
		r1 = ADD(a1, b1);
		r2 = ADD(a2, b2);
		r3 = ADD(a3, b3);
		break;
	}
	bool set_ge = !halve;
	bool is_signed = insn->id == ARM_INS_SADD8 || insn->id == ARM_INS_SHADD8 || insn->id == ARM_INS_SSUB8 ||
		insn->id == ARM_INS_SHSUB8;
	if (set_ge) {
		// Retroactively patch the ops to extend to 8 before the calculation because this is needed for ge
		// Note: add/sub members here use the same structure, so using just `.add` is fine.
		RzILOpBitVector *(*cast)(ut32 length, RzILOpBitVector *val) = is_signed ? rz_il_op_new_signed : rz_il_op_new_unsigned;
		r0->op.add.x = cast(9, r0->op.add.x);
		r0->op.add.y = cast(9, r0->op.add.y);
		r1->op.add.x = cast(9, r1->op.add.x);
		r1->op.add.y = cast(9, r1->op.add.y);
		r2->op.add.x = cast(9, r2->op.add.x);
		r2->op.add.y = cast(9, r2->op.add.y);
		r3->op.add.x = cast(9, r3->op.add.x);
		r3->op.add.y = cast(9, r3->op.add.y);
	}
	RzILOpBitVector *res;
	if (halve) {
		res = APPEND(
			APPEND(
				SHIFTRA(VARL("res3"), UN(3, 1)),
				SHIFTRA(VARL("res2"), UN(3, 1))),
			APPEND(
				SHIFTRA(VARL("res1"), UN(3, 1)),
				SHIFTRA(VARL("res0"), UN(3, 1))));
	} else {
		res = APPEND(
			APPEND(
				UNSIGNED(8, VARL("res3")),
				UNSIGNED(8, VARL("res2"))),
			APPEND(
				UNSIGNED(8, VARL("res1")),
				UNSIGNED(8, VARL("res0"))));
	}
	RzILOpEffect *eff = write_reg(REGID(0), res);
	if (!eff) {
		rz_il_op_pure_free(r0);
		rz_il_op_pure_free(r1);
		rz_il_op_pure_free(r2);
		rz_il_op_pure_free(r3);
		return NULL;
	}
	if (set_ge) {
		ut64 tval = is_signed ? 0 : 1;
		ut64 fval = 1 - tval;
		eff = SEQ2(
			SETL("gef",
				APPEND(
					APPEND(
						ITE(MSB(VARL("res3")), UN(1, tval), UN(1, fval)),
						ITE(MSB(VARL("res2")), UN(1, tval), UN(1, fval))),
					APPEND(
						ITE(MSB(VARL("res1")), UN(1, tval), UN(1, fval)),
						ITE(MSB(VARL("res0")), UN(1, tval), UN(1, fval))))),
			eff);
	}
	return SEQ5(
		SETL("res0", r0),
		SETL("res1", r1),
		SETL("res2", r2),
		SETL("res3", r3),
		eff);
}

/**
 * Capstone: ARM_INS_SEL
 * ARM: sel
 */
static RzILOpEffect *sel(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	return write_reg(REGID(0),
		APPEND(
			APPEND(
				UNSIGNED(8, SHIFTR0(ITE(IS_ZERO(LOGAND(VARG("gef"), UN(4, 1 << 3))), b, a), UN(5, 24))),
				UNSIGNED(8,
					SHIFTR0(ITE(IS_ZERO(LOGAND(VARG("gef"), UN(4, 1 << 2))), DUP(b), DUP(a)),
						UN(5, 16)))),
			APPEND(
				UNSIGNED(8,
					SHIFTR0(ITE(IS_ZERO(LOGAND(VARG("gef"), UN(4, 1 << 1))), DUP(b), DUP(a)),
						UN(5, 8))),
				UNSIGNED(8, ITE(IS_ZERO(LOGAND(VARG("gef"), UN(4, 1))), DUP(b), DUP(a))))));
}

/**
 * Capstone: ARM_INS_SBFX, ARM_INS_UBFX
 * ARM: sbfx, ubfx
 */
static RzILOpEffect *sbfx(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISIMM(2) || !ISIMM(3)) {
		return NULL;
	}
	RzILOpBitVector *val = REG(1);
	if (!val) {
		return NULL;
	}
	val = UNSIGNED(IMM(3), SHIFTR0(val, UN(5, IMM(2))));
	val = insn->id == ARM_INS_SBFX ? SIGNED(32, val) : UNSIGNED(32, val);
	return write_reg(REGID(0), val);
}

/**
 * Capstone: ARM_INS_SDIV
 * ARM: sdiv
 */
static RzILOpEffect *sdiv(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	return write_reg(REGID(0),
		ITE(EQ(b, U32(0)), U32(0),
			ITE(AND(EQ(a, U32(0x80000000)), EQ(DUP(b), U32(0xffffffff))),
				U32(0x80000000),
				SDIV(DUP(a), DUP(b)))));
}

/**
 * Capstone: ARM_INS_UDIV
 * ARM: udiv
 */
static RzILOpEffect *udiv(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	return write_reg(REGID(0),
		ITE(EQ(b, U32(0)), U32(0),
			DIV(a, DUP(b))));
}

/**
 * Capstone: ARM_INS_UMAAL
 * ARM: umaal
 */
static RzILOpEffect *umaal(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISREG(1)) {
		return NULL;
	}
	RzILOpBitVector *dl = ARG(0);
	RzILOpBitVector *dh = ARG(1);
	RzILOpBitVector *a = ARG(2);
	RzILOpBitVector *b = ARG(3);
	RzILOpEffect *wl = write_reg(REGID(0), UNSIGNED(32, VARL("res")));
	RzILOpEffect *wh = write_reg(REGID(1), UNSIGNED(32, SHIFTR0(VARL("res"), UN(6, 32))));
	if (!dl || !dh || !a || !b || !wl || !wh) {
		rz_il_op_pure_free(dl);
		rz_il_op_pure_free(dh);
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_effect_free(wl);
		rz_il_op_effect_free(wh);
		return NULL;
	}
	return SEQ3(
		SETL("res", ADD(ADD(MUL(UNSIGNED(64, a), UNSIGNED(64, b)), UNSIGNED(64, dl)), UNSIGNED(64, dh))),
		wl, wh);
}

/**
 * Capstone: ARM_INS_UMULL
 * ARM: umull
 */
static RzILOpEffect *umull(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISREG(1)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(2);
	RzILOpBitVector *b = ARG(3);
	RzILOpEffect *wl = write_reg(REGID(0), UNSIGNED(32, VARL("res")));
	RzILOpEffect *wh = write_reg(REGID(1), UNSIGNED(32, SHIFTR0(VARL("res"), UN(6, 32))));
	if (!a || !b || !wl || !wh) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_effect_free(wl);
		rz_il_op_effect_free(wh);
		return NULL;
	}
	if (insn->detail->arm.update_flags) {
		return SEQ4(
			SETL("res", MUL(UNSIGNED(64, a), UNSIGNED(64, b))),
			wl, wh, update_flags_zn(VARL("res")));
	} else {
		return SEQ3(SETL("res", MUL(UNSIGNED(64, a), UNSIGNED(64, b))), wl, wh);
	}
}

static RzILOpBitVector *absdiff(RzILOpBitVector *a, RzILOpBitVector *b) {
	return LET("a", a,
		LET("b", b, ITE(ULE(VARLP("a"), VARLP("b")), SUB(VARLP("b"), VARLP("a")), SUB(VARLP("a"), VARLP("b")))));
}

/**
 * Capstone: ARM_INS_USAD8, ARM_INS_USADA8
 * ARM: usad8, usada8
 */
static RzILOpEffect *usad8(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	bool have_acc = insn->id == ARM_INS_USADA8;
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	RzILOpBitVector *acc = have_acc ? ARG(3) : NULL;
	if (!a || !b || (have_acc && !acc)) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpBitVector *a0 = UNSIGNED(32, UNSIGNED(8, a));
	RzILOpBitVector *a1 = UNSIGNED(32, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 8))));
	RzILOpBitVector *a2 = UNSIGNED(32, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 16))));
	RzILOpBitVector *a3 = UNSIGNED(32, UNSIGNED(8, SHIFTR0(DUP(a), UN(5, 24))));
	RzILOpBitVector *b0 = UNSIGNED(32, UNSIGNED(8, b));
	RzILOpBitVector *b1 = UNSIGNED(32, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 8))));
	RzILOpBitVector *b2 = UNSIGNED(32, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 16))));
	RzILOpBitVector *b3 = UNSIGNED(32, UNSIGNED(8, SHIFTR0(DUP(b), UN(5, 24))));
	RzILOpBitVector *sum = ADD(absdiff(a0, b0), ADD(absdiff(a1, b1), ADD(absdiff(a2, b2), absdiff(a3, b3))));
	if (have_acc) {
		sum = ADD(acc, sum);
	}
	return write_reg(REGID(0), sum);
}

/**
 * Capstone: ARM_INS_SMLABB, ARM_INS_SMLABT, ARM_INS_SMLATB, ARM_INS_SMLATT, ARM_INS_SMLAD, ARM_INS_SMLADX, ARM_INS_SMLSD, ARM_INS_SMLSDX
 * ARM: smlabb, smlabt, smlatb, smlatt, smlad, smladx, smlsd, smlsdx
 */
static RzILOpEffect *smlabb(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *ra = ARG(1);
	RzILOpBitVector *rb = ARG(2);
	RzILOpBitVector *acc = ARG(3);
	if (!ra || !rb || !acc) {
		rz_il_op_pure_free(ra);
		rz_il_op_pure_free(rb);
		rz_il_op_pure_free(acc);
		return NULL;
	}
	RzILOpBitVector *a = ra;
	RzILOpBitVector *b = rb;
	bool exchange_b = insn->id == ARM_INS_SMLADX || insn->id == ARM_INS_SMLSDX;
	if (insn->id == ARM_INS_SMLATB || insn->id == ARM_INS_SMLATT) {
		a = SHIFTR0(ra, UN(5, 16));
	}
	if (insn->id == ARM_INS_SMLABT || insn->id == ARM_INS_SMLATT || exchange_b) {
		b = SHIFTR0(rb, UN(5, 16));
	}
	a = UNSIGNED(16, a);
	b = UNSIGNED(16, b);
	RzILOpBitVector *product;
	ut32 extend_bits;
	if (insn->id == ARM_INS_SMLAD || insn->id == ARM_INS_SMLADX || insn->id == ARM_INS_SMLSD ||
		insn->id == ARM_INS_SMLSDX) {
		extend_bits = 34; // need more bits for the larger range that can be reached here
		RzILOpBitVector *ah = SIGNED(extend_bits, UNSIGNED(16, SHIFTR0(DUP(ra), UN(5, 16))));
		RzILOpBitVector *bh = SIGNED(extend_bits, UNSIGNED(16, exchange_b ? DUP(rb) : SHIFTR0(DUP(rb), UN(5, 16))));
		RzILOpBitVector *proda = MUL(SIGNED(extend_bits, a), SIGNED(extend_bits, b));
		RzILOpBitVector *prodb = MUL(ah, bh);
		product = insn->id == ARM_INS_SMLSD || insn->id == ARM_INS_SMLSDX
			? SUB(proda, prodb)
			: ADD(proda, prodb);
	} else {
		extend_bits = 33;
		product = MUL(SIGNED(extend_bits, a), SIGNED(extend_bits, b));
	}
	RzILOpEffect *eff = write_reg(REGID(0), UNSIGNED(32, VARL("res")));
	if (!eff) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_pure_free(acc);
		return NULL;
	}
	return SEQ3(
		SETL("res", ADD(product, SIGNED(extend_bits, acc))),
		eff,
		BRANCH(INV(EQ(VARL("res"), SIGNED(extend_bits, REG(0)))), SETG("qf", IL_TRUE), NULL));
}

/**
 * Capstone: ARM_INS_SMLAL, ARM_INS_SMLALBB, ARM_INS_SMLALBT, ARM_INS_SMLALTB, ARM_INS_SMLALTT, ARM_INS_SMLALD, ARM_INS_SMLALDX,
 *           ARM_INS_SMLSLD, ARM_INS_SMLSLDX, ARM_INS_UMLAL
 * ARM: smlal, smlals, smlalbb, smlalbt, smlaltb, smlaltt, smlald, smlaldx, smlsld, smlsldx, umlal, umlals
 */
static RzILOpEffect *smlal(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISREG(1)) {
		return NULL;
	}
	RzILOpBitVector *dl = ARG(0);
	RzILOpBitVector *dh = ARG(1);
	RzILOpBitVector *ra = ARG(2);
	RzILOpBitVector *rb = ARG(3);
	RzILOpEffect *wl = write_reg(REGID(0), UNSIGNED(32, VARL("res")));
	RzILOpEffect *wh = write_reg(REGID(1), UNSIGNED(32, SHIFTR0(VARL("res"), UN(6, 32))));
	if (!dl || !dh || !ra || !rb || !wl || !wh) {
		rz_il_op_pure_free(dl);
		rz_il_op_pure_free(dh);
		rz_il_op_pure_free(ra);
		rz_il_op_pure_free(rb);
		rz_il_op_effect_free(wl);
		rz_il_op_effect_free(wh);
		return NULL;
	}
	RzILOpBitVector *a = ra;
	RzILOpBitVector *b = rb;
	bool exchange_b = insn->id == ARM_INS_SMLALDX || insn->id == ARM_INS_SMLSLDX;
	if (insn->id == ARM_INS_SMLALTB || insn->id == ARM_INS_SMLALTT) {
		a = SHIFTR0(ra, UN(5, 16));
	}
	if (insn->id == ARM_INS_SMLALBT || insn->id == ARM_INS_SMLALTT || exchange_b) {
		b = SHIFTR0(rb, UN(5, 16));
	}
	if (insn->id == ARM_INS_SMLALBB || insn->id == ARM_INS_SMLALBT || insn->id == ARM_INS_SMLALTB ||
		insn->id == ARM_INS_SMLALTT || insn->id == ARM_INS_SMLALD || insn->id == ARM_INS_SMLALDX ||
		insn->id == ARM_INS_SMLSLD || insn->id == ARM_INS_SMLSLDX) {
		a = UNSIGNED(16, a);
		b = UNSIGNED(16, b);
	}
	if (insn->id == ARM_INS_UMLAL) {
		a = UNSIGNED(64, a);
		b = UNSIGNED(64, b);
	} else {
		a = SIGNED(64, a);
		b = SIGNED(64, b);
	}
	RzILOpBitVector *product;
	if (insn->id == ARM_INS_SMLALD || insn->id == ARM_INS_SMLALDX || insn->id == ARM_INS_SMLSLD ||
		insn->id == ARM_INS_SMLSLDX) {
		RzILOpBitVector *ah = SIGNED(64, UNSIGNED(16, SHIFTR0(DUP(ra), UN(5, 16))));
		RzILOpBitVector *bh = SIGNED(64, UNSIGNED(16, exchange_b ? DUP(rb) : SHIFTR0(DUP(rb), UN(5, 16))));
		product = insn->id == ARM_INS_SMLSLD || insn->id == ARM_INS_SMLSLDX
			? SUB(MUL(a, b), MUL(ah, bh))
			: ADD(MUL(a, b), MUL(ah, bh));
	} else {
		product = MUL(a, b);
	}
	RzILOpBitVector *res = ADD(product, APPEND(dh, dl));
	return insn->detail->arm.update_flags
		? SEQ4(SETL("res", res), update_flags_zn(VARL("res")), wl, wh)
		: SEQ3(SETL("res", res), wl, wh);
}

/**
 * Capstone: ARM_INS_SMLAWB, ARM_INS_SMLAWT
 * ARM: smlawb, smlawt
 */
static RzILOpEffect *smlaw(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	RzILOpBitVector *acc = ARG(3);
	RzILOpBitVector *rres = ARG(0);
	RzILOpEffect *eff = write_reg(REGID(0), UNSIGNED(32, VARL("res")));
	if (!a || !b || !acc || !rres || !eff) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_pure_free(acc);
		rz_il_op_pure_free(rres);
		rz_il_op_effect_free(eff);
		return NULL;
	}
	a = SIGNED(64, a);
	b = SIGNED(64, insn->id == ARM_INS_SMLAWT ? SHIFTRA(b, UN(5, 16)) : UNSIGNED(16, b));
	acc = SIGNED(64, acc);
	return SEQ3(
		SETL("res", ADD(SHIFTR0(MUL(a, b), UN(6, 16)), acc)),
		eff,
		BRANCH(INV(EQ(UNSIGNED(48, VARL("res")), SIGNED(48, rres))), SETG("qf", IL_TRUE), NULL));
}

/**
 * Capstone: ARM_INS_SMMLA, ARM_INS_SMMLAR, ARM_INS_SMMLS, ARM_INS_SMMLSR
 * ARM: smmla, smmlar, smmls, smmlsr
 */
static RzILOpEffect *smmla(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	RzILOpBitVector *acc = ARG(3);
	if (!a || !b || !acc) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		rz_il_op_pure_free(acc);
		return NULL;
	}
	RzILOpBitVector *res = insn->id == ARM_INS_SMMLS || insn->id == ARM_INS_SMMLSR
		? SUB(APPEND(acc, U32(0)), MUL(SIGNED(64, a), SIGNED(64, b)))
		: ADD(MUL(SIGNED(64, a), SIGNED(64, b)), APPEND(acc, U32(0)));
	if (insn->id == ARM_INS_SMMLAR || insn->id == ARM_INS_SMMLSR) {
		res = ADD(res, U64(0x80000000));
	}
	return write_reg(REGID(0), UNSIGNED(32, SHIFTR0(res, UN(6, 32))));
}

/**
 * Capstone: ARM_INS_SMMUL, ARM_INS_SMMULR
 * ARM: smmul, smmulr
 */
static RzILOpEffect *smmul(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *a = ARG(1);
	RzILOpBitVector *b = ARG(2);
	if (!a || !b) {
		rz_il_op_pure_free(a);
		rz_il_op_pure_free(b);
		return NULL;
	}
	RzILOpBitVector *res = MUL(SIGNED(64, a), SIGNED(64, b));
	if (insn->id == ARM_INS_SMMULR) {
		res = ADD(res, U64(0x80000000));
	}
	return write_reg(REGID(0), UNSIGNED(32, SHIFTR0(res, UN(6, 32))));
}

/**
 * Capstone: ARM_INS_SMUAD, ARM_INS_SMUADX
 * ARM: smuad
 */
static RzILOpEffect *smuad(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *ra = ARG(1);
	RzILOpBitVector *rb = ARG(2);
	RzILOpEffect *eff = write_reg(REGID(0), UNSIGNED(32, VARL("res")));
	if (!ra || !rb || !eff) {
		rz_il_op_pure_free(ra);
		rz_il_op_pure_free(rb);
		rz_il_op_effect_free(eff);
		return NULL;
	}
	RzILOpBitVector *al = SIGNED(33, UNSIGNED(16, ra));
	RzILOpBitVector *ah = SIGNED(33, UNSIGNED(16, SHIFTR0(DUP(ra), UN(5, 16))));
	RzILOpBitVector *bl = SIGNED(33, UNSIGNED(16, rb));
	RzILOpBitVector *bh = SIGNED(33, UNSIGNED(16, SHIFTR0(DUP(rb), UN(5, 16))));
	if (insn->id == ARM_INS_SMUADX) {
		RzILOpBitVector *tmp = bl;
		bl = bh;
		bh = tmp;
	}
	return SEQ3(
		SETL("res", ADD(MUL(al, bl), MUL(ah, bh))),
		eff,
		BRANCH(XOR(MSB(VARL("res")), MSB(REG(0))), SETG("qf", IL_TRUE), NULL));
}

/**
 * Capstone: ARM_INS_SMULBB, ARM_INS_SMULBT, ARM_INS_SMULTB, ARM_INS_SMULTT, ARM_INS_SMUSD, ARM_INS_SMUSDX
 * ARM: smulbb, smulbt, smultb, smultt, smusd, smusdx
 */
static RzILOpEffect *smulbb(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0)) {
		return NULL;
	}
	RzILOpBitVector *ra = ARG(1);
	RzILOpBitVector *rb = ARG(2);
	if (!ra || !rb) {
		rz_il_op_pure_free(ra);
		rz_il_op_pure_free(rb);
		return NULL;
	}
	RzILOpBitVector *a = ra;
	RzILOpBitVector *b = rb;
	if (insn->id == ARM_INS_SMULTB || insn->id == ARM_INS_SMULTT) {
		a = SHIFTR0(a, UN(5, 16));
	}
	if (insn->id == ARM_INS_SMULBT || insn->id == ARM_INS_SMULTT || insn->id == ARM_INS_SMUSDX) {
		b = SHIFTR0(b, UN(5, 16));
	}
	a = UNSIGNED(16, a);
	b = UNSIGNED(16, b);
	RzILOpBitVector *res = MUL(SIGNED(32, a), SIGNED(32, b));
	if (insn->id == ARM_INS_SMUSD || insn->id == ARM_INS_SMUSDX) {
		res = SUB(res,
			MUL(
				SIGNED(32, UNSIGNED(16, SHIFTR0(DUP(ra), UN(5, 16)))),
				SIGNED(32,
					UNSIGNED(16, insn->id == ARM_INS_SMUSDX ? DUP(rb) : SHIFTR0(DUP(rb), UN(5, 16))))));
	}
	return write_reg(REGID(0), res);
}

/**
 * Capstone: ARM_INS_TBB, ARM_INS_TBH
 * ARM: tbb, tbh
 */
static RzILOpEffect *tbb(cs_insn *insn, bool is_thumb) {
	RzILOpBitVector *addr = ARG(0);
	if (!addr) {
		return NULL;
	}
	RzILOpBitVector *off = insn->id == ARM_INS_TBB ? LOAD(addr) : LOADW(16, addr);
	return JMP(ADD(U32(PC(insn->address, is_thumb)), SHIFTL0(UNSIGNED(32, off), UN(5, 1))));
}

static RzILOpEffect *write_reg_lane(arm_reg reg, ut32 lane, ut32 vec_size, RzILOpBitVector *v) {
	ut32 reg_size = reg_bits(reg);
	ut32 offset = vec_size * lane;

	// up bound is reg_bits(<Qd>)
	if (offset + vec_size > 128) {
		return NULL;
	}

	RzILOpBitVector *sft_val = SHIFTL0(UNSIGNED(reg_size, v), UN(8, offset));
	return write_reg(reg, sft_val);
}

/**
 * For Extend Instruction Set
 * TODO: Split to a seperate file and include before arm lifter
 * VFP and NEON
 */

#if CS_API_MAJOR > 3
/**
 * Capstone: ARM_INS_VMOV
 * ARM: vmov
 */
static RzILOpEffect *vmov(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2 || !ISREG(0) || (!ISIMM(1) && !ISREG(1))) {
		return NULL;
	}

	// vmov for immediate must be unconditional
	// vmov.<dt> <Qd>, or vmov.<dt> <Dd>
	// vmov.F32 <Sd>, or vmov.F64 <Qd>
	if (ISIMM(1)) {
		// possible : I8, I16, I32, I64, F32 for Q/D register
		// possible : F32 for S register
		// possible : F64 for D register
		ut32 imm_width = DT_WIDTH(insn);
		if (!imm_width) {
			return NULL;
		}
		ut32 reg_width = REG_WIDTH(0);
		if (reg_width < imm_width) {
			return NULL;
		}

		ut32 imm = get_imm(insn, 1, NULL);
		RzILOpBitVector *imm_bv = repeated_imm(imm_width, reg_width, imm);
		if (!imm_bv) {
			return NULL;
		}

		// vmvn Dd, imm
		if (insn->id == ARM_INS_VMVN) {
			imm_bv = LOGNOT(imm_bv);
		}

		return write_reg(REGID(0), imm_bv);
	}

	// 2 core registers and 1 double-word register
	if (OPCOUNT() == 3) {
		if (!is_core_reg(REGID(0))) {
			// vmov <Dm> <Rt1> <Rt2>, Dm[low] = Rt1, Dm[high] = Rt2
			RzILOpBitVector *rt1_val = REG(1);
			RzILOpBitVector *rt2_val = REG(2);
			if (!rt1_val || !rt2_val) {
				return NULL;
			}
			return write_reg(REGID(0), APPEND(rt2_val, rt1_val));
		}
		// vmov <Rt1> <Rt2> <Dm>, Rt1 = Dm[low], Rt2 = Dm[high]
		RzILOpBitVector *reg_val = REG(2);
		if (!reg_val) {
			return NULL;
		}
		RzILOpBitVector *rt1_val = UNSIGNED(32, DUP(reg_val));
		RzILOpBitVector *rt2_val = UNSIGNED(32, SHIFTR0(reg_val, UN(8, 32)));
		return SEQ2(write_reg(REGID(0), rt1_val),
			write_reg(REGID(1), rt2_val));
	}

	// 2 core registers and 2 single-word registers
	// vmov <Sm1> <Sm2> <Rt1> <Rt2>
	// vmov <Rt1> <Rt2> <Sm1> <Sm2>
	if (OPCOUNT() == 4) {
		RzILOpBitVector *rt1_val = REG(2);
		RzILOpBitVector *rt2_val = REG(3);
		if (!rt1_val || !rt2_val) {
			return NULL;
		}
		return SEQ2(write_reg(REGID(0), rt1_val),
			write_reg(REGID(1), rt2_val));
	}

	// core register to scalar
	if (NEON_LANE(0) != -1 && !is_core_reg(REGID(0)) && is_core_reg(REGID(1))) {
		// vmov.<vec_size> <Dd>[lane], <Rt>
		// <Dd>[lane] = <Rt>{vecsize - 1 : 0}
		if (!VVEC_SIZE(insn) || NEON_LANE(0) == -1) {
			return NULL;
		}
		RzILOpBitVector *rt_val = UNSIGNED(VVEC_SIZE(insn), REG(1));
		return write_reg_lane(REGID(0), NEON_LANE(0), VVEC_SIZE(insn), rt_val);
	}

	// scalar to core register
	if (NEON_LANE(1) != -1 && !is_core_reg(REGID(1)) && is_core_reg(REGID(0))) {
		// vmov.<dt> <Rt> <Dd>[lane]
		// <Rt> = extend_to_32(<Dd>[lane], lane has size of dt)
		// unsigned/signed extend is specified by <dt> in capstone
		if (VVEC_DT(insn) == ARM_VECTORDATA_INVALID) {
			return NULL;
		}
		bool use_zero_ext = true;
		if (VVEC_DT(insn) == ARM_VECTORDATA_S8 || VVEC_DT(insn) == ARM_VECTORDATA_S16) {
			use_zero_ext = false;
		}
		RzILOpBitVector *lane_val = read_reg_lane(REGID(1), NEON_LANE(1), DT_WIDTH(insn));
		RzILOpBitVector *ext_lane_val = use_zero_ext ? UNSIGNED(32, lane_val) : SIGNED(32, lane_val);
		return write_reg(REGID(0), ext_lane_val);
	}

	// 1. vmov rd, rn
	// 2. core register and single-word register
	RzILOpBitVector *val = ARG(1);
	if (!val) {
		return NULL;
	}

	// vmvn Qd, Qn
	if (insn->id == ARM_INS_VMVN) {
		val = LOGNOT(val);
	}

	return write_reg(REGID(0), val);
}
#endif

/**
 * Capstone: ARM_INS_VMRS
 * ARM: vmrs
 * read extension/NEON system register into core register
 */
static RzILOpEffect *vmrs(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISREG(1)) {
		return NULL;
	}

	// VMRS <Rt>, FPSCR only
	if (REGID(1) != ARM_REG_FPSCR) {
		return NULL;
	}

	// if <Rt> is APSR, transfer to flags
	if (REGID(0) == ARM_REG_APSR_NZCV) {
		RzILOpBitVector *val = VARG("fpscr");
		return SEQ4(
			SETG("nf", INV(IS_ZERO(LOGAND(val, U32(1ul << 31))))),
			SETG("zf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 30))))),
			SETG("cf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 29))))),
			SETG("vf", INV(IS_ZERO(LOGAND(DUP(val), U32(1ul << 28))))));
	}

	if (is_core_reg(REGID(0)) && REGID(0) != ARM_REG_PC) {
		return write_reg(REGID(0), VARG("fpscr"));
	}

	return NULL;
}

/**
 * Capstone: ARM_INS_VMSR
 * ARM: vmsr
 * write core register value into extension/NEON system register
 */
static RzILOpEffect *vmsr(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || REGID(0) != ARM_REG_FPSCR) {
		return NULL;
	}

	RzILOpBitVector *val;
	if (REGID(1) == ARM_REG_CPSR || REGID(1) == ARM_REG_SPSR || REGID(1) == ARM_REG_APSR) {
		val = LOGOR(ITE(VARG("nf"), U32(1ul << 31), U32(0)),
			LOGOR(ITE(VARG("zf"), U32(1ul << 30), U32(0)),
				LOGOR(ITE(VARG("cf"), U32(1ul << 29), U32(0)),
					LOGOR(ITE(VARG("vf"), U32(1ul << 28), U32(0)),
						LOGOR(ITE(VARG("qf"), U32(1ul << 27), U32(0)),
							SHIFTL0(UNSIGNED(32, VARG("gef")), UN(5, 16)))))));
	} else if (REGID(1) == ARM_REG_APSR_NZCV) {
		val = LOGOR(ITE(VARG("nf"), U32(1ul << 31), U32(0)),
			LOGOR(ITE(VARG("zf"), U32(1ul << 30), U32(0)),
				LOGOR(ITE(VARG("cf"), U32(1ul << 29), U32(0)),
					LOGOR(ITE(VARG("vf"), U32(1ul << 28), U32(0)),
						U32(0)))));
	} else {
		val = ARG(1);
	}

	return SETG("fpscr", val);
}

/**
 * Capstone: ARM_INS_VAND, ARM_INS_VORR, ARM_INS_VORN, ARM_INS_VEOR, ARM_INS_VBIC,
 * vand, vorr, vorn, veor, vbic
 */
static RzILOpEffect *vbitwise(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || OPCOUNT() < 2) {
		return NULL;
	}

	// has following types:
	// 1. bitwise_op <dst>, #imm
	// 2. bitwise_op <dst>, <src1>, <src2>
	RzILOpBitVector *src_a = ARG(OPCOUNT() - 2);
	RzILOpBitVector *src_b;

	// pseudo-instruction VAND(imm) disassembly produces VBIC(imm)
	// pseudo-instruction VORN(imm) disassembly produces VORR(imm)
	if ((insn->id == ARM_INS_VBIC || insn->id == ARM_INS_VORR) && ISIMM(OPCOUNT() - 1)) {
		ut32 imm = get_imm(insn, OPCOUNT() - 1, NULL);
		src_b = repeated_imm(DT_WIDTH(insn), REG_WIDTH(0), imm);
	} else {
		src_b = REG(OPCOUNT() - 1);
	}

	if (!src_a || !src_b) {
		rz_il_op_pure_free(src_a);
		rz_il_op_pure_free(src_b);
		return NULL;
	}

	RzILOpBitVector *res;
	switch (insn->id) {
	case ARM_INS_VAND:
		res = LOGAND(src_a, src_b);
		break;
	case ARM_INS_VORR:
		res = LOGOR(src_a, src_b);
		break;
	case ARM_INS_VORN:
		res = LOGOR(src_a, LOGNOT(src_b));
		break;
	case ARM_INS_VEOR:
		res = LOGXOR(src_a, src_b);
		break;
	case ARM_INS_VBIC:
		res = LOGAND(src_a, LOGNOT(src_b));
		break;
	default:
		rz_il_op_pure_free(src_a);
		rz_il_op_pure_free(src_b);
		return NULL;
	}

	return write_reg(REGID(0), res);
}

/**
 * Capstone: ARM_INS_VBIT, ARM_INS_VBIF, ARM_INS_VBSL
 * ARM: vbit, vbif, vbsl
 */
static RzILOpEffect *vbit_insert(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || OPCOUNT() < 3) {
		return NULL;
	}

	// v<op> <Qd>, <Qn>, <Qm>
	// v<op> <Dd>, <Dn>, <Dm>
	RzILOpBitVector *d = REG(0);
	RzILOpBitVector *n = REG(1);
	RzILOpBitVector *m = REG(2);

	if (!d || !n || !m) {
		rz_il_op_pure_free(d);
		rz_il_op_pure_free(n);
		rz_il_op_pure_free(m);
		return NULL;
	}

	RzILOpBitVector *res;
	switch (insn->id) {
	case ARM_INS_VBIF:
		// Rd = (d and m) or (n and not(m))
		res = LOGOR(LOGAND(d, m), LOGAND(n, LOGNOT(DUP(m))));
		break;
	case ARM_INS_VBIT:
		// Rd = (n and m) or (d and not(m))
		res = LOGOR(LOGAND(n, m), LOGAND(d, LOGNOT(DUP(m))));
		break;
	case ARM_INS_VBSL:
		// Rd = (n and d) or (m and not(d))
		res = LOGOR(LOGAND(n, d), LOGAND(m, LOGNOT(DUP(d))));
		break;
	default:
		rz_il_op_pure_free(d);
		rz_il_op_pure_free(n);
		rz_il_op_pure_free(m);
		return NULL;
	}

	return write_reg(REGID(0), res);
}

/**
 * Capstone: ARM_INS_VCEQ, ARM_INS_VCGE, ARM_INS_VCGT, ARM_INS_VCLE, ARM_INS_VCLT
 * ARM_INS_VACGE, ARM_INS_VACGT
 * ARM: vceq, vcge, vcgt, vcle, vclt, vacge, vacgt, [pseudo: vacle, vaclt]
 */
static RzILOpEffect *vec_cmp(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) && OPCOUNT() < 3) {
		return NULL;
	}

	if (VVEC_DT(insn) == ARM_VECTORDATA_F32) {
		ut32 vec_size = 32;
		RzILOpEffect *eff = EMPTY();
		for (int i = 0; i < REG_WIDTH(0) / vec_size; ++i) {
			RzILOpFloat *l_elem = BV2F(RZ_FLOAT_IEEE754_BIN_32,
				read_reg_lane(REGID(1), i, vec_size));
			RzILOpFloat *r_elem = ISIMM(2) ? F32(0.0f) : BV2F(RZ_FLOAT_IEEE754_BIN_32, read_reg_lane(REGID(2), i, vec_size));
			RzILOpBool *cond;
			switch (insn->id) {
			case ARM_INS_VCEQ:
				cond = FEQ(l_elem, r_elem);
				break;
			case ARM_INS_VCGE:
				cond = INV(FORDER(l_elem, r_elem));
				break;
			case ARM_INS_VCGT:
				cond = FORDER(r_elem, l_elem);
				break;
			case ARM_INS_VCLE:
				cond = INV(FORDER(r_elem, l_elem));
				break;
			case ARM_INS_VCLT:
				cond = FORDER(l_elem, r_elem);
				break;
			case ARM_INS_VACGE:
				cond = INV(FORDER(FABS(l_elem), FABS(r_elem)));
				break;
			case ARM_INS_VACGT:
				cond = FORDER(FABS(r_elem), FABS(l_elem));
				break;
			default:
				cond = NULL;
				rz_il_op_pure_free(l_elem);
				rz_il_op_pure_free(r_elem);
				rz_il_op_effect_free(eff);
				return NULL;
			}

			eff = SEQ2(eff,
				write_reg_lane(REGID(0), i, vec_size,
					ITE(cond, LOGNOT(UN(vec_size, 0)), UN(vec_size, 0))));
		}
		return eff;
	}

	// for integer number
	ut32 vec_size = DT_WIDTH(insn);
	RzILOpEffect *eff = EMPTY();
	for (int i = 0; i < REG_WIDTH(0) / vec_size; ++i) {
		RzILOpBitVector *l_elem = read_reg_lane(REGID(1), i, vec_size);
		RzILOpBitVector *r_elem = ISIMM(2) ? UN(vec_size, 0) : read_reg_lane(REGID(2), i, vec_size);

		RzILOpBool *cond;
		bool as_signed = is_vec_signed(VVEC_DT(insn));
		switch (insn->id) {
		case ARM_INS_VCEQ:
			cond = EQ(l_elem, r_elem);
			break;
		case ARM_INS_VCGE:
			cond = as_signed ? SGE(l_elem, r_elem) : UGE(l_elem, r_elem);
			break;
		case ARM_INS_VCGT:
			cond = as_signed ? SGT(l_elem, r_elem) : UGT(l_elem, r_elem);
			break;
		case ARM_INS_VCLE:
			cond = as_signed ? SLE(l_elem, r_elem) : ULE(l_elem, r_elem);
			break;
		case ARM_INS_VCLT:
			cond = as_signed ? SLT(l_elem, r_elem) : SLE(l_elem, r_elem);
			break;
		default:
			cond = NULL;
			rz_il_op_pure_free(l_elem);
			rz_il_op_pure_free(r_elem);
			rz_il_op_effect_free(eff);
			return NULL;
		}

		eff = SEQ2(eff,
			write_reg_lane(REGID(0), i, vec_size,
				ITE(cond, LOGNOT(UN(vec_size, 0)), UN(vec_size, 0))));
	}

	return eff;
}

static RzILOpEffect *vtst(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 3) {
		return NULL;
	}

	// vtst <Vd>, <Vn>, <Vm>
	// for each lane:
	// Vd = iszero((n and m)) ? zero : not(zero)
	ut32 vec_size = VVEC_SIZE(insn);
	RzILOpEffect *eff = EMPTY();
	for (int i = 0; i < REG_WIDTH(0) / vec_size; ++i) {
		RzILOpBitVector *n = read_reg_lane(REGID(1), i, vec_size);
		RzILOpBitVector *m = read_reg_lane(REGID(2), i, vec_size);
		RzILOpBitVector *d = ITE(IS_ZERO(LOGAND(n, m)),
			UN(vec_size, 0),
			LOGNOT(UN(vec_size, 0)));
		eff = SEQ2(eff,
			write_reg_lane(REGID(0), i, vec_size, d));
	}

	return eff;
}

static RzILOpEffect *vldn_multiple_elem(cs_insn *insn, bool is_thumb) {
	ut32 rm_idx = OPCOUNT() - 1;
	ut32 rn_idx;
	ut32 regs = 0;
	bool wback = insn->detail->arm.writeback;
	bool use_rm_as_wback_offset = false;
	ut32 group_sz = insn->id - ARM_INS_VLD1 + 1;

	// vldn {list}, [Rn], Rm
	if (!ISMEM(rm_idx)) {
		regs = OPCOUNT() - 2;
		use_rm_as_wback_offset = true;
	} else {
		// vldn {list}, [Rn]
		rm_idx = -1;
		regs = OPCOUNT() - 1;
	}

	// mem_idx
	rn_idx = regs;

	// assert list_size % n == 0
	// assert they were all Dn
	ut32 n_groups = regs / group_sz;
	ut32 elem_bits = VVEC_SIZE(insn);
	ut32 elem_bytes = elem_bits / 8;
	ut32 lanes = 64 / elem_bits;
	ut32 addr_bits = REG_WIDTH(rn_idx);

	RzILOpEffect *wback_eff = NULL;
	RzILOpEffect *eff = EMPTY();
	RzILOpBitVector *addr = ARG(rn_idx);

	for (int i = 0; i < n_groups; ++i) {
		for (int j = 0; j < lanes; ++j) {
			RzILOpBitVector *data0, *data1, *data2, *data3;
			ut32 vreg_idx = i * group_sz;
			switch (group_sz) {
			case 1:
				data0 = LOADW(elem_bits, addr);
				eff = SEQ2(eff,
					write_reg_lane(REGID(vreg_idx), j, elem_bits, data0));
				break;
			case 2:
				data0 = LOADW(elem_bits, addr);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				data1 = LOADW(elem_bits, addr);
				eff = SEQ3(eff,
					write_reg_lane(REGID(vreg_idx), j, elem_bits, data0),
					write_reg_lane(REGID(vreg_idx + 1), j, elem_bits, data1));
				break;
			case 3:
				data0 = LOADW(elem_bits, addr);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				data1 = LOADW(elem_bits, addr);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				data2 = LOADW(elem_bits, addr);
				eff = SEQ4(eff,
					write_reg_lane(REGID(vreg_idx), j, elem_bits, data0),
					write_reg_lane(REGID(vreg_idx + 1), j, elem_bits, data1),
					write_reg_lane(REGID(vreg_idx + 2), j, elem_bits, data2));
				break;
			case 4:
				data0 = LOADW(elem_bits, addr);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				data1 = LOADW(elem_bits, addr);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				data2 = LOADW(elem_bits, addr);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				data3 = LOADW(elem_bits, addr);
				eff = SEQ5(eff,
					write_reg_lane(REGID(vreg_idx), j, elem_bits, data0),
					write_reg_lane(REGID(vreg_idx + 1), j, elem_bits, data1),
					write_reg_lane(REGID(vreg_idx + 2), j, elem_bits, data2),
					write_reg_lane(REGID(vreg_idx + 3), j, elem_bits, data3));
				break;
			default:
				rz_warn_if_reached();
				return NULL;
			}
			addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		}
	}

	// free last address inc op
	rz_il_op_pure_free(addr);

	// update Rn
	// if write_back then Rn = Rn + (if use_rm then Rm else 8 * regs)
	if (wback) {
		RzILOpBitVector *new_offset = use_rm_as_wback_offset ? ARG(rm_idx) : UN(32, 8 * regs);
		wback_eff = write_reg(REGID(rn_idx), ADD(REG(rn_idx), new_offset));
	} else {
		wback_eff = EMPTY();
	}

	return SEQ2(eff, wback_eff);
}

#if CS_API_MAJOR > 3
static RzILOpEffect *vldn_single_lane(cs_insn *insn, bool is_thumb) {
	ut32 rm_idx = OPCOUNT() - 1;
	ut32 rn_idx;
	bool use_rm = false;
	ut32 regs; // number of regs in {list}

	if (!ISMEM(rm_idx)) {
		use_rm = true;
		regs = OPCOUNT() - 2;
	} else {
		rm_idx = -1;
		regs = OPCOUNT() - 1;
	}
	rn_idx = regs;

	ut32 group_sz = insn->id - ARM_INS_VLD1 + 1;
	if (group_sz != regs) {
		return NULL;
	}

	RzILOpBitVector *data0, *data1, *data2, *data3;
	RzILOpEffect *eff;
	RzILOpBitVector *addr = ARG(rn_idx);
	ut32 vreg_idx = 0;
	ut32 elem_bits = VVEC_SIZE(insn);
	ut32 elem_bytes = elem_bits / 8;
	ut32 addr_bits = REG_WIDTH(rn_idx);

	// vld1/vld2/vld3/vld4, max(lane_size) == 4 Bytes
	if (group_sz > 4 || elem_bytes > 4) {
		return NULL;
	}

	unsigned char lane = NEON_LANE(0);
	switch (group_sz) {
	case 1:
		data0 = LOADW(elem_bits, addr);
		eff = write_reg_lane(REGID(vreg_idx), lane, elem_bits, data0);
		break;
	case 2:
		data0 = LOADW(elem_bits, addr);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data1 = LOADW(elem_bits, addr);
		eff = SEQ2(write_reg_lane(REGID(vreg_idx), lane, elem_bits, data0),
			write_reg_lane(REGID(vreg_idx + 1), lane, elem_bits, data1));
		break;
	case 3:
		data0 = LOADW(elem_bits, addr);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data1 = LOADW(elem_bits, addr);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data2 = LOADW(elem_bits, addr);
		eff = SEQ3(write_reg_lane(REGID(vreg_idx), lane, elem_bits, data0),
			write_reg_lane(REGID(vreg_idx + 1), lane, elem_bits, data1),
			write_reg_lane(REGID(vreg_idx + 2), lane, elem_bits, data2));
		break;
	case 4:
		data0 = LOADW(elem_bits, addr);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data1 = LOADW(elem_bits, addr);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data2 = LOADW(elem_bits, addr);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data3 = LOADW(elem_bits, addr);
		eff = SEQ4(write_reg_lane(REGID(vreg_idx), lane, elem_bits, data0),
			write_reg_lane(REGID(vreg_idx + 1), lane, elem_bits, data1),
			write_reg_lane(REGID(vreg_idx + 2), lane, elem_bits, data2),
			write_reg_lane(REGID(vreg_idx + 3), lane, elem_bits, data3));
		break;
	default:
		rz_warn_if_reached();
		return NULL;
	}

	bool wback = insn->detail->arm.writeback;
	RzILOpEffect *wback_eff;
	if (wback) {
		RzILOpBitVector *new_offset = use_rm ? ARG(rm_idx) : UN(32, elem_bytes * group_sz);
		wback_eff = write_reg(REGID(rn_idx), ADD(REG(rn_idx), new_offset));
	} else {
		wback_eff = EMPTY();
	}

	return SEQ2(eff, wback_eff);
}
#endif

static RzILOpEffect *vldn_all_lane(cs_insn *insn, bool is_thumb) {
	ut32 rm_idx = OPCOUNT() - 1;
	ut32 rn_idx;
	bool use_rm = false;
	ut32 regs; // number of regs in {list}

	if (!ISMEM(rm_idx)) {
		use_rm = true;
		regs = OPCOUNT() - 2;
	} else {
		rm_idx = -1;
		regs = OPCOUNT() - 1;
	}
	rn_idx = regs;

	ut32 group_sz = insn->id - ARM_INS_VLD1 + 1;
	if (group_sz != regs) {
		return NULL;
	}

	RzILOpBitVector *data0 = NULL, *data1 = NULL, *data2 = NULL, *data3 = NULL;
	RzILOpEffect *eff = NULL;
	RzILOpBitVector *addr = ARG(rn_idx);
	ut32 elem_bits = VVEC_SIZE(insn);
	ut32 elem_bytes = elem_bits / 8;
	ut32 addr_bits = REG_WIDTH(rn_idx);

	// vld1/vld2/vld3/vld4, max(lane_size) == 4 Bytes
	if (group_sz > 4 || elem_bytes > 4) {
		return NULL;
	}

	ut32 dreg_size = 64;
	switch (group_sz) {
	case 1:
		data0 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		eff = write_reg(REGID(0), DUP(data0));
		if (regs == 2) {
			eff = write_reg(REGID(1), data0);
		}
		break;
	case 2:
		data0 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data1 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		eff = SEQ2(write_reg(REGID(0), data0),
			write_reg(REGID(1), data1));
		break;
	case 3:
		data0 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data1 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data2 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		eff = SEQ3(write_reg(REGID(0), data0),
			write_reg(REGID(1), data1),
			write_reg(REGID(2), data2));
		break;
	case 4:
		data0 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data1 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data2 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		data3 = replicated_val(elem_bits, dreg_size, LOADW(elem_bits, addr));
		eff = SEQ4(write_reg(REGID(0), data0),
			write_reg(REGID(1), data1),
			write_reg(REGID(2), data2),
			write_reg(REGID(3), data3));
		break;
	default:
		rz_warn_if_reached();
		return NULL;
	}

	bool wback = insn->detail->arm.writeback;
	RzILOpEffect *wback_eff;
	if (wback) {
		RzILOpBitVector *new_offset = use_rm ? ARG(rm_idx) : UN(32, elem_bytes * group_sz);
		wback_eff = write_reg(REGID(rn_idx), ADD(REG(rn_idx), new_offset));
	} else {
		wback_eff = EMPTY();
	}

	return SEQ2(eff, wback_eff);
}

static RzILOpEffect *vldn(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2 || !ISREG(0)) {
		return NULL;
	}

#if CS_API_MAJOR > 3
	// to single lane
	if (NEON_LANE(0) != -1) {
		return vldn_single_lane(insn, is_thumb);
	}
#endif

	// TODO: capstone cannot distinguish details of the following instructions
	// vld3.8 {d0, d1, d2}, [r0] (f420040f)
	// vld3.8 {d0[], d1[], d2[]}, [r0] (f4a00e0f)
	bool all_lane = (insn->bytes[2] & 0x0C) == 0x0C;
	return all_lane ? vldn_all_lane(insn, is_thumb) : vldn_multiple_elem(insn, is_thumb);
}

static RzILOpEffect *vstn_multiple_elem(cs_insn *insn, bool is_thumb) {
	ut32 rm_idx = OPCOUNT() - 1;
	ut32 rn_idx;
	ut32 regs = 0;
	bool wback = insn->detail->arm.writeback;
	bool use_rm_as_wback_offset = false;
	ut32 group_sz = insn->id - ARM_INS_VST1 + 1;

	// vldn {list}, [Rn], Rm
	if (!ISMEM(rm_idx)) {
		regs = OPCOUNT() - 2;
		use_rm_as_wback_offset = true;
	} else {
		// vldn {list}, [Rn]
		rm_idx = -1;
		regs = OPCOUNT() - 1;
	}

	// mem_idx
	rn_idx = regs;

	// assert list_size % n == 0
	// assert they were all Dn
	ut32 n_groups = regs / group_sz;
	ut32 elem_bits = VVEC_SIZE(insn);
	ut32 elem_bytes = elem_bits / 8;
	ut32 lanes = 64 / elem_bits;
	ut32 addr_bits = REG_WIDTH(rn_idx);

	RzILOpEffect *wback_eff = NULL;
	RzILOpEffect *eff = EMPTY(), *eff_ = NULL, *eff__ = NULL;
	RzILOpBitVector *addr = ARG(rn_idx);

	for (int i = 0; i < n_groups; ++i) {
		for (int j = 0; j < lanes; ++j) {
			RzILOpBitVector *data0, *data1, *data2, *data3;
			ut32 vreg_idx = i * group_sz;
			switch (group_sz) {
			case 1:
				data0 = read_reg_lane(REGID(vreg_idx), j, elem_bits);
				eff = SEQ2(eff, STOREW(addr, data0));
				break;
			case 2:
				data0 = read_reg_lane(REGID(vreg_idx), j, elem_bits);
				data1 = read_reg_lane(REGID(vreg_idx + 1), j, elem_bits);
				eff = SEQ2(eff, STOREW(addr, data0));
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				eff = SEQ2(eff, STOREW(addr, data1));
				break;
			case 3:
				data0 = read_reg_lane(REGID(vreg_idx), j, elem_bits);
				data1 = read_reg_lane(REGID(vreg_idx + 1), j, elem_bits);
				data2 = read_reg_lane(REGID(vreg_idx + 2), j, elem_bits);
				eff = SEQ2(eff, STOREW(addr, data0));
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				eff_ = STOREW(addr, data1);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				eff = SEQ3(eff, eff_, STOREW(addr, data2));
				break;
			case 4:
				data0 = read_reg_lane(REGID(vreg_idx), j, elem_bits);
				data1 = read_reg_lane(REGID(vreg_idx + 1), j, elem_bits);
				data2 = read_reg_lane(REGID(vreg_idx + 2), j, elem_bits);
				data3 = read_reg_lane(REGID(vreg_idx + 3), j, elem_bits);
				eff = SEQ2(eff, STOREW(addr, data0));
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				eff_ = STOREW(addr, data1);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				eff__ = STOREW(addr, data2);
				addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
				eff = SEQ4(eff, eff_, eff__, STOREW(addr, data3));
				break;
			default:
				rz_warn_if_reached();
				return NULL;
			}
			addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		}
	}

	// free last address inc op
	rz_il_op_pure_free(addr);

	// update Rn
	// if write_back then Rn = Rn + (if use_rm then Rm else 8 * regs)
	if (wback) {
		RzILOpBitVector *new_offset = use_rm_as_wback_offset ? ARG(rm_idx) : UN(32, 8 * regs);
		wback_eff = write_reg(REGID(rn_idx), ADD(REG(rn_idx), new_offset));
	} else {
		wback_eff = EMPTY();
	}

	return SEQ2(eff, wback_eff);
}

#if CS_API_MAJOR > 3
static RzILOpEffect *vstn_from_single_lane(cs_insn *insn, bool is_thumb) {
	ut32 rm_idx = OPCOUNT() - 1;
	ut32 rn_idx;
	bool use_rm = false;
	ut32 regs; // number of regs in {list}

	if (!ISMEM(rm_idx)) {
		use_rm = true;
		regs = OPCOUNT() - 2;
	} else {
		rm_idx = -1;
		regs = OPCOUNT() - 1;
	}
	rn_idx = regs;

	ut32 group_sz = insn->id - ARM_INS_VST1 + 1;
	if (group_sz != regs) {
		return NULL;
	}

	RzILOpBitVector *data0, *data1, *data2, *data3;
	RzILOpEffect *eff, *eff_, *eff__;
	RzILOpBitVector *addr = ARG(rn_idx);
	ut32 vreg_idx = 0;
	ut32 elem_bits = VVEC_SIZE(insn);
	ut32 elem_bytes = elem_bits / 8;
	ut32 addr_bits = REG_WIDTH(rn_idx);

	if (group_sz > 4 || elem_bytes > 4) {
		return NULL;
	}

	unsigned char lane = NEON_LANE(0);
	switch (group_sz) {
	case 1:
		data0 = read_reg_lane(REGID(vreg_idx), lane, elem_bits);
		eff = STOREW(addr, data0);
		break;
	case 2:
		data0 = read_reg_lane(REGID(vreg_idx), lane, elem_bits);
		data1 = read_reg_lane(REGID(vreg_idx + 1), lane, elem_bits);
		eff = STOREW(addr, data0);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		eff = SEQ2(eff, STOREW(addr, data1));
		break;
	case 3:
		data0 = read_reg_lane(REGID(vreg_idx), lane, elem_bits);
		data1 = read_reg_lane(REGID(vreg_idx + 1), lane, elem_bits);
		data2 = read_reg_lane(REGID(vreg_idx + 2), lane, elem_bits);
		eff = STOREW(addr, data0);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		eff_ = STOREW(addr, data1);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		eff = SEQ3(eff, eff_, STOREW(addr, data2));
		break;
	case 4:
		data0 = read_reg_lane(REGID(vreg_idx), lane, elem_bits);
		data1 = read_reg_lane(REGID(vreg_idx + 1), lane, elem_bits);
		data2 = read_reg_lane(REGID(vreg_idx + 2), lane, elem_bits);
		data3 = read_reg_lane(REGID(vreg_idx + 3), lane, elem_bits);
		eff = STOREW(addr, data0);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		eff_ = STOREW(addr, data1);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		eff__ = STOREW(addr, data2);
		addr = ADD(DUP(addr), UN(addr_bits, elem_bytes));
		eff = SEQ4(eff, eff_, eff__, STOREW(addr, data3));
		break;
	default:
		rz_warn_if_reached();
		return NULL;
	}

	bool wback = insn->detail->arm.writeback;
	RzILOpEffect *wback_eff;
	if (wback) {
		RzILOpBitVector *new_offset = use_rm ? ARG(rm_idx) : UN(32, elem_bytes * group_sz);
		wback_eff = write_reg(REGID(rn_idx), ADD(REG(rn_idx), new_offset));
	} else {
		wback_eff = EMPTY();
	}

	return SEQ2(eff, wback_eff);
}
#endif

static RzILOpEffect *vstn(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2 || !ISREG(0)) {
		return NULL;
	}

#if CS_API_MAJOR > 3
	if (NEON_LANE(0) != -1) {
		return vstn_from_single_lane(insn, is_thumb);
	}
#endif

	return vstn_multiple_elem(insn, is_thumb);
}

static RzILOpEffect *try_as_float_cvt(cs_insn *insn, bool is_thumb, bool *success) {
	RzFloatFormat from_fmt, to_fmt;
	from_fmt = FROM_FMT(VVEC_DT(insn));
	to_fmt = TO_FMT(VVEC_DT(insn));
	if (from_fmt == RZ_FLOAT_UNK || to_fmt == RZ_FLOAT_UNK) {
		*success = false;
		return NULL;
	}

	// note that the ARM manual didn't specify rounding mode
	// VFP operation for single and double
	if (from_fmt == RZ_FLOAT_IEEE754_BIN_64 || to_fmt == RZ_FLOAT_IEEE754_BIN_64) {
		*success = true;
		return write_reg(REGID(0), F2BV(FCONVERT(to_fmt, RZ_FLOAT_RMODE_RNE, BV2F(from_fmt, REG(1)))));
	}

	// NEON vcvt for f16 and f32
	// Qn have 4 f32, Dn have 4 f16
	ut32 elem_n = 4;
	ut32 from_elem_sz = rz_float_get_format_info(from_fmt, RZ_FLOAT_INFO_TOTAL_LEN);
	ut32 to_elem_sz = rz_float_get_format_info(to_fmt, RZ_FLOAT_INFO_TOTAL_LEN);

	RzILOpEffect *eff = EMPTY();
	for (int i = 0; i < elem_n; ++i) {
		RzILOpFloat *from_val = BV2F(from_fmt, read_reg_lane(REGID(1), i, from_elem_sz));
		eff = SEQ2(eff,
			write_reg_lane(REGID(0), i, to_elem_sz,
				F2BV(FCONVERT(to_fmt, RZ_FLOAT_RMODE_RNE, from_val))));
	}

	*success = true;
	return eff;
}

static inline ut32 cvt_isize(arm_vectordata_type type, bool *is_signed) {
	switch (type) {
	case ARM_VECTORDATA_F32S32:
	case ARM_VECTORDATA_F64S32:
	case ARM_VECTORDATA_S32F32:
	case ARM_VECTORDATA_S32F64:
		*is_signed = true;
		return 32;
#if CS_API_MAJOR > 4
	case ARM_VECTORDATA_F16U32:
#endif
	case ARM_VECTORDATA_F32U32:
	case ARM_VECTORDATA_F64U32:
#if CS_API_MAJOR > 4
	case ARM_VECTORDATA_U32F16:
#endif
	case ARM_VECTORDATA_U32F32:
	case ARM_VECTORDATA_U32F64:
		*is_signed = false;
		return 32;
	case ARM_VECTORDATA_F32S16:
	case ARM_VECTORDATA_F64S16:
	case ARM_VECTORDATA_S16F32:
	case ARM_VECTORDATA_S16F64:
		*is_signed = true;
		return 16;
#if CS_API_MAJOR > 4
	case ARM_VECTORDATA_F16U16:
#endif
	case ARM_VECTORDATA_F32U16:
	case ARM_VECTORDATA_F64U16:
#if CS_API_MAJOR > 4
	case ARM_VECTORDATA_U16F16:
#endif
	case ARM_VECTORDATA_U16F32:
	case ARM_VECTORDATA_U16F64:
		*is_signed = false;
		return 16;
	default:
		rz_warn_if_reached();
		return 0;
	}
}

static RzILOpEffect *try_as_int_cvt(cs_insn *insn, bool is_thumb, bool *success) {
	bool is_f2i = false;
	bool is_signed = false;

	RzFloatFormat from_fmt = FROM_FMT(VVEC_DT(insn));
	RzFloatFormat to_fmt = TO_FMT(VVEC_DT(insn));
	ut32 bv_sz;
	if (from_fmt == RZ_FLOAT_UNK && to_fmt == RZ_FLOAT_UNK) {
		return NULL;
	}

	is_f2i = from_fmt == RZ_FLOAT_UNK ? false : true;
	bv_sz = cvt_isize(VVEC_DT(insn), &is_signed);
	ut32 fl_sz = rz_float_get_format_info(is_f2i ? from_fmt : to_fmt, RZ_FLOAT_INFO_TOTAL_LEN);

	if (insn->detail->groups[0] != ARM_GRP_NEON) {
		// vfp
		// VCVT.F64.S32/U32 <Dd>, <Sm>
		// VCVT.F32.S32/U32 <Sd>, <Sm>
		RzILOpBitVector *from_val;
		if (is_f2i) {
			from_val = is_signed ? F2SINT(bv_sz, RZ_FLOAT_RMODE_RTZ,
						       BV2F(from_fmt, REG(1)))
					     : F2INT(bv_sz, RZ_FLOAT_RMODE_RTZ,
						       BV2F(from_fmt, REG(1)));
		} else {
			from_val = is_signed ? F2BV(SINT2F(to_fmt, RZ_FLOAT_RMODE_RNE,
						       REG(1)))
					     : F2BV(INT2F(to_fmt, RZ_FLOAT_RMODE_RNE,
						       REG(1)));
		}

		return write_reg(REGID(0), from_val);
	}

	RzILOpEffect *eff = EMPTY();
	for (int i = 0; i < REG_WIDTH(0) / bv_sz; ++i) {
		RzILOpBitVector *from_val;
		if (is_f2i) {
			from_val = is_signed ? F2SINT(bv_sz, RZ_FLOAT_RMODE_RTZ,
						       BV2F(from_fmt,
							       read_reg_lane(REGID(1), i, fl_sz)))
					     : F2INT(bv_sz, RZ_FLOAT_RMODE_RTZ,
						       BV2F(from_fmt,
							       read_reg_lane(REGID(1), i, fl_sz)));
		} else {
			from_val = is_signed ? F2BV(SINT2F(to_fmt, RZ_FLOAT_RMODE_RNE,
						       read_reg_lane(REGID(1), i, bv_sz)))
					     : F2BV(INT2F(to_fmt, RZ_FLOAT_RMODE_RNE,
						       read_reg_lane(REGID(1), i, bv_sz)));
		}
		eff = SEQ2(eff, write_reg_lane(REGID(0), i, bv_sz, from_val));
	}

	return eff;
}

static RzILOpEffect *vcvt(cs_insn *insn, bool is_thumb) {
	if (VVEC_DT(insn) == ARM_VECTORDATA_INVALID || OPCOUNT() < 2) {
		return NULL;
	}

	bool success = false;
	RzILOpEffect *eff = NULL;
	// vcvt between floats (advanced SIMD and VFP)
	// F16 <-> F32 (NEON) , F32 <-> F64 (VFP)
	eff = try_as_float_cvt(insn, is_thumb, &success);
	if (success) {
		return eff;
	}

	// vcvt between integer and float
	eff = try_as_int_cvt(insn, is_thumb, &success);
	if (success) {
		return eff;
	}

	// vcvt between fix-point and float-point
	// currently could not find a way to process fixed point value
	return NULL;
}

#if CS_API_MAJOR > 3
static RzILOpEffect *vdup(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2) {
		return NULL;
	}

	ut32 elem_bits = VVEC_SIZE(insn);
	RzILOpEffect *eff = EMPTY();

	// 1. vdup <Vd> <Vn>[x], duplicate scalar
	// 2. vdup <Vd> <Rn>, duplicate Rn bits to Vd
	bool is_dup_lane = NEON_LANE(1) != -1;

	for (int i = 0; i < reg_bits(REGID(0)) / elem_bits; ++i) {
		RzILOpBitVector *scalar = is_dup_lane ? read_reg_lane(REGID(1), NEON_LANE(1), elem_bits) : UNSIGNED(elem_bits, REG(1));
		eff = SEQ2(eff,
			write_reg_lane(REGID(0), i, elem_bits, scalar));
	}

	return eff;
}
#endif

static RzILOpEffect *vext(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2) {
		rz_warn_if_reached();
		return NULL;
	}

	// vext.8 <Vm>, <Vn>, <Vd>, #imm
	// vext.16, vext.32 are pseudo instruction of vext.8
	// objdump disasm them to vext.8 <Vm>, <Vn>, <Vd>, #imm * x
	// but capstone parse it as .16 and .32
	ut32 vec_bits = VVEC_SIZE(insn);
	ut32 imm = get_imm(insn, OPCOUNT() - 1, NULL);

	// (vec_bits * imm < reg_bits(Vd)) === True, else invalid in capstone
	ut32 shift_dist = imm * vec_bits;
	if (shift_dist >= reg_bits(REGID(0))) {
		rz_warn_if_reached();
		return NULL;
	}

	// <Vm:Vn>(start_bits: start_bits+reg_bits(Vd))
	return write_reg(REGID(0),
		UNSIGNED(reg_bits(REGID(0)),
			SHIFTR0(APPEND(REG(2), REG(1)),
				UN(8, shift_dist))));
}

static RzILOpEffect *vzip(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2) {
		rz_warn_if_reached();
		return NULL;
	}

	if (REGID(0) == REGID(1)) {
		// UNKNOWN behavior
		rz_warn_if_reached();
		return EMPTY();
	}

	ut32 reg_sz = REG_WIDTH(0);
	ut32 vec_bits = VVEC_SIZE(insn);
	ut32 tmp_bits = reg_sz * 2;
	ut32 lanes = reg_sz / vec_bits;
	if (reg_sz % vec_bits != 0) {
		rz_warn_if_reached();
		return NULL;
	}

	// Assume Vd: A7, A6, A5, A4, A3, A2, A1, A0
	// Assume Vm: B7, B6, B5, B4, B3, B2, B1, B0
	// After interleave:
	// Vd: B3, A3, ... B1, A0
	// Vm: B7, A7, ... B4, A4
	RzILOpBitVector *interleaved_val = UN(tmp_bits, 0);
	for (ut32 i = 0; i < lanes; ++i) {
		RzILOpBitVector *d = UNSIGNED(tmp_bits, read_reg_lane(REGID(0), i, vec_bits));
		RzILOpBitVector *m = UNSIGNED(tmp_bits, read_reg_lane(REGID(1), i, vec_bits));
		interleaved_val = LOGOR(interleaved_val,
			SHIFTL0(LOGOR(SHIFTL0(m, UN(8, vec_bits)), d),
				UN(32, vec_bits * 2)));
	}

	return SEQ2(write_reg(REGID(0), UNSIGNED(reg_sz, DUP(interleaved_val))),
		write_reg(REGID(1), UNSIGNED(reg_sz, SHIFTR0(interleaved_val, UN(8, vec_bits)))));
}

static RzILOpEffect *vunzip(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2) {
		rz_warn_if_reached();
		return NULL;
	}

	if (REGID(0) == REGID(1)) {
		// UNKNOWN behavior
		rz_warn_if_reached();
		return EMPTY();
	}

	ut32 reg_sz = REG_WIDTH(0);
	ut32 vec_bits = VVEC_SIZE(insn);
	ut32 lanes = reg_sz / vec_bits;
	if (reg_sz % vec_bits != 0) {
		rz_warn_if_reached();
		return NULL;
	}

	// Assume Vd: A7, A6, A5, A4, A3, A2, A1, A0
	// Assume Vm: B7, B6, B5, B4, B3, B2, B1, B0
	// After interleave:
	// Vd: B6, B4, B2, B0, A6, A4, A2, A0 (even)
	// Vm: B7, B5, B3, B1, A7, A5, A3, A1 (odd)
	RzILOpBitVector *deinterleave_d = UN(reg_sz, 0);
	RzILOpBitVector *deinterleave_m = UN(reg_sz, 0);
	for (ut32 i = 0; i < lanes; ++i) {
		RzILOpBitVector *d_lane = UNSIGNED(reg_sz, read_reg_lane(REGID(0), i, vec_bits));
		RzILOpBitVector *m_lane = UNSIGNED(reg_sz, read_reg_lane(REGID(1), i, vec_bits));

		// construct (Bn, 0, 0, 0, An)
		ut32 lane_shift_dist = i / 2 * vec_bits;
		d_lane = SHIFTL0(d_lane, UN(8, lane_shift_dist));
		m_lane = SHIFTL0(SHIFTL0(m_lane, UN(8, lane_shift_dist)), UN(8, reg_sz / 2));

		if (i % 2 == 0) {
			// even
			deinterleave_d = LOGOR(deinterleave_d, LOGOR(d_lane, m_lane));
		} else {
			// odd
			deinterleave_m = LOGOR(deinterleave_m, LOGOR(d_lane, m_lane));
		}
	}

	return SEQ2(write_reg(REGID(0), deinterleave_d),
		write_reg(REGID(1), deinterleave_m));
}

static RzILOpEffect *vswp(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 2) {
		rz_warn_if_reached();
		return NULL;
	}

	if (REGID(0) == REGID(1)) {
		// UNKNOWN
		rz_warn_if_reached();
		return EMPTY();
	}

	RzILOpBitVector *d_val = REG(0);
	RzILOpBitVector *m_val = REG(1);
	return SEQ2(write_reg(REGID(0), m_val),
		write_reg(REGID(1), d_val));
}

static RzILOpEffect *vadd(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 3) {
		rz_warn_if_reached();
		return NULL;
	}

	// TODO: vaddhn, vaddl, vaddw
	// determine format of float to interpret
	arm_vectordata_type dt = VVEC_DT(insn);
	RzFloatFormat fmt = dt2fmt(dt);
	bool is_float_vec = fmt == RZ_FLOAT_UNK ? false : true;

	if (insn->detail->groups[0] != ARM_GRP_NEON) {
		// VFP
		return write_reg(REGID(0),
			F2BV(FADD(RZ_FLOAT_RMODE_RNE,
				BV2F(fmt, REG(1)),
				BV2F(fmt, REG(2)))));
	}

	ut32 elem_bits = DT_WIDTH(insn);
	ut32 lanes = reg_bits(REGID(0)) / elem_bits;
	if (reg_bits(REGID(0)) % elem_bits != 0) {
		rz_warn_if_reached();
		return NULL;
	}

	RzILOpEffect *eff = EMPTY();
	for (int i = 0; i < lanes; ++i) {
		RzILOpBitVector *a = read_reg_lane(REGID(1), i, elem_bits);
		RzILOpBitVector *b = read_reg_lane(REGID(2), i, elem_bits);

		RzILOpBitVector *sum = NULL;
		if (is_float_vec) {
			sum = F2BV(FADD(RZ_FLOAT_RMODE_RNE,
				BV2F(fmt, a),
				BV2F(fmt, b)));
		} else {
			sum = ADD(a, b);
		}

		eff = SEQ2(eff, write_reg_lane(REGID(0), i, elem_bits, sum));
	}

	return eff;
}

static RzILOpEffect *vsub(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 3) {
		rz_warn_if_reached();
		return NULL;
	}

	// TODO: vsubl, vsubw, vsubhn
	// determine format of float to interpret
	arm_vectordata_type dt = VVEC_DT(insn);
	RzFloatFormat fmt = dt2fmt(dt);
	bool is_float_vec = fmt == RZ_FLOAT_UNK ? false : true;

	if (insn->detail->groups[0] != ARM_GRP_NEON) {
		// VFP
		return write_reg(REGID(0),
			F2BV(FSUB(RZ_FLOAT_RMODE_RNE,
				BV2F(fmt, REG(1)),
				BV2F(fmt, REG(2)))));
	}

	ut32 elem_bits = DT_WIDTH(insn);
	ut32 lanes = reg_bits(REGID(0)) / elem_bits;
	if (reg_bits(REGID(0)) % elem_bits != 0) {
		rz_warn_if_reached();
		return NULL;
	}

	RzILOpEffect *eff = EMPTY();
	for (int i = 0; i < lanes; ++i) {
		RzILOpBitVector *a = read_reg_lane(REGID(1), i, elem_bits);
		RzILOpBitVector *b = read_reg_lane(REGID(2), i, elem_bits);

		RzILOpBitVector *sum = NULL;
		if (is_float_vec) {
			sum = F2BV(FSUB(RZ_FLOAT_RMODE_RNE,
				BV2F(fmt, a),
				BV2F(fmt, b)));
		} else {
			sum = SUB(a, b);
		}

		eff = SEQ2(eff, write_reg_lane(REGID(0), i, elem_bits, sum));
	}

	return eff;
}

static RzILOpEffect *vmul(cs_insn *insn, bool is_thumb) {
	if (OPCOUNT() < 3) {
		rz_warn_if_reached();
		return NULL;
	}

	// determine format of float to interpret
	arm_vectordata_type dt = VVEC_DT(insn);
	RzFloatFormat fmt = dt2fmt(dt);

	if (insn->detail->groups[0] != ARM_GRP_NEON) {
		// VFP fmul
		return write_reg(REGID(0),
			F2BV(FMUL(RZ_FLOAT_RMODE_RNE,
				BV2F(fmt, REG(1)),
				BV2F(fmt, REG(2)))));
	}

	// not implemented
	return EMPTY();
}

static RzILOpEffect *vldr(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISMEM(1)) {
		rz_warn_if_reached();
		return NULL;
	}

	RzILOpBitVector *addr;
	size_t mem_idx = 1;
	cs_arm_op *memop = &insn->detail->arm.operands[mem_idx];
	if (memop->mem.base == ARM_REG_PC) {
		// LDR (literal) is different in the sense that it aligns the pc value:
		addr = arg_mem(U32(PCALIGN(insn->address, is_thumb) + MEMDISP(mem_idx)), memop, NULL);
	} else {
		addr = ARG(mem_idx);
	}
	if (!addr) {
		return NULL;
	}

	RzILOpBitVector *data = LOADW(reg_bits(REGID(0)), addr);
	return write_reg(REGID(0), data);
}

static RzILOpEffect *vstr(cs_insn *insn, bool is_thumb) {
	if (!ISREG(0) || !ISMEM(1)) {
		rz_warn_if_reached();
		return NULL;
	}

	size_t mem_idx = 1;
	RzILOpBitVector *addr = ARG(mem_idx);
	if (!addr) {
		return NULL;
	}

	RzILOpBitVector *val = REG(0);
	if (!val) {
		rz_il_op_pure_free(addr);
		return NULL;
	}

	return STOREW(addr, val);
}

static RzILOpEffect *vcmp(cs_insn *insn, bool is_thumb) {
	// VFP only
	if (OPCOUNT() < 2) {
		rz_warn_if_reached();
		return NULL;
	}

	RzILOpFloat *l = NULL;
	RzILOpFloat *r = NULL;
	RzFloatFormat fmt = dt2fmt(VVEC_DT(insn));
	if (ISIMM(1)) {
		ut64 imm = get_imm(insn, 1, NULL);
		if (imm != 0) {
			// only #0 is allowed in vcmp
			rz_warn_if_reached();
			return NULL;
		}

		r = fmt == RZ_FLOAT_IEEE754_BIN_32 ? F32(0) : F64(0);
	} else {
		r = BV2F(fmt, REG(1));
	}
	l = BV2F(fmt, REG(0));

	// only NZCV flag will change, ignore carry and overflow for float
	RzILOpBool *is_neg = FORDER(DUP(l), DUP(r));
	RzILOpBool *is_zero = FEQ(l, r);
	RzILOpBitVector *res = LOGOR(
		SHIFTL0(BOOL_TO_BV(is_neg, 32), UN(8, 31)),
		SHIFTL0(BOOL_TO_BV(is_zero, 32), UN(8, 30)));

	return SETG("fpscr", res);
}

static RzILOpEffect *vabs(cs_insn *insn, bool is_thumb) {
	// implement vabs for VFP now.
	if (OPCOUNT() < 2) {
		rz_warn_if_reached();
		return NULL;
	}

	if (insn->detail->groups[0] == ARM_GRP_NEON) {
		// not implement
		return NULL;
	}

	RzFloatFormat fmt = dt2fmt(VVEC_DT(insn));
	RzILOpFloat *abs_val = FABS(BV2F(fmt, REG(1)));
	return write_reg(REGID(0), F2BV(abs_val));
}

/**
 * Lift an ARM instruction to RzIL, without considering its condition
 *
 * Currently unimplemented:
 * - BKPT: causes a breakpoint instruction exception
 * - CLREX: clears the local monitor
 * - CPS, CPSID, CPSIE: changes interrupt mask bits and optionally PSTATE.M
 * - CRC32, CRC32C: does crc32, new feature in armv8
 * - CSDB, DMB, DSB, ESB, ISB, PSSBB, SB, SSBB: synchronization, memory barriers
 * - DCPS1, DCPS2, DCPS3: for debuggers
 * - ERET: exception return
 * - HLT: software breakpoint
 * - LDC, MCR, MRC, MRRC, STC: coprocessor instructions
 * - SETEND: switches endianness, but it's out of control of the IL
 * - SETPAN: not supported by capstone
 * - SEV, SEVL: multiprocessor event
 * - SMC: secure monitor call
 * - SRS: depends on mode, unpredictable in user mode
 * - UDF: permanently undefined
 * - WFE, WFI: wait
 */
static RzILOpEffect *il_unconditional(csh *handle, cs_insn *insn, bool is_thumb) {
	switch (insn->id) {
	// --
	// Base Instruction Set
	case ARM_INS_DBG:
	case ARM_INS_NOP:
	case ARM_INS_PLD:
	case ARM_INS_PLDW:
	case ARM_INS_PLI:
	case ARM_INS_YIELD:
		// barriers/synchronization
	case ARM_INS_DMB:
	case ARM_INS_DSB:
	case ARM_INS_ISB:
		return NOP();
	case ARM_INS_B:
	case ARM_INS_BX:
	case ARM_INS_BXJ: {
		RzILOpBitVector *dst = ARG(0);
		return dst ? JMP(dst) : NULL;
	}
	case ARM_INS_BL:
	case ARM_INS_BLX:
		return bl(insn, is_thumb);
	case ARM_INS_MOV:
#if CS_API_MAJOR > 4
	case ARM_INS_MOVS:
#endif
	case ARM_INS_MOVW:
	case ARM_INS_LSL:
	case ARM_INS_LSR:
	case ARM_INS_ASR:
	case ARM_INS_RRX:
	case ARM_INS_ROR:
	case ARM_INS_MVN:
		return mov(insn, is_thumb);
	case ARM_INS_MOVT:
		return movt(insn, is_thumb);
	case ARM_INS_ADR:
		return adr(insn, is_thumb);
	case ARM_INS_ADD:
	case ARM_INS_ADDW:
	case ARM_INS_ADC:
	case ARM_INS_SUB:
	case ARM_INS_SUBW:
	case ARM_INS_RSB:
	case ARM_INS_RSC:
	case ARM_INS_SBC:
		return add_sub(insn, is_thumb);
	case ARM_INS_MUL:
		return mul(insn, is_thumb);
	case ARM_INS_LDR:
	case ARM_INS_LDREX:
	case ARM_INS_LDRB:
	case ARM_INS_LDRH:
	case ARM_INS_LDRT:
	case ARM_INS_LDRBT:
	case ARM_INS_LDRHT:
	case ARM_INS_LDA:
	case ARM_INS_LDAB:
	case ARM_INS_LDAH:
	case ARM_INS_LDAEX:
	case ARM_INS_LDAEXB:
	case ARM_INS_LDAEXH:
	case ARM_INS_LDRD:
	case ARM_INS_LDREXD:
	case ARM_INS_LDRSB:
	case ARM_INS_LDRSBT:
	case ARM_INS_LDRSH:
	case ARM_INS_LDRSHT:
		return ldr(insn, is_thumb);
	case ARM_INS_STR:
	case ARM_INS_STRB:
	case ARM_INS_STRH:
	case ARM_INS_STRT:
	case ARM_INS_STRBT:
	case ARM_INS_STRHT:
	case ARM_INS_STL:
	case ARM_INS_STLB:
	case ARM_INS_STLH:
	case ARM_INS_STRD:
		return str(insn, is_thumb);
	case ARM_INS_STREX:
	case ARM_INS_STREXB:
	case ARM_INS_STREXD:
	case ARM_INS_STREXH:
	case ARM_INS_STLEX:
	case ARM_INS_STLEXB:
	case ARM_INS_STLEXD:
	case ARM_INS_STLEXH:
		return strex(insn, is_thumb);
	case ARM_INS_AND:
	case ARM_INS_ORR:
	case ARM_INS_ORN:
	case ARM_INS_EOR:
	case ARM_INS_BIC:
		return bitwise(insn, is_thumb);
	case ARM_INS_TST:
	case ARM_INS_TEQ:
		return tst(insn, is_thumb);
	case ARM_INS_UXTB:
	case ARM_INS_UXTAB:
	case ARM_INS_UXTH:
	case ARM_INS_UXTAH:
	case ARM_INS_SXTB:
	case ARM_INS_SXTAB:
	case ARM_INS_SXTH:
	case ARM_INS_SXTAH:
		return uxt(insn, is_thumb);
	case ARM_INS_UXTB16:
	case ARM_INS_UXTAB16:
	case ARM_INS_SXTB16:
	case ARM_INS_SXTAB16:
		return uxt16(insn, is_thumb);
	case ARM_INS_CMP:
	case ARM_INS_CMN:
		return cmp(insn, is_thumb);
	case ARM_INS_STM:
	case ARM_INS_STMDA:
	case ARM_INS_STMDB:
	case ARM_INS_PUSH:
	case ARM_INS_STMIB:
		return stm(insn, is_thumb);
	case ARM_INS_POP:
	case ARM_INS_LDM:
	case ARM_INS_LDMDA:
	case ARM_INS_LDMDB:
	case ARM_INS_LDMIB:
		return ldm(insn, is_thumb);
	case ARM_INS_CLZ:
		return clz(insn, is_thumb);
	case ARM_INS_SVC:
		return svc(insn, is_thumb);
#if CS_API_MAJOR > 3
	case ARM_INS_HVC:
		return hvc(insn, is_thumb);
#endif
	case ARM_INS_BFC:
		return bfc(insn, is_thumb);
	case ARM_INS_BFI:
		return bfi(insn, is_thumb);
	case ARM_INS_CBZ:
	case ARM_INS_CBNZ:
		return cbz(insn, is_thumb);
	case ARM_INS_MLA:
	case ARM_INS_MLS:
		return mla(insn, is_thumb);
	case ARM_INS_MRS:
		return mrs(insn, is_thumb);
	case ARM_INS_MSR:
		return msr(insn, is_thumb);
	case ARM_INS_PKHBT:
	case ARM_INS_PKHTB:
		return pkhbt(insn, is_thumb);
	case ARM_INS_SSAT:
	case ARM_INS_USAT:
		return ssat(insn, is_thumb);
	case ARM_INS_SSAT16:
	case ARM_INS_USAT16:
		return ssat16(insn, is_thumb);
	case ARM_INS_QADD:
	case ARM_INS_QSUB:
	case ARM_INS_QDADD:
	case ARM_INS_QDSUB:
		return qadd(insn, is_thumb);
	case ARM_INS_QADD16:
	case ARM_INS_QSUB16:
	case ARM_INS_QASX:
	case ARM_INS_QSAX:
	case ARM_INS_UQADD16:
	case ARM_INS_UQSUB16:
	case ARM_INS_UQASX:
	case ARM_INS_UQSAX:
		return qadd16(insn, is_thumb);
	case ARM_INS_QADD8:
	case ARM_INS_QSUB8:
	case ARM_INS_UQADD8:
	case ARM_INS_UQSUB8:
		return qadd8(insn, is_thumb);
	case ARM_INS_RBIT:
		return rbit(insn, is_thumb);
	case ARM_INS_REV:
	case ARM_INS_REV16:
		return rev(insn, is_thumb);
	case ARM_INS_REVSH:
		return revsh(insn, is_thumb);
	case ARM_INS_RFEDA:
	case ARM_INS_RFEDB:
	case ARM_INS_RFEIA:
	case ARM_INS_RFEIB:
		return rfe(insn, is_thumb);
	case ARM_INS_SADD16:
	case ARM_INS_SHADD16:
	case ARM_INS_SASX:
	case ARM_INS_SSAX:
	case ARM_INS_SHASX:
	case ARM_INS_SHSAX:
	case ARM_INS_SSUB16:
	case ARM_INS_SHSUB16:
	case ARM_INS_UADD16:
	case ARM_INS_UHADD16:
	case ARM_INS_UASX:
	case ARM_INS_USAX:
	case ARM_INS_UHASX:
	case ARM_INS_UHSAX:
	case ARM_INS_USUB16:
	case ARM_INS_UHSUB16:
		return sadd16(insn, is_thumb);
	case ARM_INS_SADD8:
	case ARM_INS_SHADD8:
	case ARM_INS_SSUB8:
	case ARM_INS_SHSUB8:
	case ARM_INS_UADD8:
	case ARM_INS_UHADD8:
	case ARM_INS_USUB8:
	case ARM_INS_UHSUB8:
		return sadd8(insn, is_thumb);
	case ARM_INS_SEL:
		return sel(insn, is_thumb);
	case ARM_INS_SBFX:
	case ARM_INS_UBFX:
		return sbfx(insn, is_thumb);
	case ARM_INS_SDIV:
		return sdiv(insn, is_thumb);
	case ARM_INS_UDIV:
		return udiv(insn, is_thumb);
	case ARM_INS_UMAAL:
		return umaal(insn, is_thumb);
	case ARM_INS_UMULL:
		return umull(insn, is_thumb);
	case ARM_INS_USAD8:
	case ARM_INS_USADA8:
		return usad8(insn, is_thumb);
	case ARM_INS_SMLABB:
	case ARM_INS_SMLABT:
	case ARM_INS_SMLATB:
	case ARM_INS_SMLATT:
	case ARM_INS_SMLAD:
	case ARM_INS_SMLADX:
	case ARM_INS_SMLSD:
	case ARM_INS_SMLSDX:
		return smlabb(insn, is_thumb);
	case ARM_INS_SMLAL:
	case ARM_INS_SMLALBB:
	case ARM_INS_SMLALBT:
	case ARM_INS_SMLALTB:
	case ARM_INS_SMLALTT:
	case ARM_INS_SMLALD:
	case ARM_INS_SMLALDX:
	case ARM_INS_SMLSLD:
	case ARM_INS_SMLSLDX:
	case ARM_INS_UMLAL:
		return smlal(insn, is_thumb);
	case ARM_INS_SMLAWB:
	case ARM_INS_SMLAWT:
		return smlaw(insn, is_thumb);
	case ARM_INS_SMMLA:
	case ARM_INS_SMMLAR:
	case ARM_INS_SMMLS:
	case ARM_INS_SMMLSR:
		return smmla(insn, is_thumb);
	case ARM_INS_SMMUL:
	case ARM_INS_SMMULR:
		return smmul(insn, is_thumb);
	case ARM_INS_SMUAD:
	case ARM_INS_SMUADX:
		return smuad(insn, is_thumb);
	case ARM_INS_SMULBB:
	case ARM_INS_SMULBT:
	case ARM_INS_SMULTB:
	case ARM_INS_SMULTT:
	case ARM_INS_SMUSD:
	case ARM_INS_SMUSDX:
		return smulbb(insn, is_thumb);
	case ARM_INS_TBB:
	case ARM_INS_TBH:
		return tbb(insn, is_thumb);

	// --
	// Advanced SIMD and Floating-point
	case ARM_INS_VSTMIA:
	case ARM_INS_VSTMDB:
	case ARM_INS_VPUSH:
		return stm(insn, is_thumb);
	case ARM_INS_VLDMIA:
	case ARM_INS_VLDMDB:
	case ARM_INS_VPOP:
		return ldm(insn, is_thumb);
#if CS_API_MAJOR > 4
	case ARM_INS_VMOVL:
	case ARM_INS_VMOVN:
	case ARM_INS_VMOVX:
#endif
#if CS_API_MAJOR > 3
	case ARM_INS_VMOV:
	case ARM_INS_VMVN:
		return vmov(insn, is_thumb);
#endif
	case ARM_INS_VMSR:
		return vmsr(insn, is_thumb);
	case ARM_INS_VMRS:
		return vmrs(insn, is_thumb);
	// NEON (advanced SIMD)
	case ARM_INS_VAND:
	case ARM_INS_VBIC:
	case ARM_INS_VORR:
	case ARM_INS_VORN:
	case ARM_INS_VEOR:
		return vbitwise(insn, is_thumb);
	case ARM_INS_VBIT:
	case ARM_INS_VBIF:
	case ARM_INS_VBSL:
		return vbit_insert(insn, is_thumb);
	case ARM_INS_VACGT:
	case ARM_INS_VACGE:
	case ARM_INS_VCEQ:
	case ARM_INS_VCGE:
	case ARM_INS_VCGT:
	case ARM_INS_VCLE:
	case ARM_INS_VCLT:
		return vec_cmp(insn, is_thumb);
	case ARM_INS_VTST:
		return vtst(insn, is_thumb);
	case ARM_INS_VLD1:
	case ARM_INS_VLD2:
	case ARM_INS_VLD3:
	case ARM_INS_VLD4:
		return vldn(insn, is_thumb);
	case ARM_INS_VST1:
	case ARM_INS_VST2:
	case ARM_INS_VST3:
	case ARM_INS_VST4:
		return vstn(insn, is_thumb);
	case ARM_INS_VCVT:
#if CS_API_MAJOR > 4
	case ARM_INS_VCVTA:
	case ARM_INS_VCVTB:
	case ARM_INS_VCVTM:
	case ARM_INS_VCVTN:
	case ARM_INS_VCVTP:
	case ARM_INS_VCVTR:
	case ARM_INS_VCVTT:
#endif
		return vcvt(insn, is_thumb);
#if CS_API_MAJOR > 3
	case ARM_INS_VDUP:
		return vdup(insn, is_thumb);
#endif
	case ARM_INS_VEXT:
		return vext(insn, is_thumb);
	case ARM_INS_VZIP:
		return vzip(insn, is_thumb);
	case ARM_INS_VUZP:
		return vunzip(insn, is_thumb);
	case ARM_INS_VSWP:
		return vswp(insn, is_thumb);
	case ARM_INS_VADD:
		return vadd(insn, is_thumb);
	case ARM_INS_VSUB:
		return vsub(insn, is_thumb);
	case ARM_INS_VMUL:
		return vmul(insn, is_thumb);
	case ARM_INS_VLDR:
		return vldr(insn, is_thumb);
	case ARM_INS_VSTR:
		return vstr(insn, is_thumb);
	case ARM_INS_VABS:
		return vabs(insn, is_thumb);
	case ARM_INS_VCMP:
		return vcmp(insn, is_thumb);
	default:
		return NULL;
	}
}

RZ_IPI RzILOpEffect *rz_arm_cs_32_il(csh *handle, cs_insn *insn, bool thumb) {
	if (insn->id == ARM_INS_IT) {
		// Note: IT is **not** a conditional branch!
		// It's currently handled in analysis_arm_cs.c using ArmCSContext as a hack to turn the following instructions
		// into conditional ones. So in the IL, we don't do anything for IT.
		return NOP();
	}
	RzILOpEffect *eff = il_unconditional(handle, insn, thumb);
	if (!eff) {
		return NULL;
	}
	RzILOpBool *c = cond(insn->detail->arm.cc);
	if (c) {
		return BRANCH(c, eff, NOP());
	}
	return eff;
}

#include <rz_il/rz_il_opbuilder_end.h>

RZ_IPI RzAnalysisILConfig *rz_arm_cs_32_il_config(bool big_endian) {
	RzAnalysisILConfig *r = rz_analysis_il_config_new(32, big_endian, 32);
	r->reg_bindings = regs_bound_32;
	RzILEffectLabel *svc_label = rz_il_effect_label_new("svc", EFFECT_LABEL_SYSCALL);
	svc_label->hook = label_svc;
	rz_analysis_il_config_add_label(r, svc_label);
	RzILEffectLabel *hvc_label = rz_il_effect_label_new("hvc", EFFECT_LABEL_SYSCALL);
	hvc_label->hook = label_hvc;
	rz_analysis_il_config_add_label(r, hvc_label);
	return r;
}
