#include "EeBasicBlock.h"
#include "offsetof_def.h"

void CEeBasicBlock::SetFpRoundingMode(Jitter::CJitter::ROUNDINGMODE fpRoundingMode)
{
	m_fpRoundingMode = fpRoundingMode;
}

void CEeBasicBlock::SetIsIdleLoopBlock()
{
	m_isIdleLoopBlock = true;
}

void CEeBasicBlock::CompileProlog(CMipsJitter* jitter)
{
	if(m_fpRoundingMode != DEFAULT_FP_ROUNDING_MODE)
	{
		jitter->FP_SetRoundingMode(m_fpRoundingMode);
	}
	CBasicBlock::CompileProlog(jitter);
}

void CEeBasicBlock::CompileEpilog(CMipsJitter* jitter, bool loopsOnItself)
{
	if(m_fpRoundingMode != DEFAULT_FP_ROUNDING_MODE)
	{
		jitter->FP_SetRoundingMode(DEFAULT_FP_ROUNDING_MODE);
	}

	if(m_isIdleLoopBlock || IsCodeIdleLoopBlock())
	{
		jitter->PushCst(MIPS_EXCEPTION_IDLE);
		jitter->PullRel(offsetof(CMIPS, m_State.nHasException));
	}

	CBasicBlock::CompileEpilog(jitter, loopsOnItself);
}

bool CEeBasicBlock::IsCodeIdleLoopBlock() const
{
	enum OP
	{
		OP_BEQ = 0x04,
		OP_BNE = 0x05,
		OP_ADDIU = 0x09,
		OP_SLTIU = 0x0B,
		OP_ANDI = 0x0C,
		OP_XORI = 0x0E,
		OP_LUI = 0x0F,
		OP_LQ = 0x1E,
		OP_LW = 0x23,
		OP_LBU = 0x24,
	};

	enum
	{
		OP_SPECIAL_SLL = 0x00,
		OP_SPECIAL_ADDU = 0x21,
		OP_SPECIAL_SLT = 0x2A,
		OP_SPECIAL_SLTU = 0x2B,
		OP_SPECIAL_DSLL32 = 0x3C,
		OP_SPECIAL_DSRL32 = 0x3E,
	};

	uint32 endInstructionAddress = m_end - 4;
	uint32 endInstruction = m_context.m_pMemoryMap->GetWord(endInstructionAddress);

	//We need a branch at the end of the block
	auto branchType = m_context.m_pArch->IsInstructionBranch(&m_context, endInstructionAddress, endInstruction);
	if(branchType != MIPS_BRANCH_NORMAL) return false;

	//Check that the branch target is ourself
	uint32 branchTarget = m_context.m_pArch->GetInstructionEffectiveAddress(&m_context, endInstructionAddress, endInstruction);
	if(branchTarget == MIPS_INVALID_PC) return false;
	if(branchTarget != m_begin) return false;

	uint32 compareRs = 0;
	uint32 compareRt = 0;

	//Check what kind of branching instruction we have.
	{
		uint32 op = (endInstruction >> 26) & 0x3F;
		uint32 rt = (endInstruction >> 16) & 0x1F;
		uint32 rs = (endInstruction >> 21) & 0x1F;

		//We want a BEQ or BNE
		if((op != OP_BEQ) && (op != OP_BNE)) return false;

		compareRs = rs;
		compareRt = rt;
	}

	uint32 defState = 0; //Set of completely new definitions of registers within this block
	uint32 useState = 0; //Set of previous state usage within this block

	//Check all instructions inside to see if we can prove it's waiting for some kind of flag
	for(uint32 address = m_begin; address <= m_end; address += 4)
	{
		//Don't check branch instruction as we've checked it already
		if(address == endInstructionAddress) continue;

		uint32 inst = m_context.m_pMemoryMap->GetWord(address);
		if(inst == 0) continue;
		uint32 special = inst & 0x3F;
		uint32 rd = (inst >> 11) & 0x1F;
		uint32 rt = (inst >> 16) & 0x1F;
		uint32 rs = (inst >> 21) & 0x1F;
		uint32 op = (inst >> 26) & 0x3F;

		uint32 newDef = 0;
		uint32 newUse = 0;

		switch(op)
		{
		case 0x00:
			switch(special)
			{
			case OP_SPECIAL_SLL:
			case OP_SPECIAL_DSLL32:
			case OP_SPECIAL_DSRL32:
				newUse = (1 << rt);
				newDef = (1 << rd);
				break;
			case OP_SPECIAL_ADDU:
			case OP_SPECIAL_SLT:
			case OP_SPECIAL_SLTU:
				newUse = (1 << rs) | (1 << rt);
				newDef = (1 << rd);
				break;
			default:
				//We don't know what this does, let's not take a chance
				return false;
			}
			break;
		case OP_LUI:
			newDef = (1 << rt);
			break;
		case OP_LW:
		case OP_LQ:
		case OP_LBU:
		case OP_ADDIU:
		case OP_SLTIU:
		case OP_XORI:
			newUse = (1 << rs);
			newDef = (1 << rt);
			break;
		default:
			//We don't know what this does, let's not take a chance
			return false;
		}

		//Remove uses from defs within this block
		newUse &= ~defState;
		useState |= newUse;

		//Bail if this defines any state that we previously used
		if(useState & newDef)
		{
			return false;
		}

		defState |= newDef;
	}

	//Just make sure that we've at least defined our comparision register
	FRAMEWORK_MAYBE_UNUSED bool compareRsDefined = defState & (1 << compareRs);
	FRAMEWORK_MAYBE_UNUSED bool compareRtDefined = defState & (1 << compareRt);

	assert(compareRsDefined || compareRtDefined);

	return true;
}
