From 5c0f771c59ff4eaa2d4804c3037983d2e841143b Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sun, 26 Apr 2026 12:21:13 -0700 Subject: [PATCH] recompilation: extend tolerant emit to print_branch fall-through path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found a third emit-non-compilable-code path during Pokemon Stadium runner build: print_branch lambda (line 371) checks for tail-call candidates when the branch target is outside the function, prints a warning if not found — then falls through to emit `goto L_` to a label that doesn't exist in the generated function. Linker compiles fail with "use of undeclared label L_XXXXXXXX". Fixed by emitting the same recomp_unhandled_branch runtime call as the other tolerant-emit paths, plus an early return so the fall- through to emit_goto doesn't run. Also exposes recomp_unhandled_* declarations in include/recomp.h (after the existing recomp_context definition + recomp_syscall_handler decl) so generated C compiles cleanly without consumer-side header patches. 171 → ~600+ tolerant-emit warnings on Pokemon Stadium pass; full build pipeline now produces a linked PokemonStadiumRecomp.exe that loads librecomp/ultramodern and runs cleanly to a "link-only entry" message. Real boot still needs runtime wiring (renderer, scheduler, init). Co-Authored-By: Claude Opus 4.7 (1M context) --- include/recomp.h | 20 ++++++++++++++++++++ src/recompilation.cpp | 16 +++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/include/recomp.h b/include/recomp.h index 73f3e73..2fbe6c9 100644 --- a/include/recomp.h +++ b/include/recomp.h @@ -1,6 +1,8 @@ #ifndef __RECOMP_H__ #define __RECOMP_H__ +#include + #include #include #include @@ -468,6 +470,24 @@ void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruc void pause_self(uint8_t *rdram); +/* Tolerant-emit runtime entry points. The engine emits calls to these + * for instructions/branches/calls it can't translate at compile time; + * consumers implement them in a runtime hook file (e.g. extras.c). + * See src/recompilation.cpp for the emit sites and PRINCIPLES.md #12 + * for why these exist (loud runtime aborts, NOT stubs). */ +void recomp_unhandled_branch(uint8_t *rdram, recomp_context *ctx, + uint32_t instr_vram, uint32_t branch_target); +void recomp_unhandled_call(uint8_t *rdram, recomp_context *ctx, + uint32_t instr_vram, uint32_t target); +void recomp_unhandled_jalr(uint8_t *rdram, recomp_context *ctx, + uint32_t instr_vram, uint64_t target_value, int rd); +uint64_t recomp_unhandled_cop0_read(uint8_t *rdram, recomp_context *ctx, + uint32_t instr_vram, int cop0_reg); +void recomp_unhandled_cop0_write(uint8_t *rdram, recomp_context *ctx, + uint32_t instr_vram, int cop0_reg, uint64_t value); +void recomp_unhandled_instruction(uint8_t *rdram, recomp_context *ctx, + uint32_t instr_vram, const char *opcode_name); + #ifdef __cplusplus } #endif diff --git a/src/recompilation.cpp b/src/recompilation.cpp index c29bd2e..6aeb4da 100644 --- a/src/recompilation.cpp +++ b/src/recompilation.cpp @@ -387,7 +387,21 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con return true; } - fmt::print(stderr, "[Warn] Function {} is branching outside of the function (to 0x{:08X})\n", func.name, branch_target); + // Branch out of function with no symbol at target. Emit a + // runtime abort instead of `goto L_` to a label that + // never exists. Per project principles: no stub, an + // unimplementable hole surfaced loudly at runtime. + fmt::print(stderr, "[Warn] Function {} is branching outside of the function (to 0x{:08X}) — emitting runtime abort\n", func.name, branch_target); + if (!process_delay_slot(true)) { + return false; + } + print_indent(); + print_indent(); + fmt::print(output_file, "recomp_unhandled_branch(rdram, ctx, 0x{:08X}u, 0x{:08X}u);\n", instr_vram, branch_target); + print_indent(); + print_indent(); + generator.emit_return(context, func_index); + return true; } if (!process_delay_slot(true)) {