#include <stdarg.h>
#include "binaryninjaapi.h"
#include "lowlevelilinstruction.h"
#include "il.h"
#include "spec.h"
#include "disassembler.h"

using namespace BinaryNinja;
using namespace armv7;

bool GetLowLevelILForNEONInstruction(Architecture* arch, LowLevelILFunction& il, decomp_result* instr, bool ifThenBlock);

static uint32_t GetRegisterByIndex(uint32_t i, const char* prefix = "")
{
	if (strcmp(prefix, "s") == 0)
		return REG_R0 + (i/2) + (i%2) + REG_S0;
	if (strcmp(prefix, "d") == 0)
		return REG_R0 + (i/2) + (i%2) + REG_D0;
	if (strcmp(prefix, "q") == 0)
		return REG_R0 + (i/2) + (i%2) + REG_Q0;
	return REG_R0 + i;
}

static ExprId ReadRegister(LowLevelILFunction& il, decomp_result* instr, uint32_t reg, size_t size = 4, const char* prefix = "")
{
	if (reg == armv7::REG_PC)
		return il.ConstPointer(size, instr->pc);
	if (strcmp(prefix, "s") == 0)
		size = 4;
	if (strcmp(prefix, "d") == 0)
		size = 8;
	if (strcmp(prefix, "q") == 0)
		size = 16;
	return il.Register(size, GetRegisterByIndex(reg, prefix));
}

static ExprId ReadILOperand(LowLevelILFunction& il, decomp_result* instr, size_t operand, size_t size = 4)
{
	uint32_t value;
	uint64_t imm64;
	switch (instr->format->operands[operand].type)
	{
	case OPERAND_FORMAT_IMM64:
		imm64 = instr->fields[FIELD_imm64h];
		imm64 <<= 32;
		imm64 |= instr->fields[FIELD_imm64l];
		return il.Const(8, imm64);
	case OPERAND_FORMAT_IMM:
	case OPERAND_FORMAT_OPTIONAL_IMM:
		value = instr->fields[instr->format->operands[operand].field0];
		if ((instr->mnem == armv7::ARMV7_B) || (instr->mnem == armv7::ARMV7_BL) ||
			(instr->mnem == armv7::ARMV7_CBNZ) || (instr->mnem == armv7::ARMV7_CBZ)) {
			value += instr->pc;
			return il.ConstPointer(size, value);
		} else if ((instr->mnem == armv7::ARMV7_BX) || (instr->mnem == armv7::ARMV7_BLX)) {
			value += instr->pc & (~3);
			return il.ConstPointer(size, value);
		}
		return il.Const(size, value);
	case OPERAND_FORMAT_ADD_IMM:
	case OPERAND_FORMAT_OPTIONAL_ADD_IMM:
		value = instr->fields[instr->format->operands[operand].field0];
		if (instr->fields[FIELD_add])
			return il.Const(4, value);
		return il.Const(size, -(int64_t)value);
	case OPERAND_FORMAT_ZERO:
		return il.Const(size, 0);
	case OPERAND_FORMAT_REG:
		value = instr->fields[instr->format->operands[operand].field0];
		return ReadRegister(il, instr, GetRegisterByIndex(value), size);
	case OPERAND_FORMAT_REG_FP:
		value = instr->fields[instr->format->operands[operand].field0];
		return ReadRegister(il, instr, value, size, instr->format->operands[operand].prefix);
	case OPERAND_FORMAT_SP:
		return il.Register(size, armv7::REG_SP);
	case OPERAND_FORMAT_LR:
		return il.Register(size, armv7::REG_LR);
	case OPERAND_FORMAT_PC:
		return il.ConstPointer(size, instr->pc);
	default:
		return il.Unimplemented();
	}
}

static uint32_t GetRegisterSize(decomp_result* instr, size_t operand)
{
	if (strcmp(instr->format->operands[operand].prefix, "q") == 0)
		return 16;
	if (strcmp(instr->format->operands[operand].prefix, "d") == 0)
		return 8;
	if (strcmp(instr->format->operands[operand].prefix, "s") == 0)
		return 4;
	return 4;
}

static ExprId ReadShiftedOperand(LowLevelILFunction& il, decomp_result* instr, size_t operand, size_t size = 4)
{
	uint32_t shift_t = instr->fields[FIELD_shift_t];
	uint32_t shift_n = instr->fields[FIELD_shift_n];
	ExprId value = ReadILOperand(il, instr, operand, size);

	if(shift_n == 0)
		return value;

	switch (shift_t)
	{
	case SRType_LSL:
		return il.ShiftLeft(size, value, il.Const(4, shift_n));
	case SRType_LSR:
		return il.LogicalShiftRight(size, value, il.Const(4, shift_n));
	case SRType_ASR:
		return il.ArithShiftRight(size, value, il.Const(4, shift_n));
	case SRType_RRX:
		return il.RotateRightCarry(size, value, il.Const(4, 1), il.Flag(IL_FLAG_C));
	case SRType_ROR:
		return il.RotateRight(size, value, il.Const(4, shift_n));
	default:
		return value;
	}
}

static ExprId ReadRotatedOperand(LowLevelILFunction& il, decomp_result *instr, size_t operand, size_t size = 4)
{
	uint32_t rot_n = instr->fields[FIELD_rotation];
	ExprId value = ReadILOperand(il, instr, operand, size);

	if (IS_FIELD_PRESENT(instr, FIELD_rotation) && 0 != rot_n) {
		return il.RotateRight(size, value, il.Const(4, rot_n));
	}

	return value;
}

static ExprId ReadArithOperand(LowLevelILFunction& il, decomp_result* instr, size_t operand, size_t size = 4)
{
	if (operand == 0)
	{
		if (instr->format->operandCount == 2)
			return ReadILOperand(il, instr, 0, size);
		if ((instr->format->operandCount == 3) && (instr->format->operands[2].type == OPERAND_FORMAT_SHIFT))
			return ReadILOperand(il, instr, 0, size);
		return ReadILOperand(il, instr, 1, size);
	}

	if (instr->format->operandCount == 2)
		return ReadILOperand(il, instr, 1, size);
	if (instr->format->operandCount == 3)
	{
		if (instr->format->operands[2].type != OPERAND_FORMAT_SHIFT)
			return ReadILOperand(il, instr, 2, size);
		return ReadShiftedOperand(il, instr, 1, size);
	}
	return ReadShiftedOperand(il, instr, 2, size);
}


static ExprId WriteILOperand(LowLevelILFunction& il, decomp_result* instr, size_t operand, ExprId value,
	size_t size = 4, uint32_t flags = 0)
{
	uint32_t reg;
	switch (instr->format->operands[operand].type)
	{
	case OPERAND_FORMAT_REG:
		reg = instr->fields[instr->format->operands[operand].field0];
		if (reg == 15)
			return il.Jump(value);
		return il.SetRegister(size, GetRegisterByIndex(reg), value, flags);
	case OPERAND_FORMAT_REG_FP:
		reg = instr->fields[instr->format->operands[operand].field0];
		size = GetRegisterSize(instr, operand);
		return il.SetRegister(size, GetRegisterByIndex(reg, instr->format->operands[operand].prefix), value, flags);
	case OPERAND_FORMAT_SP:
		return il.SetRegister(size, armv7::REG_SP, value, flags);
	case OPERAND_FORMAT_LR:
		return il.SetRegister(size, armv7::REG_LR, value, flags);
	case OPERAND_FORMAT_PC:
		return il.Jump(value);
	default:
		return il.Unimplemented();
	}
}


static ExprId WriteArithOperand(LowLevelILFunction& il, decomp_result* instr, ExprId value, size_t size = 4,
	uint32_t flags = 0)
{
	return WriteILOperand(il, instr, 0, value, size, flags);
}


static ExprId WriteSplitOperands(LowLevelILFunction& il, decomp_result *instr, size_t operandHi, size_t operandLo, ExprId value,
	size_t size = 4, uint32_t flags = 0)
{
	uint32_t regHi = instr->fields[instr->format->operands[operandHi].field0];
	uint32_t regLo = instr->fields[instr->format->operands[operandLo].field0];

	return il.SetRegisterSplit(size, GetRegisterByIndex(regHi), GetRegisterByIndex(regLo), value, flags);
}


static bool HasWriteback(decomp_result* instr, size_t operand)
{
	switch (instr->format->operands[operand].writeback)
	{
	case WRITEBACK_YES:
		return true;
	case WRITEBACK_OPTIONAL:
		return thumb_has_writeback(instr);
	default:
		return false;
	}
}


static ExprId ShiftedRegister(LowLevelILFunction& il, decomp_result* instr, uint32_t reg, uint32_t t, uint32_t n)
{
	if (n == 0)
		return il.Register(4, reg);
	switch (t)
	{
	case SRType_LSL:
		return il.ShiftLeft(4, ReadRegister(il, instr, reg), il.Const(4, n));
	case SRType_LSR:
		return il.LogicalShiftRight(4, ReadRegister(il, instr, reg), il.Const(4, n));
	case SRType_ASR:
		return il.ArithShiftRight(4, ReadRegister(il, instr, reg), il.Const(4, n));
	case SRType_ROR:
		return il.RotateRight(4, ReadRegister(il, instr, reg), il.Const(4, n));
	case SRType_RRX:
		return il.RotateRightCarry(4, ReadRegister(il, instr, reg), il.Const(4, 1), il.Flag(IL_FLAG_C));
	default:
		return il.Unimplemented();
	}
}


static ExprId GetMemoryAddress(LowLevelILFunction& il, decomp_result* instr, size_t operand, uint32_t size,
	bool canWriteback = true)
{
	uint32_t reg, second, t, n;
	switch (instr->format->operands[operand].type)
	{
	case OPERAND_FORMAT_MEMORY_ONE_REG:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		return il.Register(4, reg);
	case OPERAND_FORMAT_MEMORY_ONE_REG_IMM:
	case OPERAND_FORMAT_MEMORY_ONE_REG_OPTIONAL_IMM:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		second = instr->fields[instr->format->operands[operand].field1];
		if (canWriteback && HasWriteback(instr, operand))
		{
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, ReadRegister(il, instr, reg), il.Const(4, second))));
			return il.Register(4, reg);
		}
		return il.Add(4, ReadRegister(il, instr, reg), il.Const(4, second));
	case OPERAND_FORMAT_MEMORY_ONE_REG_NEG_IMM:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		second = instr->fields[instr->format->operands[operand].field1];
		if (canWriteback && HasWriteback(instr, operand))
		{
			il.AddInstruction(il.SetRegister(4, reg, il.Sub(4, ReadRegister(il, instr, reg), il.Const(4, second))));
			return il.Register(4, reg);
		}
		return il.Sub(4, ReadRegister(il, instr, reg), il.Const(4, second));
	case OPERAND_FORMAT_MEMORY_ONE_REG_ADD_IMM:
	case OPERAND_FORMAT_MEMORY_ONE_REG_OPTIONAL_ADD_IMM:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		second = instr->fields[instr->format->operands[operand].field1];
		if (canWriteback && HasWriteback(instr, operand))
		{
			if (instr->fields[FIELD_add])
				il.AddInstruction(il.SetRegister(4, reg, il.Add(4, ReadRegister(il, instr, reg), il.Const(4, second))));
			else
				il.AddInstruction(il.SetRegister(4, reg, il.Sub(4, ReadRegister(il, instr, reg), il.Const(4, second))));
			return il.Register(4, reg);
		}
		if (instr->fields[FIELD_add])
			return il.Add(4, ReadRegister(il, instr, reg), il.Const(4, second));
		return il.Sub(4, ReadRegister(il, instr, reg), il.Const(4, second));
	case OPERAND_FORMAT_MEMORY_TWO_REG:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		second = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field1]);
		if (canWriteback && HasWriteback(instr, operand))
		{
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, ReadRegister(il, instr, reg), il.Register(4, second))));
			return il.Register(4, reg);
		}
		return il.Add(4, ReadRegister(il, instr, reg), il.Register(4, second));
	case OPERAND_FORMAT_MEMORY_TWO_REG_SHIFT:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		second = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field1]);
		t = instr->fields[FIELD_shift_t];
		n = instr->fields[FIELD_shift_n];
		if (canWriteback && HasWriteback(instr, operand))
		{
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, ReadRegister(il, instr, reg),
				ShiftedRegister(il, instr, second, t, n))));
			return il.Register(4, reg);
		}
		return il.Add(4, ReadRegister(il, instr, reg), ShiftedRegister(il, instr, second, t, n));
	case OPERAND_FORMAT_MEMORY_TWO_REG_LSL_ONE:
		reg = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field0]);
		second = GetRegisterByIndex(instr->fields[instr->format->operands[operand].field1]);
		if (canWriteback && HasWriteback(instr, operand))
		{
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, ReadRegister(il, instr, reg),
				il.ShiftLeft(4, ReadRegister(il, instr, second), il.Const(4, 1)))));
			return il.Register(4, reg);
		}
		return il.Add(4, ReadRegister(il, instr, reg), il.ShiftLeft(4, ReadRegister(il, instr, second), il.Const(4, 1)));
	case OPERAND_FORMAT_MEMORY_SP_IMM:
	case OPERAND_FORMAT_MEMORY_SP_OPTIONAL_IMM:
		second = instr->fields[instr->format->operands[operand].field0];
		if (canWriteback && HasWriteback(instr, operand))
		{
			il.AddInstruction(il.SetRegister(4, armv7::REG_SP, il.Add(4, il.Register(4, armv7::REG_SP), il.Const(4, second))));
			return il.Register(4, armv7::REG_SP);
		}
		return il.Add(4, il.Register(4, armv7::REG_SP), il.Const(4, second));
	case OPERAND_FORMAT_MEMORY_PC:
		return il.ConstPointer(4, instr->pc);
	case OPERAND_FORMAT_LABEL:
		if (instr->fields[FIELD_add])
			return il.ConstPointer(4, (instr->pc + instr->fields[FIELD_imm32]) & (~(size - 1)));
		return il.ConstPointer(4, (instr->pc - instr->fields[FIELD_imm32]) & (~(size - 1)));
	default:
		return il.Unimplemented();
	}
}


static ExprId GetCondition(LowLevelILFunction& il, uint32_t cond)
{
	switch(cond)
	{
	 	case armv7::COND_EQ: return il.FlagCondition(LLFC_E);
	 	case armv7::COND_NE: return il.FlagCondition(LLFC_NE);
	 	case armv7::COND_CS: return il.FlagCondition(LLFC_UGE);
	 	case armv7::COND_CC: return il.FlagCondition(LLFC_ULT);
	 	case armv7::COND_MI: return il.FlagCondition(LLFC_NEG);
	 	case armv7::COND_PL: return il.FlagCondition(LLFC_POS);
	 	case armv7::COND_VS: return il.FlagCondition(LLFC_O);
	 	case armv7::COND_VC: return il.FlagCondition(LLFC_NO);
	 	case armv7::COND_HI: return il.FlagCondition(LLFC_UGT);
	 	case armv7::COND_LS: return il.FlagCondition(LLFC_ULE);
	 	case armv7::COND_GE: return il.FlagCondition(LLFC_SGE);
	 	case armv7::COND_LT: return il.FlagCondition(LLFC_SLT);
	 	case armv7::COND_GT: return il.FlagCondition(LLFC_SGT);
	 	case armv7::COND_LE: return il.FlagCondition(LLFC_SLE);
	 	case armv7::COND_NONE: return il.Const(0, 1); //Always branch
		default:
			return il.Const(0, 0); //Never branch
	}
}


static void ConditionalJump(Architecture* arch, LowLevelILFunction& il, uint32_t cond, uint32_t t, uint32_t f)
{
	BNLowLevelILLabel* trueLabel = il.GetLabelForAddress(arch, t);
	BNLowLevelILLabel* falseLabel = il.GetLabelForAddress(arch, f);

	if (trueLabel && falseLabel)
	{
		il.AddInstruction(il.If(GetCondition(il, cond), *trueLabel, *falseLabel));
		return;
	}

	LowLevelILLabel trueCode, falseCode;

	if (trueLabel)
	{
		il.AddInstruction(il.If(GetCondition(il, cond), *trueLabel, falseCode));
		il.MarkLabel(falseCode);
		il.AddInstruction(il.Jump(il.ConstPointer(4, f)));
		return;
	}

	if (falseLabel)
	{
		il.AddInstruction(il.If(GetCondition(il, cond), trueCode, *falseLabel));
		il.MarkLabel(trueCode);
		il.AddInstruction(il.Jump(il.ConstPointer(4, t)));
		return;
	}

	il.AddInstruction(il.If(GetCondition(il, cond), trueCode, falseCode));
	il.MarkLabel(trueCode);
	il.AddInstruction(il.Jump(il.ConstPointer(4, t)));
	il.MarkLabel(falseCode);
	il.AddInstruction(il.Jump(il.ConstPointer(4, f)));
}


static void CompareWithZeroAndConditionalJump(Architecture* arch, LowLevelILFunction& il, uint32_t reg,
	BNLowLevelILOperation cond, uint32_t t, uint32_t f)
{
	BNLowLevelILLabel* trueLabel = il.GetLabelForAddress(arch, t);
	BNLowLevelILLabel* falseLabel = il.GetLabelForAddress(arch, f);
	ExprId condExpr = il.AddExpr(cond, 4, 0, il.Register(4, GetRegisterByIndex(reg)), il.Const(4, 0));

	if (trueLabel && falseLabel)
	{
		il.AddInstruction(il.If(condExpr, *trueLabel, *falseLabel));
		return;
	}

	LowLevelILLabel trueCode, falseCode;

	if (trueLabel)
	{
		il.AddInstruction(il.If(condExpr, *trueLabel, falseCode));
		il.MarkLabel(falseCode);
		il.AddInstruction(il.Jump(il.ConstPointer(4, f)));
		return;
	}

	if (falseLabel)
	{
		il.AddInstruction(il.If(condExpr, trueCode, *falseLabel));
		il.MarkLabel(trueCode);
		il.AddInstruction(il.Jump(il.ConstPointer(4, t)));
		return;
	}

	il.AddInstruction(il.If(condExpr, trueCode, falseCode));
	il.MarkLabel(trueCode);
	il.AddInstruction(il.Jump(il.ConstPointer(4, t)));
	il.MarkLabel(falseCode);
	il.AddInstruction(il.Jump(il.ConstPointer(4, f)));
}


void SetupThumbConditionalInstructionIL(LowLevelILFunction& il, LowLevelILLabel& trueLabel,
	LowLevelILLabel& falseLabel, uint32_t cond)
{
	il.AddInstruction(il.If(GetCondition(il, cond), trueLabel, falseLabel));
}


static void Push(LowLevelILFunction& il, uint32_t regs)
{
	for (int32_t i = 15; i >= 0; i--)
	{
		if (((regs >> i) & 1) == 1)
		{
			il.AddInstruction(il.Push(4, il.Register(4, GetRegisterByIndex(i))));
		}
	}
}


static void Pop(LowLevelILFunction& il, uint32_t regs)
{
	for (int32_t i = 0; i <= 15; i++)
	{
		if (((regs >> i) & 1) == 1)
		{
			if (i == 15)
				il.AddInstruction(il.Return(il.Pop(4)));
			else
				il.AddInstruction(il.SetRegister(4, GetRegisterByIndex(i), il.Pop(4)));
		}
	}
}


static bool WritesToStatus(decomp_result* instr, bool ifThenBlock)
{
	if (ifThenBlock)
		return false;
	if (instr->format->operationFlags & INSTR_FORMAT_FLAG_OPTIONAL_STATUS)
	{
		if(IS_FIELD_PRESENT(instr, FIELD_S))
		{
			if (instr->fields[FIELD_S])
				return true;
		}
	}
	return false;
}

static bool IsPCRelativeDataAddress(decomp_result* instr, bool ifThenBlock)
{
	if ((instr->format->operandCount == 3) && (instr->format->operands[1].type == OPERAND_FORMAT_PC) &&
			(instr->format->operands[2].type == OPERAND_FORMAT_IMM) && !WritesToStatus(instr, ifThenBlock))
		return true;

	return false;
}


bool GetLowLevelILForThumbInstruction(Architecture* arch, LowLevelILFunction& il, decomp_result* instr, bool ifThenBlock)
{
	if ((instr->status & STATUS_UNDEFINED) || (!instr->format))
		return false;

	switch (instr->mnem)
	{
	case armv7::ARMV7_ADC:
			il.AddInstruction(WriteArithOperand(il, instr, il.AddCarry(4, ReadArithOperand(il, instr, 0),
				ReadArithOperand(il, instr, 1), il.Flag(IL_FLAG_C), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_ADCS:
			il.AddInstruction(WriteArithOperand(il, instr, il.AddCarry(4, ReadArithOperand(il, instr, 0),
				ReadArithOperand(il, instr, 1), il.Flag(IL_FLAG_C), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_ADD:
	case armv7::ARMV7_ADDW:
		if (IsPCRelativeDataAddress(instr, ifThenBlock))
			il.AddInstruction(WriteArithOperand(il, instr, il.Add(4, il.And(4, ReadILOperand(il, instr, 1, 4), il.Const(4, ~3)),
				ReadILOperand(il, instr, 2, 4))));
		else
			il.AddInstruction(WriteArithOperand(il, instr, il.Add(4, ReadArithOperand(il, instr, 0),
				ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_ADDS:
		il.AddInstruction(WriteArithOperand(il, instr, il.Add(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_ADR:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.ConstPointer(4, (instr->pc + instr->fields[
			instr->format->operands[1].field0]) & (~3))));
		break;
	case armv7::ARMV7_AND:
		il.AddInstruction(WriteArithOperand(il, instr, il.And(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_ANDS:
		il.AddInstruction(WriteArithOperand(il, instr, il.And(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_ASR:
		il.AddInstruction(WriteArithOperand(il, instr, il.ArithShiftRight(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_ASRS:
		il.AddInstruction(WriteArithOperand(il, instr, il.ArithShiftRight(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_B:
		if ((!(instr->format->operationFlags & INSTR_FORMAT_FLAG_CONDITIONAL)) ||
			(instr->fields[FIELD_cond] == COND_AL))
		{
			uint32_t dest = instr->pc + instr->fields[instr->format->operands[0].field0];
			BNLowLevelILLabel* label = il.GetLabelForAddress(arch, dest);
			if (label)
				il.AddInstruction(il.Goto(*label));
			else
				il.AddInstruction(il.Jump(il.ConstPointer(4, dest)));
		}
		else
		{
			uint32_t t = instr->pc + instr->fields[instr->format->operands[0].field0];
			uint32_t f = (instr->pc - 4) + (instr->instrSize / 8);
			ConditionalJump(arch, il, instr->fields[FIELD_cond], t, f);
		}
		break;
	case armv7::ARMV7_BFC:
		{
			uint32_t lsb = instr->fields[instr->format->operands[1].field0];
			uint32_t clear_width = instr->fields[instr->format->operands[2].field0];
			uint32_t mask = ((1 << clear_width) - 1) << lsb;
			il.AddInstruction(WriteILOperand(il, instr, 0,
						il.And(4,
							ReadILOperand(il, instr, 0),
							il.Const(4, ~mask))));
		}
		break;
	case armv7::ARMV7_BFI:
	{
		uint32_t width_mask;
		uint32_t mask;
		uint32_t lsb;
		uint32_t width;

		width = instr->fields[instr->format->operands[3].field0];
		lsb = instr->fields[instr->format->operands[2].field0];
		width_mask = (1 << width) - 1;
		mask = width_mask << lsb;
		//bit field insert: op1 = (op1 & (~(<width_mask> << lsb))) | ((op2 & <width_mask>) << lsb)
		il.AddInstruction(WriteILOperand(il, instr, 0,
			il.Or(4,
				il.And(4, ReadILOperand(il, instr, 0), il.Const(4, ~mask)),
			il.ShiftLeft(4,
				il.And(4, ReadILOperand(il, instr, 1), il.Const(4, width_mask)),
				il.Const(4, lsb)))));
		break;
	}
	case armv7::ARMV7_BIC:
		il.AddInstruction(WriteArithOperand(il, instr, il.And(4, ReadArithOperand(il, instr, 0),
			il.Not(4, ReadArithOperand(il, instr, 1)), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_BICS:
		il.AddInstruction(WriteArithOperand(il, instr, il.And(4, ReadArithOperand(il, instr, 0),
			il.Not(4, ReadArithOperand(il, instr, 1)), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_BKPT:
		il.AddInstruction(il.Breakpoint());
		break;
	case armv7::ARMV7_BL:
	case armv7::ARMV7_BLX:
		il.AddInstruction(il.Call(ReadILOperand(il, instr, 0)));
		break;
	case armv7::ARMV7_BX:
		if ((instr->format->operands[0].type == OPERAND_FORMAT_LR) ||
			(instr->fields[instr->format->operands[0].field0] == 14))
		{
			il.AddInstruction(il.Return(il.Register(4, armv7::REG_LR)));
		}
		else
		{
			il.AddInstruction(il.Jump(ReadRegister(il, instr, GetRegisterByIndex(instr->fields[instr->format->operands[0].field0]), 4)));
		}
		break;
	case armv7::ARMV7_CBNZ:
		CompareWithZeroAndConditionalJump(arch, il, instr->fields[instr->format->operands[0].field0], LLIL_CMP_NE,
			(instr->pc - 4) + instr->fields[instr->format->operands[1].field0], (instr->pc - 4) + (instr->instrSize / 8));
		break;
	case armv7::ARMV7_CBZ:
		CompareWithZeroAndConditionalJump(arch, il, instr->fields[instr->format->operands[0].field0], LLIL_CMP_E,
			(instr->pc - 4) + instr->fields[instr->format->operands[1].field0], (instr->pc - 4) + (instr->instrSize / 8));
		break;
	case armv7::ARMV7_CLZ:
	{
		LowLevelILLabel loopStart,
				loopBody,
				loopExit;
		//Count leading zeros
		//Based on the non-thumb CLZ lifter
		//
		// TEMP0 = 0
		// TEMP1 = op2.reg
		// while (TEMP1 != 0)
		// 		TEMP1 = TEMP1 >> 1
		// 		TEMP0 = TEMP0 + 1
		// op1.reg = 32 - TEMP0
		il.AddInstruction(il.SetRegister(4, LLIL_TEMP(0), il.Const(4, 0)));
		il.AddInstruction(il.SetRegister(4, LLIL_TEMP(1), ReadILOperand(il, instr, 1)));
		il.AddInstruction(il.Goto(loopStart));
		il.MarkLabel(loopStart);
		il.AddInstruction(il.If(il.CompareNotEqual(4, il.Register(4, LLIL_TEMP(1)), il.Const(4, 0)), loopBody, loopExit));
		il.MarkLabel(loopBody);
		il.AddInstruction(il.SetRegister(4, LLIL_TEMP(1), il.LogicalShiftRight(4, il.Register(4, LLIL_TEMP(1)), il.Const(4, 1))));
		il.AddInstruction(il.SetRegister(4, LLIL_TEMP(0), il.Add(4, il.Register(4, LLIL_TEMP(0)), il.Const(4, 1))));
		il.AddInstruction(il.Goto(loopStart));
		il.MarkLabel(loopExit);
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Sub(4, il.Const(4, 32), il.Register(4, LLIL_TEMP(0)))));
		break;
	}
	case armv7::ARMV7_CMP:
		il.AddInstruction(il.Sub(4, ReadILOperand(il, instr, 0), ReadILOperand(il, instr, 1), IL_FLAGWRITE_ALL));
		break;
	case armv7::ARMV7_CMN:
		il.AddInstruction(il.Add(4, ReadILOperand(il, instr, 0), ReadILOperand(il, instr, 1), IL_FLAGWRITE_ALL));
		break;
	case armv7::ARMV7_DBG:
		il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DBG, {il.Const(1, instr->fields[FIELD_option])}));
		break;
	case armv7::ARMV7_DMB:
		switch (instr->fields[FIELD_barrier_option]) {
		case 0xf: /* 0b1111 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_SY, {}));
			break;
		case 0xe: /* 0b1110 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_ST, {}));
			break;
		case 0xb: /* 0b1011 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_ISH, {}));
			break;
		case 0xa: /* 0b1010 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_ISHST, {}));
			break;
		case 0x7: /* 0b0111 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_NSH, {}));
			break;
		case 0x6: /* 0b0110 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_NSHST, {}));
			break;
		case 0x3: /* 0b0011 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_OSH, {}));
			break;
		case 0x2: /* 0b0011 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DMB_OSHST, {}));
			break;
		default:
			il.AddInstruction(il.Unimplemented());
			break;
		}
		break;
	case armv7::ARMV7_DSB:
		switch (instr->fields[FIELD_barrier_option]) {
		case 0xf: /* 0b1111 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_SY, {}));
			break;
		case 0xe: /* 0b1110 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_ST, {}));
			break;
		case 0xb: /* 0b1011 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_ISH, {}));
			break;
		case 0xa: /* 0b1010 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_ISHST, {}));
			break;
		case 0x7: /* 0b0111 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_NSH, {}));
			break;
		case 0x6: /* 0b0110 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_NSHST, {}));
			break;
		case 0x3: /* 0b0011 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_OSH, {}));
			break;
		case 0x2: /* 0b0011 */
			il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_DSB_OSHST, {}));
			break;
		default:
			il.AddInstruction(il.Unimplemented());
			break;
		}
		break;
	case armv7::ARMV7_EOR:
		il.AddInstruction(WriteArithOperand(il, instr, il.Xor(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_EORS:
		il.AddInstruction(WriteArithOperand(il, instr, il.Xor(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case ARMV7_ISB:
		il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_ISB, {}));
		break;
	case ARMV7_LDM:
	case ARMV7_LDMIA:
	case ARMV7_LDMDB:
	{
		bool is16BitForm = (instr->instrSize == 16);
		int32_t txCnt = (instr->mnem == ARMV7_LDMDB) ? 1 : 0;
		int32_t stride = (instr->mnem == ARMV7_LDMDB) ? -4 : 4;
		uint32_t baseReg = GetRegisterByIndex(instr->fields[instr->format->operands[0].field0]);
		uint32_t regs = instr->fields[instr->format->operands[1].field0];
		uint32_t lrpcBits = (1 << armv7::REG_LR) | (1 << armv7::REG_PC);
		bool valid = true;
		if (baseReg == armv7::REG_PC)
			valid = false;
		else if (!is16BitForm)
		{
			if (((regs & (1 << armv7::REG_SP)) || (regs & (1 << armv7::REG_PC)) || ((regs & lrpcBits) == lrpcBits) || !(regs & (regs - 1)) || (HasWriteback(instr, 0) && (regs & (1 << baseReg)))))
				valid = false;
		}
		else // is16BitForm
		{
			if (instr->mnem == ARMV7_LDMDB)
				valid = false;
			else if (!HasWriteback(instr, 0) && !(regs & (1 << baseReg)))
				valid = false;
		}

		if (!valid)
		{
			il.AddInstruction(il.Undefined());
			break;
		}

		il.AddInstruction(il.SetRegister(4, LLIL_TEMP(0), il.Register(4, baseReg)));

		int32_t regLimit = is16BitForm ? 7 : 15;
		for (int32_t i = 0; i <= regLimit; i++)
		{
			if ((regs >> i) & 1)
			{
				il.AddInstruction(il.SetRegister(4, GetRegisterByIndex(i),
					il.Load(4, il.Add(4, il.Register(4, LLIL_TEMP(0)), il.Const(4, txCnt * stride)))));
				txCnt++;
			}
		}

		if (HasWriteback(instr, 0))
			il.AddInstruction(il.SetRegister(4, baseReg,
				il.Add(4, ReadRegister(il, instr, LLIL_TEMP(0)), il.Const(4, txCnt * stride))));

		if (regs & (1 << armv7::REG_PC))
			il.AddInstruction(il.Jump(ReadRegister(il, instr, armv7::REG_PC, 4)));
		break;
	}
	case armv7::ARMV7_LDA:
	case armv7::ARMV7_LDR:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(WriteILOperand(il, instr, 0, il.Load(4, GetMemoryAddress(il, instr, 1, 4, false))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(WriteILOperand(il, instr, 0, il.Load(4, GetMemoryAddress(il, instr, 1, 4))));
		}
		break;
	case armv7::ARMV7_LDAB:
	case armv7::ARMV7_LDRB:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(WriteILOperand(il, instr, 0, il.ZeroExtend(4,
				il.Load(1, GetMemoryAddress(il, instr, 1, 4, false)))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(WriteILOperand(il, instr, 0, il.ZeroExtend(4,
				il.Load(1, GetMemoryAddress(il, instr, 1, 4)))));
		}
		break;
	case armv7::ARMV7_LDAH:
	case armv7::ARMV7_LDRH:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(WriteILOperand(il, instr, 0, il.ZeroExtend(4,
				il.Load(2, GetMemoryAddress(il, instr, 1, 4, false)))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(WriteILOperand(il, instr, 0, il.ZeroExtend(4,
				il.Load(2, GetMemoryAddress(il, instr, 1, 4)))));
		}
		break;
	case armv7::ARMV7_LDRD:
		{
			ExprId mem;

			uint32_t rt = GetRegisterByIndex(instr->fields[instr->format->operands[0].field0]);
			uint32_t rt2 = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);

			mem = GetMemoryAddress(il, instr, 2, 4, instr->format->operandCount != 4);
			if (arch->GetEndianness() == LittleEndian) {
				il.AddInstruction(il.SetRegister(4, rt, il.Load(4, mem)));
				il.AddInstruction(il.SetRegister(4, rt2, il.Load(4, il.Add(4, mem, il.Const(4, 4)))));
			} else {
				il.AddInstruction(il.SetRegister(4, rt2, il.Load(4, mem)));
				il.AddInstruction(il.SetRegister(4, rt, il.Load(4, il.Add(4, mem, il.Const(4, 4)))));
			}

			if (instr->format->operandCount == 4)
			{
				uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[2].field0]);
				il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 3))));
			}
		}
		break;
	case armv7::ARMV7_LDRSB:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(WriteILOperand(il, instr, 0, il.SignExtend(4,
				il.Load(1, GetMemoryAddress(il, instr, 1, 4, false)))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(WriteILOperand(il, instr, 0, il.SignExtend(4,
				il.Load(1, GetMemoryAddress(il, instr, 1, 4)))));
		}
		break;
	case armv7::ARMV7_LDRSH:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(WriteILOperand(il, instr, 0, il.SignExtend(4,
				il.Load(2, GetMemoryAddress(il, instr, 1, 4, false)))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(WriteILOperand(il, instr, 0, il.SignExtend(4,
				il.Load(2, GetMemoryAddress(il, instr, 1, 4)))));
		}
		break;
	case armv7::ARMV7_LSL:
		il.AddInstruction(WriteArithOperand(il, instr, il.ShiftLeft(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_LSLS:
		il.AddInstruction(WriteArithOperand(il, instr, il.ShiftLeft(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_LSR:
		il.AddInstruction(WriteArithOperand(il, instr, il.LogicalShiftRight(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_LSRS:
		il.AddInstruction(WriteArithOperand(il, instr, il.LogicalShiftRight(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_MLA:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Add(4, ReadILOperand(il, instr, 3), il.Mult(4, ReadILOperand(il, instr, 1), ReadILOperand(il, instr, 2)))));
		break;
	case armv7::ARMV7_MLS:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Sub(4, ReadILOperand(il, instr, 3), il.Mult(4, ReadILOperand(il, instr, 1), ReadILOperand(il, instr, 2)))));
		break;
	case armv7::ARMV7_MOV:
	case armv7::ARMV7_MOVW:
		il.AddInstruction(WriteILOperand(il, instr, 0, ReadILOperand(il, instr, 1)));
		break;
	case armv7::ARMV7_MOVS:
		il.AddInstruction(WriteILOperand(il, instr, 0, ReadILOperand(il, instr, 1), 4,
			ifThenBlock ? 0 : IL_FLAGWRITE_NZ));
		break;
	case armv7::ARMV7_MOVT:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Or(4,
			il.ShiftLeft(4, il.Const(2, instr->fields[instr->format->operands[1].field0]), il.Const(1, 16)),
			il.And(4, il.Const(4, 0x0000ffff), ReadILOperand(il, instr, 0)))));
		break;
	case armv7::ARMV7_MUL:
		il.AddInstruction(WriteArithOperand(il, instr, il.Mult(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_NZ : 0)));
		break;
	case armv7::ARMV7_MULS:
		il.AddInstruction(WriteArithOperand(il, instr, il.Mult(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_NZ)));
		break;
	case armv7::ARMV7_MVN:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Not(4, ReadILOperand(il, instr, 1))));
		break;
	case armv7::ARMV7_MVNS:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Not(4, ReadILOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_NOP:
		il.AddInstruction(il.Nop());
		break;
	case ARMV7_ORN:
		il.AddInstruction(WriteArithOperand(il, instr, il.Or(4, ReadArithOperand(il, instr, 0),
			il.Not(4, ReadArithOperand(il, instr, 1)), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_ORR:
		il.AddInstruction(WriteArithOperand(il, instr, il.Or(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_ORRS:
		il.AddInstruction(WriteArithOperand(il, instr, il.Or(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_POP:
		Pop(il, instr->fields[FIELD_registers]);
		break;
	case armv7::ARMV7_PUSH:
		Push(il, instr->fields[FIELD_registers]);
		break;
	case armv7::ARMV7_REV:
		il.AddInstruction(WriteILOperand(il, instr, 0,
			il.Or(4, il.LogicalShiftRight(4, ReadILOperand(il, instr, 1), il.Const(4, 24)),
				 il.Or(4, il.And(4, il.LogicalShiftRight(4, ReadILOperand(il, instr, 1), il.Const(4, 16)), il.Const(4, 0xff)),
					  il.Or(4, il.And(4, il.LogicalShiftRight(4, ReadILOperand(il, instr, 1), il.Const(4, 8)), il.Const(4, 0xff)),
						   il.And(4, ReadILOperand(il, instr, 1), il.Const(4, 0xff)))))));
		break;
	case armv7::ARMV7_REV16:
		il.AddInstruction(il.SetRegister(2, LLIL_TEMP(0), il.RotateRight(2, il.LowPart(2, ReadILOperand(il, instr, 1)), il.Const(1, 16))));
		il.AddInstruction(il.SetRegister(2, LLIL_TEMP(1), il.RotateRight(2, il.LogicalShiftRight(2, ReadILOperand(il, instr, 1), il.Const(1, 16)), il.Const(1, 16))));
		il.AddInstruction(WriteILOperand(il, instr, 0, il.Or(4, il.ShiftLeft(4, il.Register(2, LLIL_TEMP(1)), il.Const(1, 16)), il.Register(2, LLIL_TEMP(0)))));
		break;
	case armv7::ARMV7_RSB:
		il.AddInstruction(WriteArithOperand(il, instr, il.Sub(4, ReadArithOperand(il, instr, 1),
			ReadArithOperand(il, instr, 0), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_RSBS:
		il.AddInstruction(WriteArithOperand(il, instr, il.Sub(4, ReadArithOperand(il, instr, 1),
			ReadArithOperand(il, instr, 0), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_UDIV:
		il.AddInstruction(WriteArithOperand(il, instr, il.DivUnsigned(4, ReadArithOperand(il, instr, 0), ReadArithOperand(il, instr, 1))));
		break;
	case armv7::ARMV7_SDIV:
		il.AddInstruction(WriteArithOperand(il, instr, il.DivSigned(4, ReadArithOperand(il, instr, 0), ReadArithOperand(il, instr, 1))));
		break;
	case armv7::ARMV7_SBC:
		il.AddInstruction(WriteArithOperand(il, instr, il.SubBorrow(4, ReadArithOperand(il, instr, 0),
									       ReadArithOperand(il, instr, 1),
									       il.Not(1, il.Flag(IL_FLAG_C)),
									       WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_SBCS:
		il.AddInstruction(WriteArithOperand(il, instr, il.SubBorrow(4, ReadArithOperand(il, instr, 0),
									       ReadArithOperand(il, instr, 1),
									       il.Not(1, il.Flag(IL_FLAG_C)),
									       ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case ARMV7_SEV:
		il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_SEV, {}));
		break;
	case ARMV7_STM:
	case ARMV7_STMIA:
	case ARMV7_STMDB:
	{
		bool is16BitForm = (instr->instrSize == 16);
		int32_t txCnt = (instr->mnem == ARMV7_LDMDB) ? 1 : 0;
		int32_t stride = (instr->mnem == ARMV7_LDMDB) ? -4 : 4;
		uint32_t baseReg = GetRegisterByIndex(instr->fields[instr->format->operands[0].field0]);
		uint32_t regs = instr->fields[instr->format->operands[1].field0];
		bool valid = true;
		if (baseReg == armv7::REG_PC)
			valid = false;
		else if (!is16BitForm)
		{
			if (((regs & (1 << armv7::REG_SP)) || (regs & (1 << armv7::REG_PC)) || !(regs & (regs - 1)) || (HasWriteback(instr, 0) && (regs & (1 << baseReg)))))
				valid = false;
		}
		else // is16BitForm
		{
			if ((instr->mnem == ARMV7_LDMDB) || !HasWriteback(instr, 0))
				valid = false;
			// TODO technically not allowed...perhaps add a tag for indication of cases like this
			// else if ((regs & (1 << baseReg)) && (((1 << baseReg) - 1) & regs))
			// 	valid = false;
		}

		if (!valid)
		{
			il.AddInstruction(il.Undefined());
			break;
		}

		int32_t regLimit = is16BitForm ? 7 : 15;
		for (int32_t i = 0; i <= regLimit; i++)
		{
			if ((regs >> i) & 1)
			{
				il.AddInstruction(il.Store(4,
					il.Add(4, il.Register(4, baseReg), il.Const(4, txCnt * stride)),
						il.Register(4, GetRegisterByIndex(i))));
				txCnt++;
			}
		}

		if (HasWriteback(instr, 0))
			il.AddInstruction(il.SetRegister(4, baseReg,
				il.Add(4, ReadRegister(il, instr, baseReg), il.Const(4, txCnt * stride))));

		if (regs & (1 << armv7::REG_PC))
			il.AddInstruction(il.Jump(ReadRegister(il, instr, armv7::REG_PC, 4)));
		break;
	}
	case armv7::ARMV7_STL:
	case armv7::ARMV7_STR:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(il.Store(4, GetMemoryAddress(il, instr, 1, 4, false), ReadILOperand(il, instr, 0)));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(il.Store(4, GetMemoryAddress(il, instr, 1, 4), ReadILOperand(il, instr, 0)));
		}
		break;
	case armv7::ARMV7_STLB:
	case armv7::ARMV7_STRB:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(il.Store(1, GetMemoryAddress(il, instr, 1, 4, false), il.LowPart(1, ReadILOperand(il, instr, 0))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(il.Store(1, GetMemoryAddress(il, instr, 1, 4), il.LowPart(1, ReadILOperand(il, instr, 0))));
		}
		break;
	case armv7::ARMV7_STLH:
	case armv7::ARMV7_STRH:
		if (instr->format->operandCount == 3)
		{
			uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[1].field0]);
			il.AddInstruction(il.Store(2, GetMemoryAddress(il, instr, 1, 4, false), il.LowPart(2, ReadILOperand(il, instr, 0))));
			il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 2))));
		}
		else
		{
			il.AddInstruction(il.Store(2, GetMemoryAddress(il, instr, 1, 4), il.LowPart(2, ReadILOperand(il, instr, 0))));
		}
		break;
	case armv7::ARMV7_STRD:
		{
			ExprId mem;

			mem = GetMemoryAddress(il, instr, 2, 4, instr->format->operandCount != 4);
			if (arch->GetEndianness() == LittleEndian) {
				il.AddInstruction(il.Store(4, mem, ReadILOperand(il, instr, 0)));
				il.AddInstruction(il.Store(4, il.Add(4, mem, il.Const(4, 4)), ReadILOperand(il, instr, 1)));
			} else {
				il.AddInstruction(il.Store(4, mem, ReadILOperand(il, instr, 1)));
				il.AddInstruction(il.Store(4, il.Add(4, mem, il.Const(4, 4)), ReadILOperand(il, instr, 0)));
			}

			if (instr->format->operandCount == 4)
			{
				uint32_t reg = GetRegisterByIndex(instr->fields[instr->format->operands[2].field0]);
				il.AddInstruction(il.SetRegister(4, reg, il.Add(4, il.Register(4, reg), ReadILOperand(il, instr, 3))));
			}
		}
		break;
	case armv7::ARMV7_SUB:
	case armv7::ARMV7_SUBW:
		if (IsPCRelativeDataAddress(instr, ifThenBlock))
			il.AddInstruction(WriteArithOperand(il, instr, il.Sub(4, il.And(4, ReadILOperand(il, instr, 1, 4), il.Const(4, ~3)),
				ReadILOperand(il, instr, 2, 4))));
		else
			il.AddInstruction(WriteArithOperand(il, instr, il.Sub(4, ReadArithOperand(il, instr, 0),
				ReadArithOperand(il, instr, 1), WritesToStatus(instr, ifThenBlock) ? IL_FLAGWRITE_ALL : 0)));
		break;
	case armv7::ARMV7_SUBS:
		il.AddInstruction(WriteArithOperand(il, instr, il.Sub(4, ReadArithOperand(il, instr, 0),
			ReadArithOperand(il, instr, 1), ifThenBlock ? 0 : IL_FLAGWRITE_ALL)));
		break;
	case armv7::ARMV7_SXTAB:
		il.AddInstruction(WriteArithOperand(il, instr, il.Add(4, ReadILOperand(il, instr, 1), il.SignExtend(4, il.LowPart(1, ReadRotatedOperand(il, instr, 2))))));
		break;
	case armv7::ARMV7_SXTAH:
		il.AddInstruction(WriteArithOperand(il, instr, il.Add(4, ReadILOperand(il, instr, 1), il.SignExtend(4, il.LowPart(2, ReadRotatedOperand(il, instr, 2))))));
		break;
	case armv7::ARMV7_SXTB:
		il.AddInstruction(WriteArithOperand(il, instr, il.SignExtend(4, il.LowPart(1, ReadRotatedOperand(il, instr, 1)))));
		break;
	case armv7::ARMV7_SXTH:
		il.AddInstruction(WriteArithOperand(il, instr, il.SignExtend(4, il.LowPart(2, ReadRotatedOperand(il, instr, 1)))));
		break;
	case armv7::ARMV7_TBB:
		il.AddInstruction(il.Jump(il.Add(4, il.ConstPointer(4, instr->pc), il.Mult(4, il.Const(4, 2),
			il.ZeroExtend(4, il.Load(1, GetMemoryAddress(il, instr, 0, 4, false)))))));
		break;
	case armv7::ARMV7_TBH:
		il.AddInstruction(il.Jump(il.Add(4, il.ConstPointer(4, instr->pc), il.Mult(4, il.Const(4, 2),
			il.ZeroExtend(4, il.Load(2, GetMemoryAddress(il, instr, 0, 4, false)))))));
		break;
	case armv7::ARMV7_TEQ:
		il.AddInstruction(il.Xor(4, ReadILOperand(il, instr, 0), ReadILOperand(il, instr, 1), IL_FLAGWRITE_ALL));
		break;
	case armv7::ARMV7_TST:
		il.AddInstruction(il.And(4, ReadILOperand(il, instr, 0), ReadILOperand(il, instr, 1), IL_FLAGWRITE_ALL));
		break;
	case armv7::ARMV7_UBFX:
		il.AddInstruction(WriteILOperand(il, instr, 0, il.And(4, il.LogicalShiftRight(4, ReadILOperand(il, instr, 1),
												 ReadILOperand(il, instr, 2)),
									 il.Const(4, (1 << instr->fields[instr->format->operands[3].field0]) - 1))));
		break;
	case armv7::ARMV7_UDF:
		il.AddInstruction(il.Trap(ReadILOperand(il, instr, 0)));
		break;
	case armv7::ARMV7_UMULL:
		il.AddInstruction(WriteSplitOperands(il, instr, 1, 0, il.MultDoublePrecUnsigned(8, ReadILOperand(il, instr, 2), ReadILOperand(il, instr, 3))));
		break;
	case armv7::ARMV7_SMULL:
		il.AddInstruction(WriteSplitOperands(il, instr, 1, 0, il.MultDoublePrecSigned(8, ReadILOperand(il, instr, 2), ReadILOperand(il, instr, 3))));
		break;
	case armv7::ARMV7_UXTAB:
		il.AddInstruction(WriteArithOperand(il, instr,
			il.Add(4,
				ReadILOperand(il, instr, 1),
				il.ZeroExtend(4, il.LowPart(1, ReadRotatedOperand(il, instr, 2))))));
		break;
	case armv7::ARMV7_UXTAH:
		il.AddInstruction(WriteArithOperand(il, instr,
			il.Add(4,
				ReadILOperand(il, instr, 1),
				il.ZeroExtend(4, il.LowPart(2, ReadRotatedOperand(il, instr, 2))))));
		break;
	case armv7::ARMV7_UXTB:
		il.AddInstruction(WriteArithOperand(il, instr, il.ZeroExtend(4, il.LowPart(1, ReadRotatedOperand(il, instr, 1)))));
		break;
	case armv7::ARMV7_UXTH:
		il.AddInstruction(WriteArithOperand(il, instr, il.ZeroExtend(4, il.LowPart(2, ReadRotatedOperand(il, instr, 1)))));
		break;
	case ARMV7_WFE:
		il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_WFE, {}));
		break;
	case ARMV7_WFI:
		il.AddInstruction(il.Intrinsic({}, ARMV7_INTRIN_WFI, {}));
		break;
	default:
		GetLowLevelILForNEONInstruction(arch, il, instr, ifThenBlock);
		break;
	}
	return true;
}

bool GetLowLevelILForNEONInstruction(Architecture* arch, LowLevelILFunction& il, decomp_result* instr, bool ifThenBlock)
{
	(void)arch;
	(void)ifThenBlock;
	switch (instr->mnem){
	case armv7::ARMV7_VADD:
		il.AddInstruction(WriteArithOperand(il, instr, il.Add(GetRegisterSize(instr, 0), ReadILOperand(il, instr, 1), ReadILOperand(il, instr, 2))));
		break;
	case armv7::ARMV7_VBIF:
		il.AddInstruction(WriteArithOperand(il, instr, il.Or(GetRegisterSize(instr, 0),
			il.And(GetRegisterSize(instr, 0),
				ReadILOperand(il, instr, 0),
				ReadILOperand(il, instr, 2)),
			il.And(GetRegisterSize(instr, 0),
				ReadILOperand(il, instr, 1),
				il.Not(GetRegisterSize(instr, 0), ReadILOperand(il, instr, 2))))
			));
		break;
	case armv7::ARMV7_VBIT:
		il.AddInstruction(WriteArithOperand(il, instr, il.Or(GetRegisterSize(instr, 0),
			il.And(GetRegisterSize(instr, 0),
				ReadILOperand(il, instr, 1),
				ReadILOperand(il, instr, 2)),
			il.And(GetRegisterSize(instr, 0),
				ReadILOperand(il, instr, 0),
				il.Not(GetRegisterSize(instr, 0), ReadILOperand(il, instr, 2))))
			));
		break;
	case armv7::ARMV7_VBSL:
		il.AddInstruction(WriteArithOperand(il, instr, il.Or(GetRegisterSize(instr, 0),
			il.And(GetRegisterSize(instr, 0),
				ReadILOperand(il, instr, 1),
				ReadILOperand(il, instr, 0)),
			il.And(GetRegisterSize(instr, 0),
				ReadILOperand(il, instr, 2),
				il.Not(GetRegisterSize(instr, 0), ReadILOperand(il, instr, 0))))
			));
		break;
	case armv7::ARMV7_VEOR:
		il.AddInstruction(WriteArithOperand(il, instr, il.Xor(GetRegisterSize(instr, 0), ReadILOperand(il, instr, 1), ReadILOperand(il, instr, 2))));
		break;
	case armv7::ARMV7_VSUB:
		il.AddInstruction(WriteArithOperand(il, instr, il.Sub(GetRegisterSize(instr, 0), ReadILOperand(il, instr, 1), ReadILOperand(il, instr, 2))));
		break;
	default:
		il.AddInstruction(il.Unimplemented());
		break;
	}
	return true;
}
