Skip to content

fix(recomp): advance ctx->pc on fallthrough functions with no terminating branch#168

Open
TH3BACKLOG wants to merge 2 commits into
ran-j:mainfrom
TH3BACKLOG:fix/dispatch-fallthrough-pc
Open

fix(recomp): advance ctx->pc on fallthrough functions with no terminating branch#168
TH3BACKLOG wants to merge 2 commits into
ran-j:mainfrom
TH3BACKLOG:fix/dispatch-fallthrough-pc

Conversation

@TH3BACKLOG

Copy link
Copy Markdown

Summary

  • FunctionEmitter::emit only advances ctx->pc per-instruction (overwritten each iteration) or via handleBranchDelaySlots when the last instruction is a branch/jump.
  • A function whose last instruction is not a branch (e.g. a single no-op-style instruction with no terminator) leaves ctx->pc stuck at its own address forever after returning.
  • dispatchLoop then re-reads ctx->pc, looks up the same function, and calls it again — indefinitely. No crash, no exception, just a silent infinite loop with zero forward progress.

Repro

Found while debugging a "silent boot hang" in a downstream game (SDBZ, SLUS_214.42). The ELF entry point 0x100008 gets emitted as a standalone one-instruction function (padduw $at, $zero, $zero, no branch), and dispatchLoop spins on pc=0x100008 forever — confirmed live via debugger, watching ctx->pc stay constant across repeated dispatch iterations.

Fix

Track whether the last instruction processed in the loop had a delay slot (i.e. was a branch/jump). If the function's last instruction isn't one, emit an unconditional ctx->pc = 0x<function.end>u; before closing the function body, so dispatchLoop's next lookup resumes at the following function instead of spinning on the same one.

Test plan

  • Applied cleanly on top of upstream main
  • Confirmed via live debugger session that the affected function (sub_00100008_0x100008) previously left ctx->pc unchanged after returning
  • Would appreciate a maintainer/CI build+regression pass, since I don't have a local build environment set up against this exact upstream checkout

…ting branch

FunctionEmitter::emit only ever advances ctx->pc via the per-instruction
`ctx->pc = 0x<addr>u;` assignment (overwritten by the next instruction in
the same function) or via handleBranchDelaySlots when the last instruction
is a branch/jump. A function whose last instruction is neither (e.g. a
lone padduw/NOP-style instruction with no terminator) leaves ctx->pc
pointing at its own last instruction forever after returning, since
nothing ever advances it to the next function.

dispatchLoop then reads ctx->pc, looks up the same function, and calls it
again -- forever. No exception, no crash, just an infinite loop that
silently never makes forward progress.

Reproduced on SDBZ's SLUS_214.42 ELF entry point: 0x100008 is emitted as a
standalone 1-instruction function (padduw $at, $zero, $zero) with no
branch, causing dispatchLoop to spin on pc=0x100008 indefinitely.

Fix: track whether the last processed instruction had a delay slot (i.e.
was a branch/jump); if the function ends without one, emit an
unconditional ctx->pc = function.end before closing the function so
dispatchLoop resumes at the next function instead of spinning.
Comment thread ps2xRecomp/src/lib/function_emitter.cpp Outdated
}
}

// If the function's last instruction is not a branch/jump (no delay

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We dont need this comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or at least reduce maybe is too verbose

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants