diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 4695d13926..695c39c05d 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -1738,7 +1738,7 @@ bool CanReplaceJalTo(u32 dest, const ReplacementTableEntry **entry, u32 *funcSiz return false; } - if ((*entry)->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT | REPFLAG_DISABLED)) { + if ((*entry)->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT | REPFLAG_DISABLED | REPFLAG_SLICED)) { // If it's a hook, we can't replace the jal, we have to go inside the func. return false; } diff --git a/Core/HLE/ReplaceTables.h b/Core/HLE/ReplaceTables.h index 980f506b6a..c8dd61f400 100644 --- a/Core/HLE/ReplaceTables.h +++ b/Core/HLE/ReplaceTables.h @@ -48,6 +48,8 @@ enum { REPFLAG_HOOKENTER = 0x04, // Only hooks jr ra, so only use on funcs that have that. REPFLAG_HOOKEXIT = 0x08, + // Function may take a lot of time and execute in slices (executed multiple times.) + REPFLAG_SLICED = 0x10, }; // Kind of similar to HLE functions but with different data. diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 4cfbc25124..e830a6b310 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -617,7 +617,18 @@ void ArmJit::Comp_ReplacementFunc(MIPSOpcode op) } else { ApplyRoundingMode(); RestoreDowncount(); + + CMPI2R(R0, 0, SCRATCHREG2); + FixupBranch positive = B_CC(CC_GE); + + RSB(R0, R0, Operand2(0)); + MovFromPC(R1); + FixupBranch done = B(); + + SetJumpTarget(positive); LDR(R1, CTXREG, MIPS_REG_RA * 4); + + SetJumpTarget(done); WriteDownCountR(R0); WriteExitDestInR(R1); js.compiling = false; diff --git a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp index 8fba3c3205..91a63978f4 100644 --- a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp @@ -242,7 +242,16 @@ void Arm64JitBackend::CompIR_System(IRInst inst) { QuickCallFunction(SCRATCH2_64, GetReplacementFunc(inst.constant)->replaceFunc); WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); LoadStaticRegisters(); - SUB(DOWNCOUNTREG, DOWNCOUNTREG, W0); + + // Absolute value the result and subtract. + CMP(W0, 0); + CSNEG(SCRATCH1, W0, W0, CC_PL); + SUB(DOWNCOUNTREG, DOWNCOUNTREG, SCRATCH1); + + // W0 might be the mapped reg, but there's only one. + // Set dest reg to the sign of the result. + regs_.Map(inst); + ASR(regs_.R(inst.dest), W0, 31); break; case IROp::Break: diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index 9abb69920b..a82b791d13 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -614,7 +614,18 @@ void Arm64Jit::Comp_ReplacementFunc(MIPSOpcode op) } else { ApplyRoundingMode(); LoadStaticRegisters(); + + CMPI2R(W0, 0); + FixupBranch positive = B(CC_GE); + + NEG(W0, W0); + MovFromPC(W1); + FixupBranch done = B(); + + SetJumpTarget(positive); LDR(INDEX_UNSIGNED, W1, CTXREG, MIPS_REG_RA * 4); + + SetJumpTarget(done); WriteDownCountR(W0); WriteExitDestInR(W1); js.compiling = false; diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 5b2a389601..2270484391 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -164,7 +164,7 @@ void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) { FlushAll(); RestoreRoundingMode(); ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC())); - ir.Write(IROp::CallReplacement, 0, ir.AddConstant(index)); + ir.Write(IROp::CallReplacement, IRTEMP_0, ir.AddConstant(index)); if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { // Compile the original instruction at this address. We ignore cycles for hooks. @@ -172,7 +172,10 @@ void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) { MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else { ApplyRoundingMode(); + // If IRTEMP_0 was set to 1, it means the replacement needs to run again (sliced.) + // This is necessary for replacements that take a lot of cycles. ir.Write(IROp::Downcount, 0, ir.AddConstant(js.downcountAmount)); + ir.Write(IROp::ExitToConstIfNeq, ir.AddConstant(GetCompilerPC()), IRTEMP_0, MIPS_REG_ZERO); ir.Write(IROp::ExitToReg, 0, MIPS_REG_RA, 0); js.compiling = false; } diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 3db8c4e4e2..64d1497d5e 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -165,7 +165,7 @@ static const IRMeta irMeta[] = { { IROp::Break, "Break", "", IRFLAG_EXIT }, { IROp::SetPC, "SetPC", "_G" }, { IROp::SetPCConst, "SetPC", "_C" }, - { IROp::CallReplacement, "CallRepl", "_C", IRFLAG_BARRIER }, + { IROp::CallReplacement, "CallRepl", "GC", IRFLAG_BARRIER }, { IROp::Breakpoint, "Breakpoint", "_C", IRFLAG_BARRIER }, { IROp::MemoryCheck, "MemoryCheck", "IGC", IRFLAG_BARRIER }, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 8fa713d809..9d83d5da68 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -1089,7 +1089,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { int funcIndex = inst->constant; const ReplacementTableEntry *f = GetReplacementFunc(funcIndex); int cycles = f->replaceFunc(); - mips->downcount -= cycles; + mips->r[inst->dest] = cycles < 0 ? -1 : 0; + mips->downcount -= cycles < 0 ? -cycles : cycles; break; } diff --git a/Core/MIPS/MIPSInt.cpp b/Core/MIPS/MIPSInt.cpp index 95c341c988..93644706d6 100644 --- a/Core/MIPS/MIPSInt.cpp +++ b/Core/MIPS/MIPSInt.cpp @@ -1038,13 +1038,17 @@ namespace MIPSInt int index = op.encoding & 0xFFFFFF; const ReplacementTableEntry *entry = GetReplacementFunc(index); if (entry && entry->replaceFunc && (entry->flags & REPFLAG_DISABLED) == 0) { - entry->replaceFunc(); + int cycles = entry->replaceFunc(); if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { // Interpret the original instruction under the hook. MIPSInterpret(Memory::Read_Instruction(PC, true)); + } else if (cycles < 0) { + // Leave PC unchanged, call the replacement again (assumes args are modified.) + currentMIPS->downcount += cycles; } else { PC = currentMIPS->r[MIPS_REG_RA]; + currentMIPS->downcount -= cycles; } } else { if (!entry || !entry->replaceFunc) { diff --git a/Core/MIPS/RiscV/RiscVCompSystem.cpp b/Core/MIPS/RiscV/RiscVCompSystem.cpp index 4605648ed8..a291d85930 100644 --- a/Core/MIPS/RiscV/RiscVCompSystem.cpp +++ b/Core/MIPS/RiscV/RiscVCompSystem.cpp @@ -220,6 +220,13 @@ void RiscVJitBackend::CompIR_System(IRInst inst) { QuickCallFunction(GetReplacementFunc(inst.constant)->replaceFunc, SCRATCH2); WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); LoadStaticRegisters(); + + regs_.Map(inst); + SRAIW(regs_.R(inst.dest), X10, 31); + + // Absolute value trick: if neg, abs(x) == (x ^ -1) + 1. + XOR(X10, X10, regs_.R(inst.dest)); + SUBW(X10, X10, regs_.R(inst.dest)); SUB(DOWNCOUNTREG, DOWNCOUNTREG, X10); break; diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 2f561a2cd0..b23b8385d6 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -658,8 +658,18 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op) { ApplyRoundingMode(); MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else { + CMP(32, R(EAX), Imm32(0)); + FixupBranch positive = J_CC(CC_GE); + + MOV(32, R(ECX), MIPSSTATE_VAR(pc)); + ADD(32, MIPSSTATE_VAR(downcount), R(EAX)); + FixupBranch done = J(); + + SetJumpTarget(positive); MOV(32, R(ECX), MIPSSTATE_VAR(r[MIPS_REG_RA])); SUB(32, MIPSSTATE_VAR(downcount), R(EAX)); + + SetJumpTarget(done); ApplyRoundingMode(); // Need to set flags again, ApplyRoundingMode destroyed them (and EAX.) SUB(32, MIPSSTATE_VAR(downcount), Imm8(0)); diff --git a/Core/MIPS/x86/X64IRCompSystem.cpp b/Core/MIPS/x86/X64IRCompSystem.cpp index 9918bd4678..c3bbd46aee 100644 --- a/Core/MIPS/x86/X64IRCompSystem.cpp +++ b/Core/MIPS/x86/X64IRCompSystem.cpp @@ -232,6 +232,17 @@ void X64JitBackend::CompIR_System(IRInst inst) { ABI_CallFunction(GetReplacementFunc(inst.constant)->replaceFunc); WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); LoadStaticRegisters(); + + // Since we flushed above, and we're mapping write, EAX should be safe. + regs_.Map(inst); + MOV(32, regs_.R(inst.dest), R(EAX)); + NEG(32, R(EAX)); + // Set it back if it negate made it negative. That's the absolute value. + CMOVcc(32, EAX, regs_.R(inst.dest), CC_S); + + // Now set the dest to the sign bit status. + SAR(32, regs_.R(inst.dest), Imm8(31)); + if (jo.downcountInRegister) SUB(32, R(DOWNCOUNTREG), R(EAX)); else