mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2026-06-10 18:12:16 +00:00
recompilation: emit MIPS link side-effect ($ra := PC+8) for jal/jalr/bltzal/bgezal
Previously the engine relied on callees to save/restore $ra on their own stack frame, leaving ctx->r31 stale across linking instructions. This worked for normal code that only uses $ra for `jr $ra` returns (which the engine maps to a C `return`, never reading ctx->r31), but broke any handwritten code that computes addresses from $ra. Concrete failure in Stadium: func_80048904 (audio synth) uses the bltzal $zero, X / bgezal $zero, X PC-arithmetic primitive: the branch is always-not-taken (since $zero is never < 0), but the link write is still committed by real MIPS, allowing the following `addiu $t3, $ra, OFFSET` to compute a helper-function address PC-relatively. With the link write missing, $t3 became a stale return-address-into-the-caller plus OFFSET, then `jalr $t3` inside func_80048A58 dispatched to garbage (LOOKUP_FUNC miss at 0x800AA638) and SEH-crashed mid-audio-frame. Fix: emit `ctx->r31 = vram + 8;` immediately before each linking instruction's call/branch emit. For conditional-link branches the write is hoisted outside the if-block so it fires regardless of branch direction. Verified: regen + 60s smoke test reaches frame 3597 with func_80048904 → func_80048A58 in the trace, no SEH crash, no 0x800AA638 lookup miss, audio probe 6/6 OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bc00a039f7
commit
16b4d33cf6
1 changed files with 21 additions and 0 deletions
|
|
@ -538,6 +538,12 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
|
|||
break;
|
||||
// Branches
|
||||
case InstrId::cpu_jal:
|
||||
// MIPS link side-effect: $ra := PC+8, written before the delay slot
|
||||
// executes. Required for handwritten code that computes addresses
|
||||
// from $ra (e.g. Stadium audio synth's `addiu $t3, $ra, OFFSET`
|
||||
// PC-arithmetic trick).
|
||||
print_indent();
|
||||
fmt::print(output_file, "ctx->r31 = 0x{:08X}u;\n", instr_vram + 8);
|
||||
if (!print_func_call_by_address(instr.getBranchVramGeneric())) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -548,6 +554,9 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
|
|||
fmt::print(stderr, "Invalid return address reg for jalr: f{}\n", rd);
|
||||
return false;
|
||||
}
|
||||
// MIPS link side-effect: $ra := PC+8 (see cpu_jal comment).
|
||||
print_indent();
|
||||
fmt::print(output_file, "ctx->r31 = 0x{:08X}u;\n", instr_vram + 8);
|
||||
needs_link_branch = true;
|
||||
print_func_call_by_register(rs);
|
||||
break;
|
||||
|
|
@ -773,6 +782,18 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
|
|||
|
||||
auto find_conditional_branch_it = conditional_branch_ops.find(instr_id);
|
||||
if (find_conditional_branch_it != conditional_branch_ops.end()) {
|
||||
// MIPS link side-effect for bltzal/bgezal/bltzall/bgezall: $ra := PC+8
|
||||
// is committed unconditionally, regardless of whether the branch is
|
||||
// taken. Emit the write BEFORE the if-block so it runs in both
|
||||
// branch-taken and branch-not-taken paths. Required for handwritten
|
||||
// code (e.g. Stadium audio synth) that uses bltzal $zero,X as a
|
||||
// PC-arithmetic primitive: the branch is always-not-taken (since
|
||||
// $zero is never < 0), but $ra still gets loaded so that the
|
||||
// following `addiu $t3, $ra, OFFSET` can compute a function pointer.
|
||||
if (find_conditional_branch_it->second.link) {
|
||||
print_indent();
|
||||
fmt::print(output_file, "ctx->r31 = 0x{:08X}u;\n", instr_vram + 8);
|
||||
}
|
||||
print_indent();
|
||||
// TODO combining the branch condition and branch target into one generator call would allow better optimization in the runtime's JIT generator.
|
||||
// This would require splitting into a conditional jump method and conditional function call method.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue