DSPJit: Rework overflow and carry handling

This commit is contained in:
Pokechu22 2021-08-19 17:12:23 -07:00
parent 4508ca6734
commit 3ee605d699
5 changed files with 390 additions and 382 deletions

View File

@ -228,6 +228,7 @@ private:
void get_long_prod(Gen::X64Reg long_prod = Gen::RAX); void get_long_prod(Gen::X64Reg long_prod = Gen::RAX);
void get_long_prod_round_prodl(Gen::X64Reg long_prod = Gen::RAX); void get_long_prod_round_prodl(Gen::X64Reg long_prod = Gen::RAX);
void set_long_prod(); void set_long_prod();
void dsp_convert_long_acc(Gen::X64Reg long_acc); // s64 -> s40
void round_long_acc(Gen::X64Reg long_acc = Gen::EAX); void round_long_acc(Gen::X64Reg long_acc = Gen::EAX);
void set_long_acc(int _reg, Gen::X64Reg acc = Gen::EAX); void set_long_acc(int _reg, Gen::X64Reg acc = Gen::EAX);
void get_acc_h(int _reg, Gen::X64Reg acc = Gen::EAX, bool sign = true); void get_acc_h(int _reg, Gen::X64Reg acc = Gen::EAX, bool sign = true);
@ -246,7 +247,16 @@ private:
// CC helpers // CC helpers
void Update_SR_Register64(Gen::X64Reg val = Gen::EAX, Gen::X64Reg scratch = Gen::EDX); void Update_SR_Register64(Gen::X64Reg val = Gen::EAX, Gen::X64Reg scratch = Gen::EDX);
void Update_SR_Register64_Carry(Gen::X64Reg val, Gen::X64Reg carry_ovfl, bool carry_eq = false); void UpdateSR64AddSub(Gen::X64Reg val1, Gen::X64Reg val2, Gen::X64Reg result, Gen::X64Reg scratch,
bool subtract);
void UpdateSR64Add(Gen::X64Reg val1, Gen::X64Reg val2, Gen::X64Reg result, Gen::X64Reg scratch)
{
UpdateSR64AddSub(val1, val2, result, scratch, false);
}
void UpdateSR64Sub(Gen::X64Reg val1, Gen::X64Reg val2, Gen::X64Reg result, Gen::X64Reg scratch)
{
UpdateSR64AddSub(val1, val2, result, scratch, true);
}
void Update_SR_Register16(Gen::X64Reg val = Gen::EAX); void Update_SR_Register16(Gen::X64Reg val = Gen::EAX);
void Update_SR_Register16_OverS32(Gen::X64Reg val = Gen::EAX); void Update_SR_Register16_OverS32(Gen::X64Reg val = Gen::EAX);

View File

@ -64,18 +64,19 @@ void DSPEmitter::andcf(const UDSPInstruction opc)
if (FlagsNeeded()) if (FlagsNeeded())
{ {
const u8 reg = (opc >> 8) & 0x1; const u8 reg = (opc >> 8) & 0x1;
// u16 imm = dsp_fetch_code(); // const u16 imm = m_dsp_core.DSPState().FetchInstruction();
const u16 imm = m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1); const u16 imm = m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1);
// u16 val = dsp_get_acc_m(reg); // const u16 val = GetAccMid(reg);
get_acc_m(reg); X64Reg val = RAX;
// Update_SR_LZ(((val & imm) == imm) ? true : false); get_acc_m(reg, val);
// if ((val & imm) == imm) // UpdateSRLogicZero((val & imm) == imm);
// g_dsp.r.sr |= SR_LOGIC_ZERO; // if ((val & imm) == imm)
// else // g_dsp.r.sr |= SR_LOGIC_ZERO;
// g_dsp.r.sr &= ~SR_LOGIC_ZERO; // else
// g_dsp.r.sr &= ~SR_LOGIC_ZERO;
const OpArg sr_reg = m_gpr.GetReg(DSP_REG_SR); const OpArg sr_reg = m_gpr.GetReg(DSP_REG_SR);
AND(16, R(RAX), Imm16(imm)); AND(16, R(val), Imm16(imm));
CMP(16, R(RAX), Imm16(imm)); CMP(16, R(val), Imm16(imm));
FixupBranch notLogicZero = J_CC(CC_NE); FixupBranch notLogicZero = J_CC(CC_NE);
OR(16, sr_reg, Imm16(SR_LOGIC_ZERO)); OR(16, sr_reg, Imm16(SR_LOGIC_ZERO));
FixupBranch exit = J(); FixupBranch exit = J();
@ -99,17 +100,18 @@ void DSPEmitter::andf(const UDSPInstruction opc)
if (FlagsNeeded()) if (FlagsNeeded())
{ {
const u8 reg = (opc >> 8) & 0x1; const u8 reg = (opc >> 8) & 0x1;
// u16 imm = dsp_fetch_code(); // const u16 imm = m_dsp_core.DSPState().FetchInstruction();
const u16 imm = m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1); const u16 imm = m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1);
// u16 val = dsp_get_acc_m(reg); // const u16 val = GetAccMid(reg);
get_acc_m(reg); X64Reg val = RAX;
// Update_SR_LZ(((val & imm) == 0) ? true : false); get_acc_m(reg, val);
// if ((val & imm) == 0) // UpdateSRLogicZero((val & imm) == 0);
// g_dsp.r.sr |= SR_LOGIC_ZERO; // if ((val & imm) == 0)
// else // g_dsp.r.sr |= SR_LOGIC_ZERO;
// g_dsp.r.sr &= ~SR_LOGIC_ZERO; // else
// g_dsp.r.sr &= ~SR_LOGIC_ZERO;
const OpArg sr_reg = m_gpr.GetReg(DSP_REG_SR); const OpArg sr_reg = m_gpr.GetReg(DSP_REG_SR);
TEST(16, R(RAX), Imm16(imm)); TEST(16, R(val), Imm16(imm));
FixupBranch notLogicZero = J_CC(CC_NE); FixupBranch notLogicZero = J_CC(CC_NE);
OR(16, sr_reg, Imm16(SR_LOGIC_ZERO)); OR(16, sr_reg, Imm16(SR_LOGIC_ZERO));
FixupBranch exit = J(); FixupBranch exit = J();
@ -167,18 +169,21 @@ void DSPEmitter::cmp(const UDSPInstruction opc)
{ {
if (FlagsNeeded()) if (FlagsNeeded())
{ {
// const s64 acc0 = GetLongAcc(0);
X64Reg acc0 = RAX;
get_long_acc(0, acc0);
// const s64 acc1 = GetLongAcc(1);
X64Reg acc1 = RDX;
get_long_acc(1, acc1);
// s64 res = dsp_convert_long_acc(acc0 - acc1);
X64Reg res = RCX;
MOV(64, R(res), R(acc0));
SUB(64, R(res), R(acc1));
dsp_convert_long_acc(RCX);
// UpdateSR64Sub(acc0, acc1, res);
X64Reg tmp1 = m_gpr.GetFreeXReg(); X64Reg tmp1 = m_gpr.GetFreeXReg();
// s64 acc0 = dsp_get_long_acc(0); UpdateSR64Sub(acc0, acc1, res, tmp1);
get_long_acc(0, tmp1);
MOV(64, R(RAX), R(tmp1));
// s64 acc1 = dsp_get_long_acc(1);
get_long_acc(1, RDX);
// s64 res = dsp_convert_long_acc(acc0 - acc1);
SUB(64, R(RAX), R(RDX));
// Update_SR_Register64(res, isCarry2(acc0, res), isOverflow(acc0, -acc1, res)); // CF ->
// influence on ABS/0xa100
NEG(64, R(RDX));
Update_SR_Register64_Carry(EAX, tmp1, true);
m_gpr.PutXReg(tmp1); m_gpr.PutXReg(tmp1);
} }
} }
@ -195,19 +200,22 @@ void DSPEmitter::cmpar(const UDSPInstruction opc)
u8 rreg = ((opc >> 12) & 0x1); u8 rreg = ((opc >> 12) & 0x1);
u8 sreg = (opc >> 11) & 0x1; u8 sreg = (opc >> 11) & 0x1;
// const s64 acc = GetLongAcc(sreg);
X64Reg acc = RAX;
get_long_acc(sreg, acc);
// s64 ax = GetAXHigh(rreg);
X64Reg ax = RDX;
get_ax_h(rreg, ax);
// ax <<= 16;
SHL(64, R(ax), Imm8(16));
// const s64 res = dsp_convert_long_acc(acc - ax);
X64Reg res = RCX;
MOV(64, R(res), R(acc));
SUB(64, R(res), R(ax));
dsp_convert_long_acc(res);
// UpdateSR64Sub(acc, ax, res);
X64Reg tmp1 = m_gpr.GetFreeXReg(); X64Reg tmp1 = m_gpr.GetFreeXReg();
// s64 sr = dsp_get_long_acc(sreg); UpdateSR64Sub(acc, ax, res, tmp1);
get_long_acc(sreg, tmp1);
MOV(64, R(RAX), R(tmp1));
// s64 rr = (s16)g_dsp.r.axh[rreg];
get_ax_h(rreg, RDX);
// rr <<= 16;
SHL(64, R(RDX), Imm8(16));
// s64 res = dsp_convert_long_acc(sr - rr);
SUB(64, R(RAX), R(RDX));
// Update_SR_Register64(res, isCarry2(sr, res), isOverflow(sr, -rr, res));
NEG(64, R(RDX));
Update_SR_Register64_Carry(EAX, tmp1, true);
m_gpr.PutXReg(tmp1); m_gpr.PutXReg(tmp1);
} }
} }
@ -224,19 +232,24 @@ void DSPEmitter::cmpi(const UDSPInstruction opc)
if (FlagsNeeded()) if (FlagsNeeded())
{ {
const u8 reg = (opc >> 8) & 0x1; const u8 reg = (opc >> 8) & 0x1;
const X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 val = GetLongAcc(reg);
// s64 val = dsp_get_long_acc(reg); X64Reg val = RAX;
get_long_acc(reg, tmp1); get_long_acc(reg, val);
MOV(64, R(RAX), R(tmp1)); // Immediate is considered to be at M level in the 40-bit accumulator.
// s64 imm = (s64)(s16)dsp_fetch_code() << 16; // Immediate is considered to be at M level in // s64 imm = static_cast<s16>(state.FetchInstruction());
// the 40-bit accumulator. // imm <<= 16;
const u16 imm = m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1); X64Reg imm_reg = RDX;
MOV(64, R(RDX), Imm64((s64)(s16)imm << 16)); s64 imm = static_cast<s16>(m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1));
// s64 res = dsp_convert_long_acc(val - imm); imm <<= 16;
SUB(64, R(RAX), R(RDX)); MOV(64, R(imm_reg), Imm64(imm));
// Update_SR_Register64(res, isCarry2(val, res), isOverflow(val, -imm, res)); // const s64 res = dsp_convert_long_acc(val - imm);
NEG(64, R(RDX)); X64Reg res = RCX;
Update_SR_Register64_Carry(EAX, tmp1, true); MOV(64, R(res), R(val));
SUB(64, R(res), R(imm_reg));
dsp_convert_long_acc(res);
// UpdateSR64Sub(val, imm, res);
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Sub(val, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1); m_gpr.PutXReg(tmp1);
} }
} }
@ -253,18 +266,23 @@ void DSPEmitter::cmpis(const UDSPInstruction opc)
if (FlagsNeeded()) if (FlagsNeeded())
{ {
u8 areg = (opc >> 8) & 0x1; u8 areg = (opc >> 8) & 0x1;
// s64 acc = dsp_get_long_acc(areg); // const s64 acc = GetLongAcc(areg);
X64Reg acc = RAX;
get_long_acc(areg, acc);
// s64 imm = static_cast<s8>(opc);
// imm <<= 16;
X64Reg imm_reg = RDX;
s64 imm = static_cast<s8>(opc);
imm <<= 16;
MOV(64, R(imm_reg), Imm64(imm));
// const s64 res = dsp_convert_long_acc(acc - imm);
X64Reg res = RCX;
MOV(64, R(res), R(acc));
SUB(64, R(res), R(imm_reg));
dsp_convert_long_acc(res);
// UpdateSR64Sub(acc, imm, res);
X64Reg tmp1 = m_gpr.GetFreeXReg(); X64Reg tmp1 = m_gpr.GetFreeXReg();
get_long_acc(areg, tmp1); UpdateSR64Sub(acc, imm_reg, res, tmp1);
MOV(64, R(RAX), R(tmp1));
// s64 val = (s8)opc;
// val <<= 16;
MOV(64, R(RDX), Imm64((s64)(s8)opc << 16));
// s64 res = dsp_convert_long_acc(acc - val);
SUB(64, R(RAX), R(RDX));
// Update_SR_Register64(res, isCarry2(acc, res), isOverflow(acc, -val, res));
NEG(64, R(RDX));
Update_SR_Register64_Carry(EAX, tmp1, true);
m_gpr.PutXReg(tmp1); m_gpr.PutXReg(tmp1);
} }
} }
@ -521,29 +539,27 @@ void DSPEmitter::addr(const UDSPInstruction opc)
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
u8 sreg = ((opc >> 9) & 0x3) + DSP_REG_AXL0; u8 sreg = ((opc >> 9) & 0x3) + DSP_REG_AXL0;
// s64 acc = dsp_get_long_acc(dreg); // const s64 acc = GetLongAcc(dreg);
X64Reg tmp1 = m_gpr.GetFreeXReg(); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // s64 ax = ...;
// s64 ax = (s16)g_dsp.r[sreg]; X64Reg ax = RDX;
dsp_op_read_reg(sreg, RDX, RegisterExtension::Sign); dsp_op_read_reg(sreg, ax, RegisterExtension::Sign);
// ax <<= 16; // ax <<= 16;
SHL(64, R(RDX), Imm8(16)); SHL(64, R(ax), Imm8(16));
// s64 res = acc + ax; // const s64 res = acc + ax;
ADD(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MRegSum(acc, ax));
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, ax, res)); // SetLongAcc(dreg, res);
set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc, ax, GetLongAcc(dreg));
set_long_acc(dreg, RCX); get_long_acc(dreg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, ax, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// ADDAX $acD, $axS // ADDAX $acD, $axS
@ -556,28 +572,25 @@ void DSPEmitter::addax(const UDSPInstruction opc)
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
u8 sreg = (opc >> 9) & 0x1; u8 sreg = (opc >> 9) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // const s64 ax = GetLongACX(sreg);
// s64 ax = dsp_get_long_acx(sreg); X64Reg ax = RDX;
get_long_acx(sreg, RDX); get_long_acx(sreg, ax);
// s64 res = acc + ax; // const s64 res = acc + ax;
ADD(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MRegSum(acc, ax));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, ax, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc, ax, GetLongAcc(dreg));
set_long_acc(dreg, RCX); get_long_acc(dreg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, ax, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// ADD $acD, $ac(1-D) // ADD $acD, $ac(1-D)
@ -589,28 +602,25 @@ void DSPEmitter::add(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc0 = GetLongAcc(dreg);
// s64 acc0 = dsp_get_long_acc(dreg); X64Reg acc0 = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc0);
MOV(64, R(RAX), R(tmp1)); // const s64 acc1 = GetLongAcc(1 - dreg);
// s64 acc1 = dsp_get_long_acc(1 - dreg); X64Reg acc1 = RDX;
get_long_acc(1 - dreg, RDX); get_long_acc(1 - dreg, acc1);
// s64 res = acc0 + acc1; // const s64 res = acc0 + acc1;
ADD(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MRegSum(acc0, acc1));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry(acc0, res), isOverflow(acc0, acc1, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc0, acc1, GetLongAcc(dreg));
set_long_acc(dreg, RCX); get_long_acc(dreg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc0, acc1, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// ADDP $acD // ADDP $acD
@ -622,28 +632,25 @@ void DSPEmitter::addp(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // const s64 prod = GetLongProduct();
// s64 prod = dsp_get_long_prod(); X64Reg prod = RDX;
get_long_prod(RDX); get_long_prod(prod);
// s64 res = acc + prod; // const s64 res = acc + prod;
ADD(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MRegSum(acc, prod));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, prod, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc, prod, GetLongAcc(dreg));
set_long_acc(dreg, RCX); get_long_acc(dreg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, prod, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// ADDAXL $acD, $axS.l // ADDAXL $acD, $axS.l
@ -657,29 +664,26 @@ void DSPEmitter::addaxl(const UDSPInstruction opc)
u8 sreg = (opc >> 9) & 0x1; u8 sreg = (opc >> 9) & 0x1;
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const u64 acc = GetLongAcc(dreg);
// u64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // const u16 acx = static_cast<u16>(GetAXLow(sreg));
// u16 acx = (u16)dsp_get_ax_l(sreg); X64Reg acx = RDX;
get_ax_l(sreg, RDX); get_ax_l(sreg, acx);
MOVZX(64, 16, RDX, R(RDX)); MOVZX(64, 16, acx, R(acx));
// u64 res = acc + acx; // const u64 res = acc + acx;
ADD(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, (s64)res); LEA(64, res, MRegSum(acc, acx));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, static_cast<s64>(res));
// Update_SR_Register64((s64)res, isCarry(acc, res), isOverflow((s64)acc, (s64)acx, (s64)res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc, acx, GetLongAcc(dreg));
set_long_acc(dreg, RCX); get_long_acc(dreg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, acx, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// ADDI $amR, #I // ADDI $amR, #I
@ -691,30 +695,30 @@ void DSPEmitter::addaxl(const UDSPInstruction opc)
void DSPEmitter::addi(const UDSPInstruction opc) void DSPEmitter::addi(const UDSPInstruction opc)
{ {
u8 areg = (opc >> 8) & 0x1; u8 areg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(areg);
// s64 acc = dsp_get_long_acc(areg); X64Reg acc = RAX;
get_long_acc(areg, tmp1); get_long_acc(areg, acc);
MOV(64, R(RAX), R(tmp1)); // s64 imm = static_cast<s16>(state.FetchInstruction());
// s64 imm = (s16)dsp_fetch_code();
const s16 imm = m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1);
// imm <<= 16; // imm <<= 16;
MOV(64, R(RDX), Imm32(imm << 16)); s64 imm = static_cast<s16>(m_dsp_core.DSPState().ReadIMEM(m_compile_pc + 1));
// s64 res = acc + imm; imm <<= 16;
ADD(64, R(RAX), R(RDX)); // const s64 res = acc + imm;
// dsp_set_long_acc(areg, res); X64Reg res = RCX;
// res = dsp_get_long_acc(areg); // Can safely use LEA as we are using a 16-bit sign-extended immediate shifted left by 16, which
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, imm, res)); // fits in a signed 32-bit immediate
LEA(64, res, MDisp(acc, static_cast<s32>(imm)));
// SetLongAcc(areg, res);
set_long_acc(areg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc, imm, GetLongAcc(areg));
set_long_acc(areg, RCX); get_long_acc(areg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg imm_reg = RDX;
MOV(64, R(imm_reg), Imm64(imm));
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(areg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// ADDIS $acD, #I // ADDIS $acD, #I
@ -726,30 +730,28 @@ void DSPEmitter::addis(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // s64 imm = static_cast<s8>(opc);
// s64 imm = (s8)(u8)opc; // imm <<= 16;
// imm <<= 16; s64 imm = static_cast<s8>(opc);
s32 imm = static_cast<u8>(opc) << 24 >> 8; imm <<= 16;
MOV(64, R(RDX), Imm32(imm)); // const s64 res = acc + imm;
// s64 res = acc + imm; X64Reg res = RCX;
ADD(64, R(RAX), R(RDX)); LEA(64, res, MDisp(acc, static_cast<s32>(imm)));
// dsp_set_long_acc(dreg, res); // SetLongAcc(dreg, res);
// res = dsp_get_long_acc(dreg); set_long_acc(dreg, res);
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, imm, res));
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RCX), R(RAX)); // UpdateSR64Add(acc, imm, GetLongAcc(dreg));
set_long_acc(dreg, RCX); get_long_acc(dreg, res);
Update_SR_Register64_Carry(EAX, tmp1); X64Reg imm_reg = RDX;
MOV(64, R(imm_reg), Imm64(imm));
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// INCM $acsD // INCM $acsD
@ -761,26 +763,24 @@ void DSPEmitter::incm(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
s64 subtract = 0x10000; s64 subtract = 0x10000;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
// s64 res = acc + sub; // const s64 res = acc + sub;
LEA(64, RAX, MDisp(tmp1, subtract)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MDisp(acc, static_cast<s32>(subtract)));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, subtract, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RDX), Imm32((u32)subtract)); // UpdateSR64Add(acc, sub, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg imm_reg = RDX;
Update_SR_Register64_Carry(EAX, tmp1); MOV(64, R(imm_reg), Imm64(subtract));
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg);
}
m_gpr.PutXReg(tmp1);
} }
// INC $acD // INC $acD
@ -791,26 +791,24 @@ void DSPEmitter::incm(const UDSPInstruction opc)
void DSPEmitter::inc(const UDSPInstruction opc) void DSPEmitter::inc(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
// s64 res = acc + 1; // const s64 res = acc + 1;
LEA(64, RAX, MDisp(tmp1, 1)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MDisp(acc, 1));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry(acc, res), isOverflow(acc, 1, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RDX), Imm64(1)); // UpdateSR64Add(acc, 1, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg imm_reg = RDX;
Update_SR_Register64_Carry(EAX, tmp1); MOV(64, R(imm_reg), Imm64(1));
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Add(acc, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg);
}
m_gpr.PutXReg(tmp1);
} }
//---- //----
@ -825,31 +823,28 @@ void DSPEmitter::subr(const UDSPInstruction opc)
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
u8 sreg = ((opc >> 9) & 0x3) + DSP_REG_AXL0; u8 sreg = ((opc >> 9) & 0x3) + DSP_REG_AXL0;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // s64 ax = ...;
// s64 ax = (s16)g_dsp.r[sreg]; X64Reg ax = RDX;
dsp_op_read_reg(sreg, RDX, RegisterExtension::Sign); dsp_op_read_reg(sreg, ax, RegisterExtension::Sign);
// ax <<= 16; // ax <<= 16;
SHL(64, R(RDX), Imm8(16)); SHL(64, R(ax), Imm8(16));
// s64 res = acc - ax; // const s64 res = acc - ax;
SUB(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); MOV(64, R(res), R(acc));
// res = dsp_get_long_acc(dreg); SUB(64, R(res), R(ax));
// Update_SR_Register64(res, isCarry2(acc, res), isOverflow(acc, -ax, res)); // SetLongAcc(dreg, res);
set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
NEG(64, R(RDX)); // UpdateSR64Sub(acc, ax, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg tmp1 = m_gpr.GetFreeXReg();
Update_SR_Register64_Carry(EAX, tmp1, true); UpdateSR64Sub(acc, ax, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// SUBAX $acD, $axS // SUBAX $acD, $axS
@ -862,29 +857,26 @@ void DSPEmitter::subax(const UDSPInstruction opc)
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
u8 sreg = (opc >> 9) & 0x1; u8 sreg = (opc >> 9) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // const s64 acx = GetLongACX(sreg);
// s64 acx = dsp_get_long_acx(sreg); X64Reg acx = RDX;
get_long_acx(sreg, RDX); get_long_acx(sreg, acx);
// s64 res = acc - acx; // const s64 res = acc - acx;
SUB(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); MOV(64, R(res), R(acc));
// res = dsp_get_long_acc(dreg); SUB(64, R(res), R(acx));
// Update_SR_Register64(res, isCarry2(acc, res), isOverflow(acc, -acx, res)); // SetLongAcc(dreg, res);
set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
NEG(64, R(RDX)); // UpdateSR64Sub(acc, acx, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg tmp1 = m_gpr.GetFreeXReg();
Update_SR_Register64_Carry(EAX, tmp1, true); UpdateSR64Sub(acc, acx, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// SUB $acD, $ac(1-D) // SUB $acD, $ac(1-D)
@ -895,29 +887,26 @@ void DSPEmitter::subax(const UDSPInstruction opc)
void DSPEmitter::sub(const UDSPInstruction opc) void DSPEmitter::sub(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc1 = GetLongAcc(dreg);
// s64 acc1 = dsp_get_long_acc(dreg); X64Reg acc1 = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc1);
MOV(64, R(RAX), R(tmp1)); // const s64 acc2 = GetLongAcc(1 - dreg);
// s64 acc2 = dsp_get_long_acc(1 - dreg); X64Reg acc2 = RDX;
get_long_acc(1 - dreg, RDX); get_long_acc(1 - dreg, acc2);
// s64 res = acc1 - acc2; // const s64 res = acc1 - acc2;
SUB(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); MOV(64, R(res), R(acc1));
// res = dsp_get_long_acc(dreg); SUB(64, R(res), R(acc2));
// Update_SR_Register64(res, isCarry2(acc1, res), isOverflow(acc1, -acc2, res)); // SetLongAcc(dreg, res);
set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
NEG(64, R(RDX)); // UpdateSR64Sub(acc1, acc2, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg tmp1 = m_gpr.GetFreeXReg();
Update_SR_Register64_Carry(EAX, tmp1, true); UpdateSR64Sub(acc1, acc2, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// SUBP $acD // SUBP $acD
@ -928,29 +917,26 @@ void DSPEmitter::sub(const UDSPInstruction opc)
void DSPEmitter::subp(const UDSPInstruction opc) void DSPEmitter::subp(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x1; u8 dreg = (opc >> 8) & 0x1;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
MOV(64, R(RAX), R(tmp1)); // const s64 prod = GetLongProduct();
// s64 prod = dsp_get_long_prod(); X64Reg prod = RDX;
get_long_prod(RDX); get_long_prod(prod);
// s64 res = acc - prod; // const s64 res = acc - prod;
SUB(64, R(RAX), R(RDX)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); MOV(64, R(res), R(acc));
// res = dsp_get_long_acc(dreg); SUB(64, R(res), R(prod));
// Update_SR_Register64(res, isCarry2(acc, res), isOverflow(acc, -prod, res)); // SetLongAcc(dreg, res);
set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
NEG(64, R(RDX)); // UpdateSR64Sub(acc, prod, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg tmp1 = m_gpr.GetFreeXReg();
Update_SR_Register64_Carry(EAX, tmp1, true); UpdateSR64Sub(acc, prod, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// DECM $acsD // DECM $acsD
@ -962,26 +948,24 @@ void DSPEmitter::decm(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x01; u8 dreg = (opc >> 8) & 0x01;
s64 subtract = 0x10000; s64 subtract = 0x10000;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
// s64 res = acc - sub; // const s64 res = acc - sub;
LEA(64, RAX, MDisp(tmp1, -subtract)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MDisp(acc, -subtract));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry2(acc, res), isOverflow(acc, -subtract, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RDX), Imm64(-subtract)); // UpdateSR64Sub(acc, sub, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg imm_reg = RDX;
Update_SR_Register64_Carry(EAX, tmp1, true); MOV(64, R(imm_reg), Imm64(subtract));
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Sub(acc, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg, RAX);
}
m_gpr.PutXReg(tmp1);
} }
// DEC $acD // DEC $acD
@ -992,26 +976,24 @@ void DSPEmitter::decm(const UDSPInstruction opc)
void DSPEmitter::dec(const UDSPInstruction opc) void DSPEmitter::dec(const UDSPInstruction opc)
{ {
u8 dreg = (opc >> 8) & 0x01; u8 dreg = (opc >> 8) & 0x01;
X64Reg tmp1 = m_gpr.GetFreeXReg(); // const s64 acc = GetLongAcc(dreg);
// s64 acc = dsp_get_long_acc(dreg); X64Reg acc = RAX;
get_long_acc(dreg, tmp1); get_long_acc(dreg, acc);
// s64 res = acc - 1; // const s64 res = acc - 1;
LEA(64, RAX, MDisp(tmp1, -1)); X64Reg res = RCX;
// dsp_set_long_acc(dreg, res); LEA(64, res, MDisp(acc, -1));
// res = dsp_get_long_acc(dreg); // SetLongAcc(dreg, res);
// Update_SR_Register64(res, isCarry2(acc, res), isOverflow(acc, -1, res)); set_long_acc(dreg, res);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
MOV(64, R(RDX), Imm64(-1)); // UpdateSR64Sub(acc, 1, GetLongAcc(dreg));
MOV(64, R(RCX), R(RAX)); get_long_acc(dreg, res);
set_long_acc(dreg, RCX); X64Reg imm_reg = RDX;
Update_SR_Register64_Carry(EAX, tmp1, true); MOV(64, R(RDX), Imm64(1));
X64Reg tmp1 = m_gpr.GetFreeXReg();
UpdateSR64Sub(acc, imm_reg, res, tmp1);
m_gpr.PutXReg(tmp1);
} }
else
{
set_long_acc(dreg);
}
m_gpr.PutXReg(tmp1);
} }
//---- //----

View File

@ -65,45 +65,52 @@ void DSPEmitter::Update_SR_Register64(Gen::X64Reg val, Gen::X64Reg scratch)
Update_SR_Register(val, scratch); Update_SR_Register(val, scratch);
} }
// In: (val): s64 _Value // Updates SR based on a 64-bit value computed by result = val1 + val2 or result = val1 - val2
// In: (carry_ovfl): 1 = carry, 2 = overflow // Clobbers scratch
// Clobbers RDX void DSPEmitter::UpdateSR64AddSub(Gen::X64Reg val1, Gen::X64Reg val2, Gen::X64Reg result,
void DSPEmitter::Update_SR_Register64_Carry(X64Reg val, X64Reg carry_ovfl, bool carry_eq) Gen::X64Reg scratch, bool subtract)
{ {
const OpArg sr_reg = m_gpr.GetReg(DSP_REG_SR); const OpArg sr_reg = m_gpr.GetReg(DSP_REG_SR);
// g_dsp.r[DSP_REG_SR] &= ~SR_CMP_MASK; // g_dsp.r[DSP_REG_SR] &= ~SR_CMP_MASK;
AND(16, sr_reg, Imm16(~SR_CMP_MASK)); AND(16, sr_reg, Imm16(~SR_CMP_MASK));
CMP(64, R(carry_ovfl), R(val)); CMP(64, R(val1), R(result));
// x86 ZF set if val1 == result
// x86 CF set if val1 < result
// Note that x86 uses a different definition of carry than the DSP
// 0x01 // 0x01
// g_dsp.r[DSP_REG_SR] |= SR_CARRY; // g_dsp.r[DSP_REG_SR] |= SR_CARRY;
// Carry = (acc>res) // isCarryAdd = (val1 > result) => skip setting if (val <= result) => jump if ZF or CF => use JBE
// Carry2 = (acc>=res) // isCarrySubtract = (val1 >= result) => skip setting if (val < result) => jump if CF => use JB
FixupBranch noCarry = J_CC(carry_eq ? CC_B : CC_BE); FixupBranch noCarry = J_CC(subtract ? CC_B : CC_BE);
OR(16, sr_reg, Imm16(SR_CARRY)); OR(16, sr_reg, Imm16(SR_CARRY));
SetJumpTarget(noCarry); SetJumpTarget(noCarry);
// 0x02 and 0x80 // 0x02 and 0x80
// g_dsp.r[DSP_REG_SR] |= SR_OVERFLOW; // g_dsp.r[DSP_REG_SR] |= SR_OVERFLOW;
// g_dsp.r[DSP_REG_SR] |= SR_OVERFLOW_STICKY; // g_dsp.r[DSP_REG_SR] |= SR_OVERFLOW_STICKY;
// Overflow = ((acc ^ res) & (ax ^ res)) < 0 // Overflow (add) = ((val1 ^ res) & (val2 ^ res)) < 0
XOR(64, R(carry_ovfl), R(val)); // Overflow (sub) = ((val1 ^ res) & (-val2 ^ res)) < 0
XOR(64, R(RDX), R(val)); MOV(64, R(scratch), R(val1));
TEST(64, R(carry_ovfl), R(RDX)); XOR(64, R(scratch), R(result));
if (subtract)
NEG(64, R(val2));
XOR(64, R(result), R(val2));
TEST(64, R(scratch), R(result)); // Test scratch & value
FixupBranch noOverflow = J_CC(CC_GE); FixupBranch noOverflow = J_CC(CC_GE);
OR(16, sr_reg, Imm16(SR_OVERFLOW | SR_OVERFLOW_STICKY)); OR(16, sr_reg, Imm16(SR_OVERFLOW | SR_OVERFLOW_STICKY));
SetJumpTarget(noOverflow); SetJumpTarget(noOverflow);
// Restore result and val2 -- TODO: does this really matter?
XOR(64, R(result), R(val2));
if (subtract)
NEG(64, R(val2));
m_gpr.PutReg(DSP_REG_SR); m_gpr.PutReg(DSP_REG_SR);
if (carry_eq) Update_SR_Register(result, scratch);
{
Update_SR_Register();
}
else
{
Update_SR_Register(val);
}
} }
// In: RAX: s64 _Value // In: RAX: s64 _Value

View File

@ -259,13 +259,14 @@ void DSPEmitter::addpaxz(const UDSPInstruction opc)
// s64 oldprod = dsp_get_long_prod(); // s64 oldprod = dsp_get_long_prod();
// dsp_set_long_acc(dreg, res); // dsp_set_long_acc(dreg, res);
// res = dsp_get_long_acc(dreg); // res = dsp_get_long_acc(dreg);
// Update_SR_Register64(res, isCarry(oldprod, res), false); // Update_SR_Register64(res, isCarryAdd(oldprod, res), false);
if (FlagsNeeded()) if (FlagsNeeded())
{ {
get_long_prod(RDX); get_long_prod(RDX);
MOV(64, R(RCX), R(RAX)); MOV(64, R(RCX), R(RAX));
set_long_acc(dreg, RCX); set_long_acc(dreg, RCX);
Update_SR_Register64_Carry(EAX, tmp1); // TODO: Why does this not set the overflow bit? (And thus, why can't it use UpdateSR64Add?)
Update_SR_Register64(EAX, tmp1);
} }
else else
{ {

View File

@ -690,7 +690,15 @@ void DSPEmitter::set_long_prod()
m_gpr.PutReg(DSP_REG_PROD_64, true); m_gpr.PutReg(DSP_REG_PROD_64, true);
} }
// Returns s64 in RAX // s64 -> s40 in long_acc
void DSPEmitter::dsp_convert_long_acc(Gen::X64Reg long_acc)
{
// return ((long_acc << (64 - 40)) >> (64 - 40))
SHL(64, R(long_acc), Imm8(64 - 40)); // sign extend
SAR(64, R(long_acc), Imm8(64 - 40));
}
// Returns s64 in long_acc
void DSPEmitter::round_long_acc(X64Reg long_acc) void DSPEmitter::round_long_acc(X64Reg long_acc)
{ {
// if (prod & 0x10000) prod = (prod + 0x8000) & ~0xffff; // if (prod & 0x10000) prod = (prod + 0x8000) & ~0xffff;