mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 16:00:30 +02:00
Mostly as on ARM64, with HI and LO in separate registers. Register shifts mask the amount to five bits first, since ARM shifts by the whole bottom byte, and msub is a multiply followed by a 64-bit subtract. Division needs the IDIV extension and goes to the IR interpreter without it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
510 lines
12 KiB
C++
510 lines
12 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "ppsspp_config.h"
|
|
#if PPSSPP_ARCH(ARM)
|
|
|
|
#include "Common/CPUDetect.h"
|
|
#include "Core/MIPS/ARM/ArmIRJit.h"
|
|
#include "Core/MIPS/ARM/ArmIRRegCache.h"
|
|
|
|
// This file contains compilation for integer arithmetic, logic, shifts, compares, and multiplies.
|
|
//
|
|
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
|
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
|
|
|
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
|
#define CONDITIONAL_DISABLE {}
|
|
#define DISABLE { CompIR_Generic(inst); return; }
|
|
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
|
|
|
namespace MIPSComp {
|
|
|
|
using namespace ArmGen;
|
|
using namespace ArmIRJitConstants;
|
|
|
|
void ArmJitBackend::CompIR_Arith(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::Add:
|
|
regs_.Map(inst);
|
|
ADD(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
break;
|
|
|
|
case IROp::Sub:
|
|
regs_.Map(inst);
|
|
SUB(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
break;
|
|
|
|
case IROp::AddConst:
|
|
regs_.Map(inst);
|
|
ADDI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
break;
|
|
|
|
case IROp::SubConst:
|
|
regs_.Map(inst);
|
|
SUBI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
break;
|
|
|
|
case IROp::Neg:
|
|
regs_.Map(inst);
|
|
RSB(regs_.R(inst.dest), regs_.R(inst.src1), Operand2(0));
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Assign(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::Mov:
|
|
if (inst.dest != inst.src1) {
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
}
|
|
break;
|
|
|
|
case IROp::Ext8to32:
|
|
regs_.Map(inst);
|
|
SXTB(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
case IROp::Ext16to32:
|
|
regs_.Map(inst);
|
|
SXTH(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Bits(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::BSwap16:
|
|
regs_.Map(inst);
|
|
REV16(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
case IROp::BSwap32:
|
|
regs_.Map(inst);
|
|
REV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
case IROp::Clz:
|
|
regs_.Map(inst);
|
|
CLZ(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
case IROp::ReverseBits:
|
|
regs_.Map(inst);
|
|
RBIT(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Compare(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
// Sets dest to 1 if cc holds, else 0. The MOVs don't touch the flags, so dest may be a source.
|
|
auto setCC = [&](ARMReg dest, CCFlags cc) {
|
|
MOV(dest, Operand2(0));
|
|
SetCC(cc);
|
|
MOV(dest, Operand2(1));
|
|
SetCC(CC_AL);
|
|
};
|
|
|
|
switch (inst.op) {
|
|
case IROp::Slt:
|
|
regs_.Map(inst);
|
|
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
|
|
setCC(regs_.R(inst.dest), CC_LT);
|
|
break;
|
|
|
|
case IROp::SltConst:
|
|
if (inst.constant == 0) {
|
|
// Basically, getting the sign bit.
|
|
regs_.Map(inst);
|
|
LSR(regs_.R(inst.dest), regs_.R(inst.src1), Operand2(31));
|
|
} else {
|
|
regs_.Map(inst);
|
|
CMPI2R(regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
setCC(regs_.R(inst.dest), CC_LT);
|
|
}
|
|
break;
|
|
|
|
case IROp::SltU:
|
|
if (regs_.IsGPRImm(inst.src1) && regs_.GetGPRImm(inst.src1) == 0) {
|
|
// This is kinda common, same as != 0. Avoid flushing src1.
|
|
regs_.SpillLockGPR(inst.src2, inst.dest);
|
|
regs_.MapGPR(inst.src2);
|
|
regs_.MapGPR(inst.dest, MIPSMap::NOINIT);
|
|
CMP(regs_.R(inst.src2), Operand2(0));
|
|
setCC(regs_.R(inst.dest), CC_NEQ);
|
|
} else {
|
|
regs_.Map(inst);
|
|
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
|
|
setCC(regs_.R(inst.dest), CC_LO);
|
|
}
|
|
break;
|
|
|
|
case IROp::SltUConst:
|
|
if (inst.constant == 0) {
|
|
regs_.SetGPRImm(inst.dest, 0);
|
|
} else {
|
|
regs_.Map(inst);
|
|
CMPI2R(regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
setCC(regs_.R(inst.dest), CC_LO);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_CondAssign(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::MovZ:
|
|
case IROp::MovNZ:
|
|
if (inst.dest != inst.src2) {
|
|
regs_.Map(inst);
|
|
CMP(regs_.R(inst.src1), Operand2(0));
|
|
SetCC(inst.op == IROp::MovZ ? CC_EQ : CC_NEQ);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src2));
|
|
SetCC(CC_AL);
|
|
}
|
|
break;
|
|
|
|
case IROp::Max:
|
|
case IROp::Min:
|
|
if (inst.src1 != inst.src2) {
|
|
regs_.Map(inst);
|
|
CMP(regs_.R(inst.src1), regs_.R(inst.src2));
|
|
// Exactly one of these happens, so dest may be either source.
|
|
CCFlags pickSrc1 = inst.op == IROp::Max ? CC_GE : CC_LE;
|
|
SetCC(pickSrc1);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
SetCC(InvertCond(pickSrc1));
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src2));
|
|
SetCC(CC_AL);
|
|
} else if (inst.dest != inst.src1) {
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Div(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
if (!cpu_info.bIDIVa) {
|
|
CompIR_Generic(inst);
|
|
return;
|
|
}
|
|
|
|
switch (inst.op) {
|
|
case IROp::Div:
|
|
{
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 1, MIPSMap::NOINIT }, { 'G', IRREG_HI, 1, MIPSMap::NOINIT } });
|
|
ARMReg lo = regs_.R(IRREG_LO);
|
|
ARMReg hi = regs_.R(IRREG_HI);
|
|
// INT_MIN divided by -1 = INT_MIN with remainder 0, anything divided by 0 = 0. The first is
|
|
// what the hardware does too (cpu/cpu_alu/cpu_div), so only the second needs tweaking.
|
|
SDIV(lo, regs_.R(inst.src1), regs_.R(inst.src2));
|
|
MUL(SCRATCH1, lo, regs_.R(inst.src2));
|
|
SUB(hi, regs_.R(inst.src1), SCRATCH1);
|
|
|
|
// Divide by zero, remainder is fine. mips->lo = numerator < 0 ? 1 : -1
|
|
CMP(regs_.R(inst.src2), Operand2(0));
|
|
FixupBranch skipNonZero = B_CC(CC_NEQ);
|
|
CMP(regs_.R(inst.src1), Operand2(0));
|
|
MOV(lo, Operand2(1));
|
|
SetCC(CC_GE);
|
|
MVN(lo, Operand2(0));
|
|
SetCC(CC_AL);
|
|
SetJumpTarget(skipNonZero);
|
|
break;
|
|
}
|
|
|
|
case IROp::DivU:
|
|
{
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 1, MIPSMap::NOINIT }, { 'G', IRREG_HI, 1, MIPSMap::NOINIT } });
|
|
ARMReg lo = regs_.R(IRREG_LO);
|
|
ARMReg hi = regs_.R(IRREG_HI);
|
|
// Anything divided by 0 = 0.
|
|
UDIV(lo, regs_.R(inst.src1), regs_.R(inst.src2));
|
|
MUL(SCRATCH1, lo, regs_.R(inst.src2));
|
|
SUB(hi, regs_.R(inst.src1), SCRATCH1);
|
|
|
|
// On divide by zero, we have to update LO - remainder is correct.
|
|
// If the numerator is <= 0xFFFF (unsigned), it's 0xFFFF, otherwise -1.
|
|
CMP(regs_.R(inst.src2), Operand2(0));
|
|
FixupBranch skipNonZero = B_CC(CC_NEQ);
|
|
MOVI2R(lo, 0xFFFF);
|
|
CMP(regs_.R(inst.src1), lo);
|
|
SetCC(CC_HI);
|
|
MVN(lo, Operand2(0));
|
|
SetCC(CC_AL);
|
|
SetJumpTarget(skipNonZero);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_HiLo(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::MtLo:
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 1, MIPSMap::NOINIT } });
|
|
MOV(regs_.R(IRREG_LO), regs_.R(inst.src1));
|
|
break;
|
|
|
|
case IROp::MtHi:
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_HI, 1, MIPSMap::NOINIT } });
|
|
MOV(regs_.R(IRREG_HI), regs_.R(inst.src1));
|
|
break;
|
|
|
|
case IROp::MfLo:
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 1, MIPSMap::INIT } });
|
|
MOV(regs_.R(inst.dest), regs_.R(IRREG_LO));
|
|
break;
|
|
|
|
case IROp::MfHi:
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_HI, 1, MIPSMap::INIT } });
|
|
MOV(regs_.R(inst.dest), regs_.R(IRREG_HI));
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Logic(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
switch (inst.op) {
|
|
case IROp::And:
|
|
if (inst.src1 != inst.src2) {
|
|
regs_.Map(inst);
|
|
AND(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
} else if (inst.src1 != inst.dest) {
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
}
|
|
break;
|
|
|
|
case IROp::Or:
|
|
if (inst.src1 != inst.src2) {
|
|
regs_.Map(inst);
|
|
ORR(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
} else if (inst.src1 != inst.dest) {
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
}
|
|
break;
|
|
|
|
case IROp::Xor:
|
|
if (inst.src1 == inst.src2) {
|
|
regs_.SetGPRImm(inst.dest, 0);
|
|
} else {
|
|
regs_.Map(inst);
|
|
EOR(regs_.R(inst.dest), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
}
|
|
break;
|
|
|
|
case IROp::AndConst:
|
|
regs_.Map(inst);
|
|
ANDI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
break;
|
|
|
|
case IROp::OrConst:
|
|
regs_.Map(inst);
|
|
ORI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
break;
|
|
|
|
case IROp::XorConst:
|
|
regs_.Map(inst);
|
|
EORI2R(regs_.R(inst.dest), regs_.R(inst.src1), inst.constant, SCRATCH1);
|
|
break;
|
|
|
|
case IROp::Not:
|
|
regs_.Map(inst);
|
|
MVN(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Mult(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
auto mapHiLo = [&](MIPSMap flags) {
|
|
regs_.MapWithExtra(inst, { { 'G', IRREG_LO, 1, flags }, { 'G', IRREG_HI, 1, flags } });
|
|
};
|
|
|
|
switch (inst.op) {
|
|
case IROp::Mult:
|
|
mapHiLo(MIPSMap::NOINIT);
|
|
SMULL(regs_.R(IRREG_LO), regs_.R(IRREG_HI), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
break;
|
|
|
|
case IROp::MultU:
|
|
mapHiLo(MIPSMap::NOINIT);
|
|
UMULL(regs_.R(IRREG_LO), regs_.R(IRREG_HI), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
break;
|
|
|
|
case IROp::Madd:
|
|
mapHiLo(MIPSMap::DIRTY);
|
|
SMLAL(regs_.R(IRREG_LO), regs_.R(IRREG_HI), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
break;
|
|
|
|
case IROp::MaddU:
|
|
mapHiLo(MIPSMap::DIRTY);
|
|
UMLAL(regs_.R(IRREG_LO), regs_.R(IRREG_HI), regs_.R(inst.src1), regs_.R(inst.src2));
|
|
break;
|
|
|
|
case IROp::Msub:
|
|
case IROp::MsubU:
|
|
// No multiply-subtract-long, so do the product and a 64-bit subtract.
|
|
mapHiLo(MIPSMap::DIRTY);
|
|
if (inst.op == IROp::Msub)
|
|
SMULL(SCRATCH1, SCRATCH2, regs_.R(inst.src1), regs_.R(inst.src2));
|
|
else
|
|
UMULL(SCRATCH1, SCRATCH2, regs_.R(inst.src1), regs_.R(inst.src2));
|
|
SUBS(regs_.R(IRREG_LO), regs_.R(IRREG_LO), SCRATCH1);
|
|
SBC(regs_.R(IRREG_HI), regs_.R(IRREG_HI), SCRATCH2);
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ArmJitBackend::CompIR_Shift(IRInst inst) {
|
|
CONDITIONAL_DISABLE;
|
|
|
|
// A shift by register uses the bottom byte of it, but MIPS only the bottom five bits.
|
|
auto shiftByReg = [&](ShiftType type) {
|
|
regs_.Map(inst);
|
|
AND(SCRATCH1, regs_.R(inst.src2), Operand2(31));
|
|
MOV(regs_.R(inst.dest), Operand2(regs_.R(inst.src1), type, SCRATCH1));
|
|
};
|
|
auto shiftByImm = [&](ShiftType type, int amount) {
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), Operand2(regs_.R(inst.src1), type, (u8)amount));
|
|
};
|
|
auto movOrNothing = [&]() {
|
|
if (inst.dest != inst.src1) {
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), regs_.R(inst.src1));
|
|
}
|
|
};
|
|
|
|
switch (inst.op) {
|
|
case IROp::Shl:
|
|
shiftByReg(ST_LSL);
|
|
break;
|
|
|
|
case IROp::Shr:
|
|
shiftByReg(ST_LSR);
|
|
break;
|
|
|
|
case IROp::Sar:
|
|
shiftByReg(ST_ASR);
|
|
break;
|
|
|
|
case IROp::Ror:
|
|
// Rotation by the bottom byte is the same as by its bottom five bits.
|
|
regs_.Map(inst);
|
|
MOV(regs_.R(inst.dest), Operand2(regs_.R(inst.src1), ST_ROR, regs_.R(inst.src2)));
|
|
break;
|
|
|
|
case IROp::ShlImm:
|
|
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
|
if (inst.src2 >= 32)
|
|
regs_.SetGPRImm(inst.dest, 0);
|
|
else if (inst.src2 == 0)
|
|
movOrNothing();
|
|
else
|
|
shiftByImm(ST_LSL, inst.src2);
|
|
break;
|
|
|
|
case IROp::ShrImm:
|
|
if (inst.src2 >= 32)
|
|
regs_.SetGPRImm(inst.dest, 0);
|
|
else if (inst.src2 == 0)
|
|
movOrNothing();
|
|
else
|
|
shiftByImm(ST_LSR, inst.src2);
|
|
break;
|
|
|
|
case IROp::SarImm:
|
|
if (inst.src2 >= 32)
|
|
shiftByImm(ST_ASR, 31);
|
|
else if (inst.src2 == 0)
|
|
movOrNothing();
|
|
else
|
|
shiftByImm(ST_ASR, inst.src2);
|
|
break;
|
|
|
|
case IROp::RorImm:
|
|
if ((inst.src2 & 31) == 0)
|
|
movOrNothing();
|
|
else
|
|
shiftByImm(ST_ROR, inst.src2 & 31);
|
|
break;
|
|
|
|
default:
|
|
INVALIDOP;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace MIPSComp
|
|
|
|
#endif
|