// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "Common/CommonTypes.h"
#include "Common/CPUDetect.h"

#include "Core/PowerPC/Jit64/Jit.h"
#include "Core/PowerPC/Jit64/JitRegCache.h"

using namespace Gen;

static const u64 GC_ALIGNED16(psSignBits[2])      = {0x8000000000000000ULL, 0x0000000000000000ULL};
static const u64 GC_ALIGNED16(psSignBits2[2])     = {0x8000000000000000ULL, 0x8000000000000000ULL};
static const u64 GC_ALIGNED16(psAbsMask[2])       = {0x7FFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFFULL};
static const u64 GC_ALIGNED16(psAbsMask2[2])      = {0x7FFFFFFFFFFFFFFFULL, 0x7FFFFFFFFFFFFFFFULL};
static const u64 GC_ALIGNED16(psGeneratedQNaN[2]) = {0x7FF8000000000000ULL, 0x7FF8000000000000ULL};
static const double GC_ALIGNED16(half_qnan_and_s32_max[2]) = {0x7FFFFFFF, -0x80000};

X64Reg Jit64::fp_tri_op(int d, int a, int b, bool reversible, bool single, void (XEmitter::*avxOp)(X64Reg, X64Reg, const OpArg&),
                        void (XEmitter::*sseOp)(X64Reg, const OpArg&), bool packed, bool preserve_inputs, bool roundRHS)
{
	fpr.Lock(d, a, b);
	fpr.BindToRegister(d, d == a || d == b || !single);
	X64Reg dest = preserve_inputs ? XMM1 : fpr.RX(d);
	if (roundRHS)
	{
		if (d == a && !preserve_inputs)
		{
			Force25BitPrecision(XMM0, fpr.R(b), XMM1);
			(this->*sseOp)(fpr.RX(d), R(XMM0));
		}
		else
		{
			Force25BitPrecision(dest, fpr.R(b), XMM0);
			(this->*sseOp)(dest, fpr.R(a));
		}
	}
	else
	{
		avx_op(avxOp, sseOp, dest, fpr.R(a), fpr.R(b), packed, reversible);
	}
	return dest;
}

// We can avoid calculating FPRF if it's not needed; every float operation resets it, so
// if it's going to be clobbered in a future instruction before being read, we can just
// not calculate it.
void Jit64::SetFPRFIfNeeded(X64Reg xmm)
{
	// As far as we know, the games that use this flag only need FPRF for fmul and fmadd, but
	// FPRF is fast enough in JIT that we might as well just enable it for every float instruction
	// if the FPRF flag is set.
	if (SConfig::GetInstance().m_LocalCoreStartupParameter.bFPRF && js.op->wantsFPRF)
		SetFPRF(xmm);
}

void Jit64::HandleNaNs(UGeckoInstruction inst, X64Reg xmm_out, X64Reg xmm, X64Reg clobber)
{
	//                      | PowerPC  | x86
	// ---------------------+----------+---------
	// input NaN precedence | 1*3 + 2  | 1*2 + 3
	// generated QNaN       | positive | negative
	//
	// Dragon Ball: Revenge of King Piccolo requires generated NaNs
	// to be positive, so we'll have to handle them manually.

	if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bAccurateNaNs)
	{
		if (xmm_out != xmm)
			MOVAPD(xmm_out, R(xmm));
		return;
	}

	_assert_(xmm != clobber);

	std::vector<u32> inputs;
	u32 a = inst.FA, b = inst.FB, c = inst.FC;
	for (u32 i : {a, b, c})
	{
		if (!js.op->fregsIn[i])
			continue;
		if (std::find(inputs.begin(), inputs.end(), i) == inputs.end())
			inputs.push_back(i);
	}
	if (inst.OPCD != 4)
	{
		// not paired-single
		UCOMISD(xmm, R(xmm));
		FixupBranch handle_nan = J_CC(CC_P, true);
		SwitchToFarCode();
			SetJumpTarget(handle_nan);
			std::vector<FixupBranch> fixups;
			for (u32 x : inputs)
			{
				MOVDDUP(xmm, fpr.R(x));
				UCOMISD(xmm, R(xmm));
				fixups.push_back(J_CC(CC_P));
			}
			MOVDDUP(xmm, M(psGeneratedQNaN));
			for (FixupBranch fixup : fixups)
				SetJumpTarget(fixup);
			FixupBranch done = J(true);
		SwitchToNearCode();
		SetJumpTarget(done);
	}
	else
	{
		// paired-single
		std::reverse(inputs.begin(), inputs.end());
		if (cpu_info.bSSE4_1)
		{
			avx_op(&XEmitter::VCMPPD, &XEmitter::CMPPD, clobber, R(xmm), R(xmm), CMP_UNORD);
			PTEST(clobber, R(clobber));
			FixupBranch handle_nan = J_CC(CC_NZ, true);
			SwitchToFarCode();
				SetJumpTarget(handle_nan);
				_assert_msg_(DYNA_REC, clobber == XMM0, "BLENDVPD implicitly uses XMM0");
				BLENDVPD(xmm, M(psGeneratedQNaN));
				for (u32 x : inputs)
				{
					avx_op(&XEmitter::VCMPPD, &XEmitter::CMPPD, clobber, fpr.R(x), fpr.R(x), CMP_UNORD);
					BLENDVPD(xmm, fpr.R(x));
				}
				FixupBranch done = J(true);
			SwitchToNearCode();
			SetJumpTarget(done);
		}
		else
		{
			// SSE2 fallback
			X64Reg tmp = fpr.GetFreeXReg();
			fpr.FlushLockX(tmp);
			MOVAPD(clobber, R(xmm));
			CMPPD(clobber, R(clobber), CMP_UNORD);
			MOVMSKPD(RSCRATCH, R(clobber));
			TEST(32, R(RSCRATCH), R(RSCRATCH));
			FixupBranch handle_nan = J_CC(CC_NZ, true);
			SwitchToFarCode();
				SetJumpTarget(handle_nan);
				MOVAPD(tmp, R(clobber));
				PANDN(clobber, R(xmm));
				PAND(tmp, M(psGeneratedQNaN));
				POR(tmp, R(clobber));
				MOVAPD(xmm, R(tmp));
				for (u32 x : inputs)
				{
					MOVAPD(clobber, fpr.R(x));
					CMPPD(clobber, R(clobber), CMP_ORD);
					MOVAPD(tmp, R(clobber));
					PANDN(clobber, fpr.R(x));
					PAND(xmm, R(tmp));
					POR(xmm, R(clobber));
				}
				FixupBranch done = J(true);
			SwitchToNearCode();
			SetJumpTarget(done);
			fpr.UnlockX(tmp);
		}
	}
	if (xmm_out != xmm)
		MOVAPD(xmm_out, R(xmm));
}

void Jit64::fp_arith(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);

	int a = inst.FA;
	int b = inst.FB;
	int c = inst.FC;
	int d = inst.FD;
	int arg2 = inst.SUBOP5 == 25 ? c : b;

	bool single = inst.OPCD == 4 || inst.OPCD == 59;
	// If both the inputs are known to have identical top and bottom halves, we can skip the MOVDDUP at the end by
	// using packed arithmetic instead.
	bool packed = inst.OPCD == 4 || (inst.OPCD == 59 &&
	                                 jit->js.op->fprIsDuplicated[a] &&
	                                 jit->js.op->fprIsDuplicated[arg2]);
	// Packed divides are slower than scalar divides on basically all x86, so this optimization isn't worth it in that case.
	// Atoms (and a few really old CPUs) are also slower on packed operations than scalar ones.
	if (inst.OPCD == 59 && (inst.SUBOP5 == 18 || cpu_info.bAtom))
		packed = false;

	bool round_input = single && !jit->js.op->fprIsSingle[inst.FC];
	bool preserve_inputs = SConfig::GetInstance().m_LocalCoreStartupParameter.bAccurateNaNs;

	X64Reg dest = INVALID_REG;
	switch (inst.SUBOP5)
	{
	case 18: dest = fp_tri_op(d, a, b, false, single, packed ? &XEmitter::VDIVPD : &XEmitter::VDIVSD,
	                          packed ? &XEmitter::DIVPD : &XEmitter::DIVSD, packed, preserve_inputs); break;
	case 20: dest = fp_tri_op(d, a, b, false, single, packed ? &XEmitter::VSUBPD : &XEmitter::VSUBSD,
	                          packed ? &XEmitter::SUBPD : &XEmitter::SUBSD, packed, preserve_inputs); break;
	case 21: dest = fp_tri_op(d, a, b, true, single, packed ? &XEmitter::VADDPD : &XEmitter::VADDSD,
	                          packed ? &XEmitter::ADDPD : &XEmitter::ADDSD, packed, preserve_inputs); break;
	case 25: dest = fp_tri_op(d, a, c, true, single, packed ? &XEmitter::VMULPD : &XEmitter::VMULSD,
	                          packed ? &XEmitter::MULPD : &XEmitter::MULSD, packed, preserve_inputs, round_input); break;
	default:
		_assert_msg_(DYNA_REC, 0, "fp_arith WTF!!!");
	}
	HandleNaNs(inst, fpr.RX(d), dest);
	if (single)
		ForceSinglePrecision(fpr.RX(d), fpr.R(d), packed, true);
	SetFPRFIfNeeded(fpr.RX(d));
	fpr.UnlockAll();
}

void Jit64::fmaddXX(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);

	int a = inst.FA;
	int b = inst.FB;
	int c = inst.FC;
	int d = inst.FD;
	bool single = inst.OPCD == 4 || inst.OPCD == 59;
	bool round_input = single && !jit->js.op->fprIsSingle[c];
	bool packed = inst.OPCD == 4 ||
	              (!cpu_info.bAtom && single &&
	               jit->js.op->fprIsDuplicated[a] &&
	               jit->js.op->fprIsDuplicated[b] &&
	               jit->js.op->fprIsDuplicated[c]);

	fpr.Lock(a, b, c, d);

	switch(inst.SUBOP5)
	{
	case 14:
		MOVDDUP(XMM1, fpr.R(c));
		if (round_input)
			Force25BitPrecision(XMM1, R(XMM1), XMM0);
		break;
	case 15:
		avx_op(&XEmitter::VSHUFPD, &XEmitter::SHUFPD, XMM1, fpr.R(c), fpr.R(c), 3);
		if (round_input)
			Force25BitPrecision(XMM1, R(XMM1), XMM0);
		break;
	default:
		bool special = inst.SUBOP5 == 30 && (!cpu_info.bFMA || Core::g_want_determinism);
		X64Reg tmp1 = special ? XMM0 : XMM1;
		X64Reg tmp2 = special ? XMM1 : XMM0;
		if (single && round_input)
			Force25BitPrecision(tmp1, fpr.R(c), tmp2);
		else
			MOVAPD(tmp1, fpr.R(c));
		break;
	}

	// While we don't know if any games are actually affected (replays seem to work with all the usual
	// suspects for desyncing), netplay and other applications need absolute perfect determinism, so
	// be extra careful and don't use FMA, even if in theory it might be okay.
	// Note that FMA isn't necessarily less correct (it may actually be closer to correct) compared
	// to what the Gekko does here; in deterministic mode, the important thing is multiple Dolphin
	// instances on different computers giving identical results.
	if (cpu_info.bFMA && !Core::g_want_determinism)
	{
		// Statistics suggests b is a lot less likely to be unbound in practice, so
		// if we have to pick one of a or b to bind, let's make it b.
		fpr.BindToRegister(b, true, false);
		switch (inst.SUBOP5)
		{
		case 28: //msub
			if (packed)
				VFMSUB132PD(XMM1, fpr.RX(b), fpr.R(a));
			else
				VFMSUB132SD(XMM1, fpr.RX(b), fpr.R(a));
			break;
		case 14: //madds0
		case 15: //madds1
		case 29: //madd
			if (packed)
				VFMADD132PD(XMM1, fpr.RX(b), fpr.R(a));
			else
				VFMADD132SD(XMM1, fpr.RX(b), fpr.R(a));
			break;
			// PowerPC and x86 define NMADD/NMSUB differently
			// x86: D = -A*C (+/-) B
			// PPC: D = -(A*C (+/-) B)
			// so we have to swap them; the ADD/SUB here isn't a typo.
		case 30: //nmsub
			if (packed)
				VFNMADD132PD(XMM1, fpr.RX(b), fpr.R(a));
			else
				VFNMADD132SD(XMM1, fpr.RX(b), fpr.R(a));
			break;
		case 31: //nmadd
			if (packed)
				VFNMSUB132PD(XMM1, fpr.RX(b), fpr.R(a));
			else
				VFNMSUB132SD(XMM1, fpr.RX(b), fpr.R(a));
			break;
		}
	}
	else if (inst.SUBOP5 == 30) //nmsub
	{
		// We implement nmsub a little differently ((b - a*c) instead of -(a*c - b)), so handle it separately.
		MOVAPD(XMM1, fpr.R(b));
		if (packed)
		{
			MULPD(XMM0, fpr.R(a));
			SUBPD(XMM1, R(XMM0));
		}
		else
		{
			MULSD(XMM0, fpr.R(a));
			SUBSD(XMM1, R(XMM0));
		}
	}
	else
	{
		if (packed)
		{
			MULPD(XMM1, fpr.R(a));
			if (inst.SUBOP5 == 28) //msub
				SUBPD(XMM1, fpr.R(b));
			else                   //(n)madd(s[01])
				ADDPD(XMM1, fpr.R(b));
		}
		else
		{
			MULSD(XMM1, fpr.R(a));
			if (inst.SUBOP5 == 28)
				SUBSD(XMM1, fpr.R(b));
			else
				ADDSD(XMM1, fpr.R(b));
		}
		if (inst.SUBOP5 == 31) //nmadd
			PXOR(XMM1, M(packed ? psSignBits2 : psSignBits));
	}
	fpr.BindToRegister(d, !single);
	if (single)
	{
		HandleNaNs(inst, fpr.RX(d), XMM1);
		ForceSinglePrecision(fpr.RX(d), fpr.R(d), packed, true);
	}
	else
	{
		HandleNaNs(inst, XMM1, XMM1);
		MOVSD(fpr.RX(d), R(XMM1));
	}
	SetFPRFIfNeeded(fpr.RX(d));
	fpr.UnlockAll();
}

void Jit64::fsign(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);

	int d = inst.FD;
	int b = inst.FB;
	bool packed = inst.OPCD == 4;

	fpr.Lock(b, d);
	OpArg src = fpr.R(b);
	fpr.BindToRegister(d, false);

	switch (inst.SUBOP10)
	{
	case 40: // neg
		avx_op(&XEmitter::VPXOR, &XEmitter::PXOR, fpr.RX(d), src, M(packed ? psSignBits2 : psSignBits), packed);
		break;
	case 136: // nabs
		avx_op(&XEmitter::VPOR, &XEmitter::POR, fpr.RX(d), src, M(packed ? psSignBits2 : psSignBits), packed);
		break;
	case 264: // abs
		avx_op(&XEmitter::VPAND, &XEmitter::PAND, fpr.RX(d), src, M(packed ? psAbsMask2 : psAbsMask), packed);
		break;
	default:
		PanicAlert("fsign bleh");
		break;
	}
	fpr.UnlockAll();
}

void Jit64::fselx(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);

	int d = inst.FD;
	int a = inst.FA;
	int b = inst.FB;
	int c = inst.FC;

	bool packed = inst.OPCD == 4; // ps_sel

	fpr.Lock(a, b, c, d);
	PXOR(XMM0, R(XMM0));
	// This condition is very tricky; there's only one right way to handle both the case of
	// negative/positive zero and NaN properly.
	// (a >= -0.0 ? c : b) transforms into (0 > a ? b : c), hence the NLE.
	if (packed)
		CMPPD(XMM0, fpr.R(a), CMP_NLE);
	else
		CMPSD(XMM0, fpr.R(a), CMP_NLE);

	if (cpu_info.bSSE4_1)
	{
		MOVAPD(XMM1, fpr.R(c));
		BLENDVPD(XMM1, fpr.R(b));
	}
	else
	{
		MOVAPD(XMM1, R(XMM0));
		PAND(XMM0, fpr.R(b));
		PANDN(XMM1, fpr.R(c));
		POR(XMM1, R(XMM0));
	}

	fpr.BindToRegister(d, !packed);
	if (packed)
		MOVAPD(fpr.RX(d), R(XMM1));
	else
		MOVSD(fpr.RX(d), R(XMM1));
	fpr.UnlockAll();
}

void Jit64::fmrx(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);

	int d = inst.FD;
	int b = inst.FB;

	if (d == b)
		return;

	fpr.Lock(b, d);

	if (fpr.IsBound(d))
	{
		// We don't need to load d, but if it is loaded, we need to mark it as dirty.
		fpr.BindToRegister(d);
		// We have to use MOVLPD if b isn't loaded because "MOVSD reg, mem" sets the upper bits (64+)
		// to zero and we don't want that.
		if (!fpr.R(b).IsSimpleReg())
			MOVLPD(fpr.RX(d), fpr.R(b));
		else
			MOVSD(fpr.R(d), fpr.RX(b));
	}
	else
	{
		fpr.BindToRegister(b, true, false);
		MOVSD(fpr.R(d), fpr.RX(b));
	}

	fpr.UnlockAll();
}

void Jit64::FloatCompare(UGeckoInstruction inst, bool upper)
{
	bool fprf = SConfig::GetInstance().m_LocalCoreStartupParameter.bFPRF && js.op->wantsFPRF;
	//bool ordered = !!(inst.SUBOP10 & 32);
	int a = inst.FA;
	int b = inst.FB;
	int crf = inst.CRFD;
	int output[4] = { CR_SO, CR_EQ, CR_GT, CR_LT };

	// Merge neighboring fcmp and cror (the primary use of cror).
	UGeckoInstruction next = js.op[1].inst;
	if (analyzer.HasOption(PPCAnalyst::PPCAnalyzer::OPTION_CROR_MERGE) &&
	    MergeAllowedNextInstructions(1) && next.OPCD == 19 && next.SUBOP10 == 449 &&
	    (next.CRBA >> 2) == crf && (next.CRBB >> 2) == crf && (next.CRBD >> 2) == crf)
	{
		js.skipInstructions = 1;
		js.downcountAmount++;
		int dst = 3 - (next.CRBD & 3);
		output[3 - (next.CRBD & 3)] &= ~(1 << dst);
		output[3 - (next.CRBA & 3)] |= 1 << dst;
		output[3 - (next.CRBB & 3)] |= 1 << dst;
	}

	fpr.Lock(a, b);
	fpr.BindToRegister(b, true, false);

	if (fprf)
		AND(32, PPCSTATE(fpscr), Imm32(~FPRF_MASK));

	if (upper)
	{
		fpr.BindToRegister(a, true, false);
		MOVHLPS(XMM0, fpr.RX(a));
		MOVHLPS(XMM1, fpr.RX(b));
		UCOMISD(XMM1, R(XMM0));
	}
	else
	{
		UCOMISD(fpr.RX(b), fpr.R(a));
	}

	FixupBranch pNaN, pLesser, pGreater;
	FixupBranch continue1, continue2, continue3;

	if (a != b)
	{
		// if B > A, goto Lesser's jump target
		pLesser = J_CC(CC_A);
	}

	// if (B != B) or (A != A), goto NaN's jump target
	pNaN = J_CC(CC_P);

	if (a != b)
	{
		// if B < A, goto Greater's jump target
		// JB can't precede the NaN check because it doesn't test ZF
		pGreater = J_CC(CC_B);
	}

	MOV(64, R(RSCRATCH), Imm64(PPCCRToInternal(output[CR_EQ_BIT])));
	if (fprf)
		OR(32, PPCSTATE(fpscr), Imm32(CR_EQ << FPRF_SHIFT));

	continue1 = J();

	SetJumpTarget(pNaN);
	MOV(64, R(RSCRATCH), Imm64(PPCCRToInternal(output[CR_SO_BIT])));
	if (fprf)
		OR(32, PPCSTATE(fpscr), Imm32(CR_SO << FPRF_SHIFT));

	if (a != b)
	{
		continue2 = J();

		SetJumpTarget(pGreater);
		MOV(64, R(RSCRATCH), Imm64(PPCCRToInternal(output[CR_GT_BIT])));
		if (fprf)
			OR(32, PPCSTATE(fpscr), Imm32(CR_GT << FPRF_SHIFT));
		continue3 = J();

		SetJumpTarget(pLesser);
		MOV(64, R(RSCRATCH), Imm64(PPCCRToInternal(output[CR_LT_BIT])));
		if (fprf)
			OR(32, PPCSTATE(fpscr), Imm32(CR_LT << FPRF_SHIFT));
	}

	SetJumpTarget(continue1);
	if (a != b)
	{
		SetJumpTarget(continue2);
		SetJumpTarget(continue3);
	}

	MOV(64, PPCSTATE(cr_val[crf]), R(RSCRATCH));
	fpr.UnlockAll();
}

void Jit64::fcmpx(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);

	FloatCompare(inst);
}

void Jit64::fctiwx(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);

	int d = inst.RD;
	int b = inst.RB;
	fpr.Lock(d, b);
	fpr.BindToRegister(d);

	// Intel uses 0x80000000 as a generic error code while PowerPC uses clamping:
	//
	// input       | output fctiw | output CVTPD2DQ
	// ------------+--------------+----------------
	// > +2^31 - 1 | 0x7fffffff   | 0x80000000
	// < -2^31     | 0x80000000   | 0x80000000
	// any NaN     | 0x80000000   | 0x80000000
	//
	// The upper 32 bits of the result are set to 0xfff80000,
	// except for -0.0 where they are set to 0xfff80001 (TODO).

	MOVAPD(XMM0, M(half_qnan_and_s32_max));
	MINSD(XMM0, fpr.R(b));
	switch (inst.SUBOP10)
	{
		// fctiwx
		case 14:
			CVTPD2DQ(XMM0, R(XMM0));
			break;

		// fctiwzx
		case 15:
			CVTTPD2DQ(XMM0, R(XMM0));
			break;
	}
	// d[64+] must not be modified
	MOVSD(fpr.R(d), XMM0);
	fpr.UnlockAll();
}

void Jit64::frspx(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	int b = inst.FB;
	int d = inst.FD;
	bool packed = jit->js.op->fprIsDuplicated[b] && !cpu_info.bAtom;

	fpr.Lock(b, d);
	OpArg src = fpr.R(b);
	fpr.BindToRegister(d, false);
	ForceSinglePrecision(fpr.RX(d), src, packed, true);
	SetFPRFIfNeeded(fpr.RX(d));
	fpr.UnlockAll();
}

void Jit64::frsqrtex(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);
	int b = inst.FB;
	int d = inst.FD;

	gpr.FlushLockX(RSCRATCH_EXTRA);
	fpr.Lock(b, d);
	fpr.BindToRegister(d);
	MOVAPD(XMM0, fpr.R(b));
	CALL((void *)asm_routines.frsqrte);
	MOVSD(fpr.R(d), XMM0);
	SetFPRFIfNeeded(fpr.RX(d));
	fpr.UnlockAll();
	gpr.UnlockAllX();
}

void Jit64::fresx(UGeckoInstruction inst)
{
	INSTRUCTION_START
	JITDISABLE(bJITFloatingPointOff);
	FALLBACK_IF(inst.Rc);
	int b = inst.FB;
	int d = inst.FD;

	gpr.FlushLockX(RSCRATCH_EXTRA);
	fpr.Lock(b, d);
	fpr.BindToRegister(d);
	MOVAPD(XMM0, fpr.R(b));
	CALL((void *)asm_routines.fres);
	MOVSD(fpr.R(d), XMM0);
	SetFPRFIfNeeded(fpr.RX(d));
	fpr.UnlockAll();
	gpr.UnlockAllX();
}
