/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 * 
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

//------------------------------------------------------------------
// Messages Called at Execution Time...
//------------------------------------------------------------------

static void __fc mVUbadOp0  (u32 prog, u32 pc)	{ Console.Error("microVU0 Warning: Exiting... Block contains an illegal opcode. [%04x] [%03d]", pc, prog); }
static void __fc mVUbadOp1  (u32 prog, u32 pc)	{ Console.Error("microVU1 Warning: Exiting... Block contains an illegal opcode. [%04x] [%03d]", pc, prog); }
static void __fc mVUwarning0(u32 prog, u32 pc)	{ Console.Error("microVU0 Warning: Exiting from Possible Infinite Loop [%04x] [%03d]", pc, prog); }
static void __fc mVUwarning1(u32 prog, u32 pc)	{ Console.Error("microVU1 Warning: Exiting from Possible Infinite Loop [%04x] [%03d]", pc, prog); }
static void __fc mVUprintPC1(u32 pc)			{ Console.WriteLn("Block Start PC = 0x%04x", pc); }
static void __fc mVUprintPC2(u32 pc)			{ Console.WriteLn("Block End PC   = 0x%04x", pc); }

//------------------------------------------------------------------
// Program Range Checking and Setting up Ranges
//------------------------------------------------------------------

// Used by mVUsetupRange
__fi void mVUcheckIsSame(mV) {
	if (mVU.prog.isSame == -1) {
		mVU.prog.isSame = !memcmp_mmx((u8*)mVUcurProg.data, mVU.regs().Micro, mVU.microMemSize);
	}
	if (mVU.prog.isSame == 0) {
		mVUcacheProg(mVU, *mVU.prog.cur);
		mVU.prog.isSame = 1;
	}
}

// Sets up microProgram PC ranges based on whats been recompiled
void mVUsetupRange(microVU& mVU, s32 pc, bool isStartPC) {
	std::deque<microRange>*& ranges = mVUcurProg.ranges;
	pc &= mVU.microMemSize - 8;

	if (isStartPC) { // Check if startPC is already within a block we've recompiled
		std::deque<microRange>::const_iterator it(ranges->begin());
		for ( ; it != ranges->end(); ++it) {
			if ((pc >= it[0].start) && (pc <= it[0].end)) {
				if (it[0].start != it[0].end)
					return; // Last case makes sure its not a 1-opcode EvilBlock
			}
		}
	}
	else if (mVUrange.end != -1) {
		// Above case was true
		return;
	}

	mVUcheckIsSame(mVU);

	if (isStartPC) {
		microRange mRange = {pc, -1};
		ranges->push_front(mRange);
		return;
	}
	if (mVUrange.start <= pc) {
		mVUrange.end = pc;
		bool mergedRange = false;
		s32  rStart = mVUrange.start;
		s32  rEnd   = mVUrange.end;
		std::deque<microRange>::iterator it(ranges->begin());
		for (++it; it != ranges->end(); ++it) {
			if((it[0].start >= rStart) && (it[0].start <= rEnd)) {
				it[0].end   = std::max(it[0].end, rEnd);
				mergedRange = true;
			}
			else if ((it[0].end >= rStart) && (it[0].end <= rEnd)) {
				it[0].start = std::min(it[0].start, rStart);
				mergedRange = true;
			}
		}
		if (mergedRange) {
			//DevCon.WriteLn(Color_Green, "microVU%d: Prog Range Merging", mVU.index);
			ranges->erase(ranges->begin());
		}
	}
	else {
		DevCon.WriteLn(Color_Green, "microVU%d: Prog Range Wrap [%04x] [%d]", mVU.index, mVUrange.start, mVUrange.end);
		mVUrange.end = mVU.microMemSize;
		microRange mRange = {0, pc};
		ranges->push_front(mRange);
	}
}

//------------------------------------------------------------------
// Execute VU Opcode/Instruction (Upper and Lower)
//------------------------------------------------------------------

__ri void doUpperOp(mV) { mVUopU(mVU, 1); mVUdivSet(mVU); }
__ri void doLowerOp(mV) { incPC(-1); mVUopL(mVU, 1); incPC(1); }
__ri void flushRegs(mV) { if (!doRegAlloc) mVU.regAlloc->flushAll(); }

void doIbit(mV) { 
	if (mVUup.iBit) { 
		incPC(-1);
		u32 tempI;
		mVU.regAlloc->clearRegVF(33);

		if (CHECK_VU_OVERFLOW && ((curI & 0x7fffffff) >= 0x7f800000)) {
			DevCon.WriteLn(Color_Green,"microVU%d: Clamping I Reg", mVU.index);
			tempI = (0x80000000 & curI) | 0x7f7fffff; // Clamp I Reg
		}
		else tempI = curI;
		
		xMOV(ptr32[&mVU.getVI(REG_I)], tempI);
		incPC(1);
	} 
}

void doSwapOp(mV) { 
	if (mVUinfo.backupVF && !mVUlow.noWriteVF) {
		DevCon.WriteLn(Color_Green, "microVU%d: Backing Up VF Reg [%04x]", getIndex, xPC);

		// Allocate t1 first for better chance of reg-alloc
		const xmm& t1 = mVU.regAlloc->allocReg(mVUlow.VF_write.reg);
		const xmm& t2 = mVU.regAlloc->allocReg();
		xMOVAPS(t2, t1); // Backup VF reg
		mVU.regAlloc->clearNeeded(t1);

		mVUopL(mVU, 1);

		const xmm& t3 = mVU.regAlloc->allocReg(mVUlow.VF_write.reg, mVUlow.VF_write.reg, 0xf, 0);
		xXOR.PS(t2, t3); // Swap new and old values of the register
		xXOR.PS(t3, t2); // Uses xor swap trick...
		xXOR.PS(t2, t3);
		mVU.regAlloc->clearNeeded(t3);

		incPC(1); 
		doUpperOp(mVU);

		const xmm& t4 = mVU.regAlloc->allocReg(-1, mVUlow.VF_write.reg, 0xf);
		xMOVAPS(t4, t2);
		mVU.regAlloc->clearNeeded(t4);
		mVU.regAlloc->clearNeeded(t2);
	}
	else { mVUopL(mVU, 1); incPC(1); flushRegs(mVU); doUpperOp(mVU); }
}

void mVUexecuteInstruction(mV) {
	if (mVUlow.isNOP) {
		incPC(1);
		doUpperOp(mVU);
		flushRegs(mVU);
		doIbit(mVU);
	}
	else if (!mVUinfo.swapOps) {
		incPC(1);
		doUpperOp(mVU);
		flushRegs(mVU);
		doLowerOp(mVU);
	}
	else {
		doSwapOp(mVU);
	}

	flushRegs(mVU);
}

//------------------------------------------------------------------
// Warnings / Errors / Illegal Instructions
//------------------------------------------------------------------

// If 1st op in block is a bad opcode, then don't compile rest of block (Dawn of Mana Level 2)
__fi void mVUcheckBadOp(mV) {

	// The BIOS writes upper and lower NOPs in reversed slots (bug)
	//So to prevent spamming we ignore these, however its possible the real VU will bomb out if 
	//this happens, so we will bomb out without warning.
	if (mVUinfo.isBadOp && mVU.code != 0x8000033c) {
		
		mVUinfo.isEOB = true;
		DevCon.Warning("microVU Warning: Block contains an illegal opcode...");

	}
}

// Prints msg when exiting block early if 1st op was a bad opcode (Dawn of Mana Level 2)
// #ifdef PCSX2_DEVBUILD because starting with SVN R5586 we get log spam in releases (Shadow Hearts battles)  
__fi void handleBadOp(mV, int count) {
#ifdef PCSX2_DEVBUILD
	if (mVUinfo.isBadOp) {
		mVUbackupRegs(mVU, true);
		xMOV(gprT2, mVU.prog.cur->idx);
		xMOV(gprT3, xPC);
		if (!isVU1) xCALL(mVUbadOp0);
		else		xCALL(mVUbadOp1);
		mVUrestoreRegs(mVU, true);
	}
#endif
}

__ri void branchWarning(mV) {
	incPC(-2);
	if (mVUup.eBit && mVUbranch) {
		incPC(2);
		Console.Error("microVU%d Warning: Branch in E-bit delay slot! [%04x]", mVU.index, xPC);
		mVUlow.isNOP = true;
	}
	else incPC(2);

	if (mVUinfo.isBdelay && !mVUlow.evilBranch) { // Check if VI Reg Written to on Branch Delay Slot Instruction
		if (mVUlow.VI_write.reg && mVUlow.VI_write.used && !mVUlow.readFlags) {
			mVUlow.backupVI = true;
			mVUregs.viBackUp = mVUlow.VI_write.reg;
		}
	}
}

__fi void eBitPass1(mV, int& branch) {
	if (mVUregs.blockType != 1) {
		branch = 1; 
		mVUup.eBit = true;
	}
}

__ri void eBitWarning(mV) {
	if (mVUpBlock->pState.blockType == 1) Console.Error("microVU%d Warning: Branch, E-bit, Branch! [%04x]",  mVU.index, xPC);
	if (mVUpBlock->pState.blockType == 2) Console.Error("microVU%d Warning: Branch, Branch, Branch! [%04x]", mVU.index, xPC);
	incPC(2);
	if (curI & _Ebit_) {
		DevCon.Warning("microVU%d: E-bit in Branch delay slot! [%04x]", mVU.index, xPC);
		mVUregs.blockType = 1;
	}
	incPC(-2);
}

//------------------------------------------------------------------
// Cycles / Pipeline State / Early Exit from Execution
//------------------------------------------------------------------
__fi void optimizeReg(u8& rState)	 { rState = (rState==1) ? 0 : rState; }
__fi void calcCycles(u8& reg, u8 x)	 { reg = ((reg > x) ? (reg - x) : 0); }
__fi void tCycles(u8& dest, u8& src) { dest = std::max(dest, src); }
__fi void incP(mV)					 { mVU.p ^= 1; }
__fi void incQ(mV)					 { mVU.q ^= 1; }

// Optimizes the End Pipeline State Removing Unnecessary Info
// If the cycles remaining is just '1', we don't have to transfer it to the next block
// because mVU automatically decrements this number at the start of its loop,
// so essentially '1' will be the same as '0'...
void mVUoptimizePipeState(mV) {
	for (int i = 0; i < 32; i++) {
		optimizeReg(mVUregs.VF[i].x);
		optimizeReg(mVUregs.VF[i].y);
		optimizeReg(mVUregs.VF[i].z);
		optimizeReg(mVUregs.VF[i].w);
	}
	for (int i = 0; i < 16; i++) {
		optimizeReg(mVUregs.VI[i]);
	}
	if (mVUregs.q) { optimizeReg(mVUregs.q); if (!mVUregs.q) { incQ(mVU); } }
	if (mVUregs.p) { optimizeReg(mVUregs.p); if (!mVUregs.p) { incP(mVU); } }
	mVUregs.r = 0; // There are no stalls on the R-reg, so its Safe to discard info
}

void mVUincCycles(mV, int x) {
	mVUcycles += x;
	// VF[0] is a constant value (0.0 0.0 0.0 1.0)
	for (int z = 31; z > 0; z--) {
		calcCycles(mVUregs.VF[z].x, x);
		calcCycles(mVUregs.VF[z].y, x);
		calcCycles(mVUregs.VF[z].z, x);
		calcCycles(mVUregs.VF[z].w, x);
	}
	// VI[0] is a constant value (0)
	for (int z = 15; z > 0; z--) {
		calcCycles(mVUregs.VI[z], x);
	}
	if (mVUregs.q) {
		if (mVUregs.q > 4) { calcCycles(mVUregs.q, x); if (mVUregs.q <= 4) { mVUinfo.doDivFlag = 1; } }
		else			   { calcCycles(mVUregs.q, x); }
		if (!mVUregs.q)    { incQ(mVU); }
	}
	if (mVUregs.p) {
		calcCycles(mVUregs.p, x);
		if (!mVUregs.p || mVUregsTemp.p) { incP(mVU); }
	}
	if (mVUregs.xgkick) {
		calcCycles(mVUregs.xgkick, x);
		if (!mVUregs.xgkick) { mVUinfo.doXGKICK = 1; mVUinfo.XGKICKPC = xPC;}
	}
	calcCycles(mVUregs.r, x);
}

// Helps check if upper/lower ops read/write to same regs...
void cmpVFregs(microVFreg& VFreg1, microVFreg& VFreg2, bool& xVar) {
	if (VFreg1.reg == VFreg2.reg) {
		if ((VFreg1.x && VFreg2.x) || (VFreg1.y && VFreg2.y)
		||	(VFreg1.z && VFreg2.z) || (VFreg1.w && VFreg2.w))
		{ xVar = 1; }
	}
}

void mVUsetCycles(mV) {
	mVUincCycles(mVU, mVUstall);
	// If upper Op && lower Op write to same VF reg:
	if ((mVUregsTemp.VFreg[0] == mVUregsTemp.VFreg[1]) && mVUregsTemp.VFreg[0]) {
		if (mVUregsTemp.r || mVUregsTemp.VI)
			mVUlow.noWriteVF = true; 
		else
			mVUlow.isNOP = true; // If lower Op doesn't modify anything else, then make it a NOP
	}
	// If lower op reads a VF reg that upper Op writes to:
	if ((mVUlow.VF_read[0].reg || mVUlow.VF_read[1].reg) && mVUup.VF_write.reg) {
		cmpVFregs(mVUup.VF_write, mVUlow.VF_read[0], mVUinfo.swapOps);
		cmpVFregs(mVUup.VF_write, mVUlow.VF_read[1], mVUinfo.swapOps);
	}
	// If above case is true, and upper op reads a VF reg that lower Op Writes to:
	if (mVUinfo.swapOps && ((mVUup.VF_read[0].reg || mVUup.VF_read[1].reg) && mVUlow.VF_write.reg)) {
		cmpVFregs(mVUlow.VF_write, mVUup.VF_read[0], mVUinfo.backupVF);
		cmpVFregs(mVUlow.VF_write, mVUup.VF_read[1], mVUinfo.backupVF);
	}

	tCycles(mVUregs.VF[mVUregsTemp.VFreg[0]].x, mVUregsTemp.VF[0].x);
	tCycles(mVUregs.VF[mVUregsTemp.VFreg[0]].y, mVUregsTemp.VF[0].y);
	tCycles(mVUregs.VF[mVUregsTemp.VFreg[0]].z, mVUregsTemp.VF[0].z);
	tCycles(mVUregs.VF[mVUregsTemp.VFreg[0]].w, mVUregsTemp.VF[0].w);

	tCycles(mVUregs.VF[mVUregsTemp.VFreg[1]].x, mVUregsTemp.VF[1].x);
	tCycles(mVUregs.VF[mVUregsTemp.VFreg[1]].y, mVUregsTemp.VF[1].y);
	tCycles(mVUregs.VF[mVUregsTemp.VFreg[1]].z, mVUregsTemp.VF[1].z);
	tCycles(mVUregs.VF[mVUregsTemp.VFreg[1]].w, mVUregsTemp.VF[1].w);

	tCycles(mVUregs.VI[mVUregsTemp.VIreg],	mVUregsTemp.VI);
	tCycles(mVUregs.q,						mVUregsTemp.q);
	tCycles(mVUregs.p,						mVUregsTemp.p);
	tCycles(mVUregs.r,						mVUregsTemp.r);
	tCycles(mVUregs.xgkick,					mVUregsTemp.xgkick);
}

// Prints Start/End PC of blocks executed, for debugging...
void mVUdebugPrintBlocks(microVU& mVU, bool isEndPC) {
	if (mVUdebugNow) {
		mVUbackupRegs(mVU, true);
		xMOV(gprT2, xPC);
		if (isEndPC) xCALL(mVUprintPC2);
		else		 xCALL(mVUprintPC1);
		mVUrestoreRegs(mVU, true);
	}
}

// vu0 is allowed to exit early, so are dev builds (for inf loops)
__fi bool doEarlyExit(microVU& mVU) {
	return IsDevBuild || !isVU1;
}

// Saves Pipeline State for resuming from early exits
__fi void mVUsavePipelineState(microVU& mVU) {
	u32* lpS = (u32*)&mVU.prog.lpState;
	for(size_t i = 0; i < (sizeof(microRegInfo)-4)/4; i++, lpS++) {
		xMOV(ptr32[lpS], lpS[0]);
	}
}

// Test cycles to see if we need to exit-early...
void mVUtestCycles(microVU& mVU) {
	iPC = mVUstartPC;
	if (doEarlyExit(mVU)) {
		xCMP(ptr32[&mVU.cycles], 0);
		xForwardJG32 skip;
		if (isVU0) {
			// TEST32ItoM((uptr)&mVU.regs().flags, VUFLAG_MFLAGSET);
			// xFowardJZ32 vu0jmp;
			// mVUbackupRegs(mVU, true);
			// xMOV(gprT2, mVU.prog.cur->idx);
			// xMOV(gprT3, xPC);
			// xCALL(mVUwarning0); // VU0 is allowed early exit for COP2 Interlock Simulation
			// mVUrestoreRegs(mVU, true);
			mVUsavePipelineState(mVU);
			mVUendProgram(mVU, NULL, 0);
			// vu0jmp.SetTarget();
		}
		else {
			mVUbackupRegs(mVU, true);
			xMOV(gprT2, mVU.prog.cur->idx);
			xMOV(gprT3, xPC);
			xCALL(mVUwarning1);
			mVUrestoreRegs(mVU, true);
			mVUsavePipelineState(mVU);
			mVUendProgram(mVU, NULL, 0);
		}
		skip.SetTarget();
	}
	xSUB(ptr32[&mVU.cycles], mVUcycles);
}

//------------------------------------------------------------------
// Initializing
//------------------------------------------------------------------

// This gets run at the start of every loop of mVU's first pass
__fi void startLoop(mV) {
	if (curI & _Mbit_)	{ DevCon.WriteLn (Color_Green, "microVU%d: M-bit set! PC = %x", getIndex, xPC); }
	if (curI & _Dbit_)	{ DevCon.WriteLn (Color_Green, "microVU%d: D-bit set! PC = %x", getIndex, xPC); }
	if (curI & _Tbit_)	{ DevCon.WriteLn (Color_Green, "microVU%d: T-bit set! PC = %x", getIndex, xPC); }
	memzero(mVUinfo);
	memzero(mVUregsTemp);
}

// Initialize VI Constants (vi15 propagates through blocks)
__fi void mVUinitConstValues(microVU& mVU) {
	for (int i = 0; i < 16; i++) {
		mVUconstReg[i].isValid	= 0;
		mVUconstReg[i].regValue	= 0;
	}
	mVUconstReg[15].isValid  = mVUregs.vi15v;
	mVUconstReg[15].regValue = mVUregs.vi15v ? mVUregs.vi15 : 0;
}

// Initialize Variables
__fi void mVUinitFirstPass(microVU& mVU, uptr pState, u8* thisPtr) {
	mVUstartPC				= iPC;	// Block Start PC
	mVUbranch				= 0;	// Branch Type
	mVUcount				= 0;	// Number of instructions ran
	mVUcycles				= 0;	// Skips "M" phase, and starts counting cycles at "T" stage
	mVU.p					= 0;	// All blocks start at p index #0
	mVU.q					= 0;	// All blocks start at q index #0
	if ((uptr)&mVUregs != pState) {	// Loads up Pipeline State Info
		memcpy((u8*)&mVUregs, (u8*)pState, sizeof(microRegInfo));
	}
	if (doEarlyExit(mVU) && ((uptr)&mVU.prog.lpState != pState)) {
		memcpy((u8*)&mVU.prog.lpState, (u8*)pState, sizeof(microRegInfo));
	}
	mVUblock.x86ptrStart	= thisPtr;
	mVUpBlock				= mVUblocks[mVUstartPC/2]->add(&mVUblock); // Add this block to block manager
	mVUregs.needExactMatch	= (mVUpBlock->pState.blockType)?7:0; // ToDo: Fix 1-Op block flag linking (MGS2:Demo/Sly Cooper)
	mVUregs.blockType		= 0;
	mVUregs.viBackUp		= 0;
	mVUregs.flagInfo		= 0;
	mVUregs.fullFlags0		= 0;
	mVUregs.fullFlags1		= 0;
	mVUsFlagHack			= CHECK_VU_FLAGHACK;
	mVUinitConstValues(mVU);
}

//------------------------------------------------------------------
// Recompiler
//------------------------------------------------------------------

//This bastardized function is used when a linked branch is in a conditional delay slot. It's messy, it's horrible, but it works.
//Unfortunately linking the reg manually and using the normal evil block method seems to suck at this :/
//If this is removed, test Evil Dead: Fistful of Boomstick (hangs going ingame), Mark of Kri (collision detection)
//and Tony Hawks Project 8 (graphics are half missing, requires Negative rounding when working)
void* mVUcompileSingleInstruction(microVU& mVU, u32 startPC, uptr pState, microFlagCycles& mFC) {
	
	u8*				thisPtr  = x86Ptr;
	
	// First Pass
	iPC = startPC / 4;
	
	mVUbranch = 0;
	incPC(1);
	startLoop(mVU);

	mVUincCycles(mVU, 1);
	mVUopU(mVU, 0);
	mVUcheckBadOp(mVU);
	if (curI & _Ebit_)  { eBitPass1(mVU, branch); DevCon.Warning("E Bit on single instruction");}
	if (curI & _DTbit_) { branch = 4; DevCon.Warning("D Bit on single instruction");}
	if (curI & _Mbit_)  { mVUup.mBit = true; DevCon.Warning("M Bit on single instruction");}
	if (curI & _Ibit_)  { mVUlow.isNOP = true; mVUup.iBit = true; DevCon.Warning("I Bit on single instruction");}
	else			    { incPC(-1); mVUopL(mVU, 0); incPC(1); }
	mVUsetCycles(mVU);
	mVUinfo.readQ  =  mVU.q;
	mVUinfo.writeQ = !mVU.q;
	mVUinfo.readP  =  mVU.p;
	mVUinfo.writeP = !mVU.p;
	mVUcount++;
	mVUsetFlagInfo(mVU);
	incPC(1);


	mVUsetFlags(mVU, mFC);           // Sets Up Flag instances
	mVUoptimizePipeState(mVU);       // Optimize the End Pipeline State for nicer Block Linking
	mVUdebugPrintBlocks(mVU, false); // Prints Start/End PC of blocks executed, for debugging...
	mVUtestCycles(mVU);              // Update VU Cycles and Exit Early if Necessary

	// Second Pass
	iPC = startPC / 4;
	setCode();

	if (mVUup.mBit) {
		xOR(ptr32[&mVU.regs().flags], VUFLAG_MFLAGSET);
	}
	mVUexecuteInstruction(mVU);

	mVUincCycles(mVU, 1); //Just incase the is XGKick
	if (mVUinfo.doXGKICK) {
		mVU_XGKICK_DELAY(mVU, true);
	}

	return thisPtr;
}

void mVUDoDBit(microVU& mVU, microFlagCycles* mFC)
{
	xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
	xForwardJump32 eJMP(Jcc_Zero);
	xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x200 : 0x2));
	xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
	incPC(1);
	mVUDTendProgram(mVU, mFC, 1);
	incPC(-1);
	eJMP.SetTarget();
}

void mVUDoTBit(microVU& mVU, microFlagCycles* mFC)
{
	xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
	xForwardJump32 eJMP(Jcc_Zero);
	xOR(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? 0x400 : 0x4));
	xOR(ptr32[&mVU.regs().flags], VUFLAG_INTCINTERRUPT);
	incPC(1);
	mVUDTendProgram(mVU, mFC, 1);
	incPC(-1);

	eJMP.SetTarget();
}

void mVUSaveFlags(microVU& mVU,microFlagCycles &mFC, microFlagCycles &mFCBackup)
{
	memcpy(&mFCBackup, &mFC, sizeof(microFlagCycles));
	mVUsetFlags(mVU, mFCBackup);	   // Sets Up Flag instances
}
void* mVUcompile(microVU& mVU, u32 startPC, uptr pState) {
	
	microFlagCycles mFC;
	u8*				thisPtr  = x86Ptr;
	const u32		endCount = (((microRegInfo*)pState)->blockType) ? 1 : (mVU.microMemSize / 8);

	// First Pass
	iPC = startPC / 4;
	mVUsetupRange(mVU, startPC, 1); // Setup Program Bounds/Range
	mVU.regAlloc->reset(); // Reset regAlloc
	mVUinitFirstPass(mVU, pState, thisPtr);
	mVUbranch = 0;
	for(int branch = 0; mVUcount < endCount;) {
		incPC(1);
		startLoop(mVU);
		mVUincCycles(mVU, 1);
		mVUopU(mVU, 0);
		mVUcheckBadOp(mVU);
		if (curI & _Ebit_)  { eBitPass1(mVU, branch); }
		
		if (curI & _Mbit_)  { mVUup.mBit = true; }
		
		if (curI & _Ibit_)  { mVUlow.isNOP = true; mVUup.iBit = true; }
		else                { incPC(-1); mVUopL(mVU, 0); incPC(1); }
		if (curI & _Dbit_)  { mVUup.dBit = true; }
		if (curI & _Tbit_)  { mVUup.tBit = true; }
		mVUsetCycles(mVU);
		mVUinfo.readQ  =  mVU.q;
		mVUinfo.writeQ = !mVU.q;
		mVUinfo.readP  =  mVU.p;
		mVUinfo.writeP = !mVU.p;
		mVUcount++;

		if (branch >= 2) {
			mVUinfo.isEOB = true;

			if (branch == 3) {
				mVUinfo.isBdelay = true;
			}

			branchWarning(mVU);
			break;
		}
		else if (branch == 1) {
			branch = 2;
		}

		if (mVUbranch) {
			mVUsetFlagInfo(mVU);
			eBitWarning(mVU);
			branch = 3;
			mVUbranch = 0;
		}

		if (mVUinfo.isEOB)
			break;

		incPC(1);
	}

	// Fix up vi15 const info for propagation through blocks
	mVUregs.vi15  = (doConstProp && mVUconstReg[15].isValid) ? (u16)mVUconstReg[15].regValue : 0;
	mVUregs.vi15v = (doConstProp && mVUconstReg[15].isValid) ? 1 : 0;
		
	mVUsetFlags(mVU, mFC);           // Sets Up Flag instances
	mVUoptimizePipeState(mVU);       // Optimize the End Pipeline State for nicer Block Linking
	mVUdebugPrintBlocks(mVU, false); // Prints Start/End PC of blocks executed, for debugging...
	mVUtestCycles(mVU);              // Update VU Cycles and Exit Early if Necessary

	// Second Pass
	iPC = mVUstartPC;
	setCode();
	mVUbranch = 0;
	u32 x = 0;
	for( ; x < endCount; x++) {
		if (mVUinfo.isEOB)			{ handleBadOp(mVU, x); x = 0xffff; } // handleBadOp currently just prints a warning
		if (mVUup.mBit)				{ xOR(ptr32[&mVU.regs().flags], VUFLAG_MFLAGSET); }
		mVUexecuteInstruction(mVU);
		if(!mVUinfo.isBdelay && !mVUlow.branch) //T/D Bit on branch is handled after the branch, branch delay slots are executed.
		{
			if (mVUup.tBit) {
				mVUDoTBit(mVU, &mFC);
			}
			else if (mVUup.dBit && doDBitHandling) {
				mVUDoDBit(mVU, &mFC);
			}
		}

		if (mVUinfo.doXGKICK) {
			mVU_XGKICK_DELAY(mVU, true);
		}

		if (isEvilBlock) {
			mVUsetupRange(mVU, xPC, false);
			normJumpCompile(mVU, mFC, true);
			return thisPtr;
		}
		else if (!mVUinfo.isBdelay) {
			incPC(1);
		}
		else {
			mVUsetupRange(mVU, xPC, false);
			mVUdebugPrintBlocks(mVU, true);
			incPC(-3); // Go back to branch opcode
		
			switch (mVUlow.branch) {
				case 1: case 2:  normBranch(mVU, mFC);			  return thisPtr; // B/BAL
				case 9: case 10: normJump  (mVU, mFC);			  return thisPtr; // JR/JALR
				case 3: condBranch(mVU, mFC, Jcc_Equal);		  return thisPtr; // IBEQ
				case 4: condBranch(mVU, mFC, Jcc_GreaterOrEqual); return thisPtr; // IBGEZ
				case 5: condBranch(mVU, mFC, Jcc_Greater);		  return thisPtr; // IBGTZ
				case 6: condBranch(mVU, mFC, Jcc_LessOrEqual);	  return thisPtr; // IBLEQ
				case 7: condBranch(mVU, mFC, Jcc_Less);			  return thisPtr; // IBLTZ
				case 8: condBranch(mVU, mFC, Jcc_NotEqual);		  return thisPtr; // IBNEQ
			}
			
		}
	}
	if ((x == endCount) && (x!=1)) { Console.Error("microVU%d: Possible infinite compiling loop!", mVU.index); }
	
	// E-bit End
	mVUsetupRange(mVU, xPC-8, false);
	mVUendProgram(mVU, &mFC, 1);
	return thisPtr;
}

// Returns the entry point of the block (compiles it if not found)
__fi void* mVUentryGet(microVU& mVU, microBlockManager* block, u32 startPC, uptr pState) {
	microBlock* pBlock = block->search((microRegInfo*)pState);
	if (pBlock) return pBlock->x86ptrStart;
	else	 {  return mVUcompile(mVU, startPC, pState);}
}

 // Search for Existing Compiled Block (if found, return x86ptr; else, compile and return x86ptr)
__fi void* mVUblockFetch(microVU& mVU, u32 startPC, uptr pState) {

	pxAssertDev((startPC & 7) == 0,				pxsFmt("microVU%d: unaligned startPC=0x%04x", mVU.index, startPC) );
	pxAssertDev( startPC <= mVU.microMemSize-8,	pxsFmt("microVU%d: invalid startPC=0x%04x",   mVU.index, startPC) );
	startPC &= mVU.microMemSize-8;

	blockCreate(startPC/8);
	return mVUentryGet(mVU, mVUblocks[startPC/8], startPC, pState);
}

// mVUcompileJIT() - Called By JR/JALR during execution
_mVUt void* __fastcall mVUcompileJIT(u32 startPC, uptr ptr) {
	if (doJumpAsSameProgram) { // Treat jump as part of same microProgram
		return mVUblockFetch(mVUx, startPC, ptr);
	}
	if (doJumpCaching) { // When doJumpCaching, ptr is a microBlock pointer
		microVU& mVU = mVUx;
		microBlock* pBlock = (microBlock*)ptr;
		microJumpCache& jc = pBlock->jumpCache[startPC/8];
		if (jc.prog && jc.prog == mVU.prog.quick[startPC/8].prog) return jc.x86ptrStart;
		void* v = mVUsearchProg<vuIndex>(startPC, (uptr)&pBlock->pStateEnd);
		jc.prog = mVU.prog.quick[startPC/8].prog;
		jc.x86ptrStart = v;
		return v;
	}
	else { // When !doJumpCaching, pBlock param is really a microRegInfo pointer
		return mVUsearchProg<vuIndex>(startPC, ptr); // Find and set correct program
	}
}
