mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
code_generator: resolve computed-stride jumps into unrolled runs (Duff's device)
A `jr R` with `R = base + index * stride` and no table in memory is how unrolled copy loops enter their body in the middle (Duff's device). The bounded-jump-table resolver only knows the `sltiu/beq; sll; addu; lw; jr` chain, so the emitter fell back to a bare CPS tail-transfer; the trampoline then found no dispatch key at the interior PC (straight-line code, no block leader) and the dirty-RAM interpreter ran the rest of the copy on every call. On Breath of Fire III that is the file decompressor's literal-copy loop (`0x80164D1C`, 32 groups of `lbu/nop/sb`), which fires on every load and was the whole non-kernel interpreted residual of a play session once the kernel patch ranges (#346) and the call vectors (#381) ran native. resolve_computed_stride_jump walks the chain back from the jr through nearest definitions in straight-line code: `addu R, B, S` with S from `sll S, I, k` or the two-sll sum `sll P,I,a; sll Q,I,b; addu S,P,Q`, and then requires the code after the delay slot to be N >= 2 shape-identical groups of `stride` bytes (same opcodes and register fields, immediates free, no control flow inside a group). Targets are run_start + k * stride for k = 0..N. The base register's value is never assumed: the emitter switches on the actual runtime target with the CPS tail-transfer as the default, so the recovered cases can only add native entries, never redirect a jump that lands elsewhere. Wired into both code-generator sites (interior labels + the switch) and the exact-entry discovery walker. On the title's boot EXE it matches exactly one site, the decompressor loop (33 entries); every other jr is untouched. Adds recompiler/tests/test_computed_stride_jump_codegen.py (synthetic PS-EXE with the idiom; asserts the switch, every case and the interior labels), registered with CTest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -2322,6 +2322,15 @@ std::string CodeGenerator::translate_basic_block(
|
||||
exe_, cfg.function_start, cfg.function_end,
|
||||
block.exit_instr.address, jr_rs, exact_table,
|
||||
ram_to_rom, cfg.producer_lo, cfg.producer_hi);
|
||||
// Not a table: a computed-stride entry into an unrolled
|
||||
// run (Duff's device — the decompressor copy loops). Same
|
||||
// switch shape; the CPS default keeps every other target.
|
||||
if (!have_exact_table) {
|
||||
have_exact_table = resolve_computed_stride_jump(
|
||||
exe_, cfg.function_start, cfg.function_end,
|
||||
block.exit_instr.address, jr_rs, exact_table,
|
||||
ram_to_rom);
|
||||
}
|
||||
uint32_t table_base = have_exact_table
|
||||
? exact_table.table_base : 0u;
|
||||
uint32_t table_count = have_exact_table
|
||||
@@ -2344,8 +2353,14 @@ std::string CodeGenerator::translate_basic_block(
|
||||
}
|
||||
}
|
||||
if (!targets.empty()) {
|
||||
ss << config_.indent << fmt::format("/* jump table 0x{:08X} (rom 0x{:08X}), {} entries */\n",
|
||||
table_base, rom_table_base, table_count);
|
||||
if (exact_table.stride != 0u) {
|
||||
ss << config_.indent << fmt::format(
|
||||
"/* computed-stride jump into unrolled run 0x{:08X} (rom 0x{:08X}), stride {}, {} entries */\n",
|
||||
table_base, rom_table_base, exact_table.stride, table_count);
|
||||
} else {
|
||||
ss << config_.indent << fmt::format("/* jump table 0x{:08X} (rom 0x{:08X}), {} entries */\n",
|
||||
table_base, rom_table_base, table_count);
|
||||
}
|
||||
ss << config_.indent << fmt::format("switch ({}) {{\n", delay_saved_target);
|
||||
for (auto& [rt, rom] : targets) {
|
||||
if (partial_block_cycle_count(rom, cfg) != 0) {
|
||||
@@ -2898,7 +2913,10 @@ void CodeGenerator::scan_jr_tables(
|
||||
if (!resolve_exact_bounded_jump_table(
|
||||
exe_, cfg.function_start, cfg.function_end,
|
||||
blk.exit_instr.address, jr_r, exact_table, ram_to_rom,
|
||||
cfg.producer_lo, cfg.producer_hi)) {
|
||||
cfg.producer_lo, cfg.producer_hi) &&
|
||||
!resolve_computed_stride_jump(
|
||||
exe_, cfg.function_start, cfg.function_end,
|
||||
blk.exit_instr.address, jr_r, exact_table, ram_to_rom)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t tb = exact_table.table_base;
|
||||
|
||||
Reference in New Issue
Block a user