mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Fix/fix jump tables (#65)
* feat: multipass discover additional entrypoints * feat: added helpers for returning feat: added game overrides * added missing PS2_SHUFFLE_EPI8 macro after report from @playmer * fix: fix jump tables * fix: fix MMI comments
This commit is contained in:
@@ -186,8 +186,7 @@ namespace ps2recomp
|
||||
|
||||
std::vector<uint32_t> sortedInternalTargets;
|
||||
if (branchInst.opcode == OPCODE_SPECIAL &&
|
||||
branchInst.function == SPECIAL_JR &&
|
||||
rs_reg == 31 &&
|
||||
(branchInst.function == SPECIAL_JR || branchInst.function == SPECIAL_JALR) &&
|
||||
!internalTargets.empty())
|
||||
{
|
||||
sortedInternalTargets.reserve(internalTargets.size());
|
||||
@@ -260,7 +259,11 @@ namespace ps2recomp
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << " " << funcName << "(rdram, ctx, runtime);\n";
|
||||
ss << " {\n";
|
||||
ss << " const uint32_t __entryPc = ctx->pc;\n";
|
||||
ss << " " << funcName << "(rdram, ctx, runtime);\n";
|
||||
ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc);
|
||||
ss << " }\n";
|
||||
ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc);
|
||||
}
|
||||
}
|
||||
@@ -303,6 +306,7 @@ namespace ps2recomp
|
||||
{
|
||||
ss << " {\n";
|
||||
ss << fmt::format(" auto targetFn = runtime->lookupFunction(0x{:X}u);\n", target);
|
||||
ss << " const uint32_t __entryPc = ctx->pc;\n";
|
||||
ss << " targetFn(rdram, ctx, runtime);\n";
|
||||
if (branchInst.opcode == OPCODE_J)
|
||||
{
|
||||
@@ -310,6 +314,7 @@ namespace ps2recomp
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc);
|
||||
ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc);
|
||||
}
|
||||
ss << " }\n";
|
||||
@@ -339,7 +344,7 @@ namespace ps2recomp
|
||||
|
||||
ss << " ctx->pc = jumpTarget;\n";
|
||||
|
||||
if (branchInst.function == SPECIAL_JR && rs_reg == 31 && !sortedInternalTargets.empty())
|
||||
if (!sortedInternalTargets.empty())
|
||||
{
|
||||
ss << " switch (jumpTarget) {\n";
|
||||
for (uint32_t t : sortedInternalTargets)
|
||||
@@ -358,7 +363,9 @@ namespace ps2recomp
|
||||
{
|
||||
ss << " {\n";
|
||||
ss << " auto targetFn = runtime->lookupFunction(jumpTarget);\n";
|
||||
ss << " const uint32_t __entryPc = ctx->pc;\n";
|
||||
ss << " targetFn(rdram, ctx, runtime);\n";
|
||||
ss << fmt::format(" if (ctx->pc == __entryPc) {{ ctx->pc = 0x{:X}u; }}\n", fallthroughPc);
|
||||
ss << fmt::format(" if (ctx->pc != 0x{:X}u) {{ return; }}\n", fallthroughPc);
|
||||
ss << " }\n";
|
||||
}
|
||||
@@ -558,10 +565,17 @@ namespace ps2recomp
|
||||
std::unordered_set<uint32_t> targets;
|
||||
std::unordered_set<uint32_t> instructionAddresses;
|
||||
instructionAddresses.reserve(instructions.size());
|
||||
bool hasIndirectRegisterJump = false;
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
{
|
||||
instructionAddresses.insert(inst.address);
|
||||
if (inst.opcode == OPCODE_SPECIAL &&
|
||||
((inst.function == SPECIAL_JR && inst.rs != 31) ||
|
||||
inst.function == SPECIAL_JALR))
|
||||
{
|
||||
hasIndirectRegisterJump = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
@@ -600,6 +614,17 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
if (hasIndirectRegisterJump)
|
||||
{
|
||||
for (uint32_t addr : instructionAddresses)
|
||||
{
|
||||
if (addr >= function.start && addr < function.end)
|
||||
{
|
||||
targets.insert(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
@@ -621,7 +646,6 @@ namespace ps2recomp
|
||||
}
|
||||
|
||||
std::unordered_set<uint32_t> internalTargets = collectInternalBranchTargets(function, instructions);
|
||||
|
||||
ss << "// Function: " << function.name << "\n";
|
||||
ss << "// Address: 0x" << std::hex << function.start << " - 0x" << function.end << std::dec << "\n";
|
||||
|
||||
@@ -1633,7 +1657,7 @@ namespace ps2recomp
|
||||
switch (subfunc)
|
||||
{
|
||||
case MMI3_PMADDUW:
|
||||
return fmt::format("Unhandled PMADDUW instruction: function 0x{:X}", subfunc);
|
||||
return fmt::format("// Unhandled PMADDUW instruction: function 0x{:X}", subfunc);
|
||||
case MMI3_PSRAVW:
|
||||
return fmt::format("SET_GPR_VEC(ctx, {}, PS2_PSRAVW(GPR_VEC(ctx, {}), GPR_VEC(ctx, {})));", rd, rs, rt);
|
||||
case MMI3_PMTHI:
|
||||
|
||||
@@ -472,6 +472,10 @@ void register_code_generator_tests()
|
||||
|
||||
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xA008u);") != std::string::npos, "JAL should set RA");
|
||||
t.IsTrue(generated.find("some_func(rdram, ctx, runtime);") != std::string::npos, "JAL should call function");
|
||||
t.IsTrue(generated.find("const uint32_t __entryPc = ctx->pc;") != std::string::npos,
|
||||
"JAL should capture entry PC before call");
|
||||
t.IsTrue(generated.find("if (ctx->pc == __entryPc) { ctx->pc = 0xA008u; }") != std::string::npos,
|
||||
"JAL should recover fallthrough when callee leaves ctx->pc unchanged");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0xA008u) { return; }") != std::string::npos, "JAL should check return PC");
|
||||
});
|
||||
|
||||
@@ -520,7 +524,11 @@ void register_code_generator_tests()
|
||||
t.IsTrue(generated.find("SET_GPR_U32(ctx, 31, 0xD008u);") != std::string::npos, "JALR should set link register");
|
||||
t.IsTrue(generated.find("auto targetFn = runtime->lookupFunction(jumpTarget);") != std::string::npos, "JALR should lookup function");
|
||||
t.IsTrue(generated.find("targetFn(rdram, ctx, runtime);") != std::string::npos, "JALR should call function");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC");
|
||||
t.IsTrue(generated.find("const uint32_t __entryPc = ctx->pc;") != std::string::npos,
|
||||
"JALR should capture entry PC before indirect call");
|
||||
t.IsTrue(generated.find("if (ctx->pc == __entryPc) { ctx->pc = 0xD008u; }") != std::string::npos,
|
||||
"JALR should recover fallthrough when callee leaves ctx->pc unchanged");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0xD008u) { return; }") != std::string::npos, "JALR should check return PC");
|
||||
});
|
||||
|
||||
tc.Run("backward BEQ emits label and goto (sign-extended offset)", [](TestCase &t) {
|
||||
@@ -614,6 +622,65 @@ void register_code_generator_tests()
|
||||
t.IsTrue(generated.find("case 0x1308u: goto label_1308;") != std::string::npos, "switch should include return address from internal JAL");
|
||||
});
|
||||
|
||||
tc.Run("JR non-RA emits switch for in-function jump targets", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "jr_non_ra_switch";
|
||||
func.start = 0x1400;
|
||||
func.end = 0x1420;
|
||||
func.isRecompiled = true;
|
||||
func.isStub = false;
|
||||
|
||||
// 0x1400: nop
|
||||
// 0x1404: jr $16 (register jump)
|
||||
// 0x1408: nop (delay slot)
|
||||
// 0x140c: nop
|
||||
Instruction i0 = makeNop(0x1400);
|
||||
Instruction jr = makeJr(0x1404, 16);
|
||||
Instruction delay = makeNop(0x1408);
|
||||
Instruction i3 = makeNop(0x140c);
|
||||
|
||||
CodeGenerator gen({});
|
||||
std::string generated = gen.generateFunction(func, {i0, jr, delay, i3}, false);
|
||||
printGeneratedCode("JR non-RA emits switch for in-function jump targets", generated);
|
||||
|
||||
t.IsTrue(generated.find("switch (jumpTarget)") != std::string::npos,
|
||||
"JR via non-RA register should emit switch for internal targets");
|
||||
t.IsTrue(generated.find("case 0x1400u: goto label_1400;") != std::string::npos,
|
||||
"switch should include in-function entry label");
|
||||
t.IsTrue(generated.find("case 0x140Cu: goto label_140c;") != std::string::npos,
|
||||
"switch should include other in-function labels");
|
||||
});
|
||||
|
||||
tc.Run("JALR includes switch and fallback/guard pair", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "jalr_switch_and_fallback";
|
||||
func.start = 0x1500;
|
||||
func.end = 0x1530;
|
||||
func.isRecompiled = true;
|
||||
func.isStub = false;
|
||||
|
||||
// A call-like setup so there are multiple in-function labels to dispatch to.
|
||||
Instruction jal = makeJal(0x1500, 0x1510);
|
||||
Instruction jalDelay = makeNop(0x1504);
|
||||
Instruction atReturn = makeNop(0x1508);
|
||||
Instruction atTarget = makeNop(0x1510);
|
||||
Instruction jalr = makeJalr(0x1514, 4, 31);
|
||||
Instruction jalrDelay = makeNop(0x1518);
|
||||
|
||||
CodeGenerator gen({});
|
||||
std::string generated = gen.generateFunction(func, {jal, jalDelay, atReturn, atTarget, jalr, jalrDelay}, false);
|
||||
printGeneratedCode("JALR includes switch and fallback/guard pair", generated);
|
||||
|
||||
t.IsTrue(generated.find("switch (jumpTarget)") != std::string::npos,
|
||||
"JALR should emit switch when in-function register-jump targets exist");
|
||||
t.IsTrue(generated.find("case 0x1508u: goto label_1508;") != std::string::npos,
|
||||
"switch should include internal return label from JAL in same function");
|
||||
t.IsTrue(generated.find("if (ctx->pc == __entryPc) { ctx->pc = 0x151Cu; }") != std::string::npos,
|
||||
"JALR should contain unchanged-PC fallback to fallthrough");
|
||||
t.IsTrue(generated.find("if (ctx->pc != 0x151Cu) { return; }") != std::string::npos,
|
||||
"JALR should retain non-fallthrough guard");
|
||||
});
|
||||
|
||||
tc.Run("resolveStubTarget allows leading underscore alias", [](TestCase &t) {
|
||||
t.Equals(PS2Recompiler::resolveStubTarget("_rand"), StubTarget::Stub,
|
||||
"_rand should resolve via rand stub alias");
|
||||
|
||||
Reference in New Issue
Block a user