From 2584583c426e8d3f748944a8b2a5efacb9fe4078 Mon Sep 17 00:00:00 2001 From: James Sandri <7078671+jlsandri@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:00:08 +1100 Subject: [PATCH] Fix truncated function codegen: safety fallback + entry slice boundary fix Two fixes for functions that end without jr $ra (issue #86): 1. code_generator.cpp: Emit implicit `ctx->pc = GPR_U32(ctx, 31); return;` at end of every generated function. For correct functions this is unreachable dead code. For functions with wrong TOML/CSV boundaries, it returns via $ra instead of leaving ctx->pc in a bad state that cascades returns up the entire call chain. 2. ps2_recompiler.cpp: When reslicing entry functions, skip boundary starts that fall inside the containing parent function. Ghidra sub-functions (sub_xxx) inside a parent were creating false boundaries that truncated sibling entry slices before their jr $ra. --- ps2xRecomp/src/lib/code_generator.cpp | 6 ++++++ ps2xRecomp/src/lib/ps2_recompiler.cpp | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ps2xRecomp/src/lib/code_generator.cpp b/ps2xRecomp/src/lib/code_generator.cpp index 666a59e7..757ec3f8 100644 --- a/ps2xRecomp/src/lib/code_generator.cpp +++ b/ps2xRecomp/src/lib/code_generator.cpp @@ -1003,6 +1003,12 @@ namespace ps2recomp } } + // Safety fallback: if execution reaches here, the function boundary + // was incomplete (missing jr $ra). Return via $ra to prevent ctx->pc + // from being left in a bad state, which would cascade returns up the + // entire call chain. For correctly-terminated functions this is + // unreachable dead code after the jr $ra return block. + ss << " ctx->pc = GPR_U32(ctx, 31); return;\n"; ss << "}\n"; return ss.str(); } diff --git a/ps2xRecomp/src/lib/ps2_recompiler.cpp b/ps2xRecomp/src/lib/ps2_recompiler.cpp index 2ea7d54c..36276d54 100644 --- a/ps2xRecomp/src/lib/ps2_recompiler.cpp +++ b/ps2xRecomp/src/lib/ps2_recompiler.cpp @@ -636,10 +636,22 @@ namespace ps2recomp const Function *containingFunction = findContainingFunction(function.start); uint32_t sliceEndAddress = containingFunction ? containingFunction->end : function.end; + // Only clamp to boundaries OUTSIDE the containing function. + // Sub-functions inside the parent (e.g. Ghidra-detected sub_xxx) + // must not truncate sibling entry slices that share the parent range. + uint32_t parentEnd = sliceEndAddress; auto nextIt = std::upper_bound(boundaryStarts.begin(), boundaryStarts.end(), function.start); - if (nextIt != boundaryStarts.end() && *nextIt < sliceEndAddress) + while (nextIt != boundaryStarts.end() && *nextIt < parentEnd) { + // Skip boundaries inside the containing function — they are + // sub-functions, not real function starts that should truncate us. + if (containingFunction && *nextIt < containingFunction->end) + { + ++nextIt; + continue; + } sliceEndAddress = *nextIt; + break; } if (sliceEndAddress <= function.start)