From 48bc21f08d87fd27bed13818ef73c44cd501e6c7 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sun, 23 Aug 2026 13:20:50 -0700 Subject: [PATCH] recompiler: tail-transfer image-edge fallthroughs --- recompiler/src/code_generator.cpp | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index c7bd9b48..68a0c5fe 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -2788,6 +2788,24 @@ GeneratedFunction CodeGenerator::generate_function( fallthrough_name); } } + } else if (!cfg.block_order.empty()) { + // No in-image fallthrough function exists (region/image boundary): a + // reachable final block that runs off the end must publish its + // continuation PC. Falling off the C body would return with the + // entry-switch's consumed cpu->pc == 0, which the top-level trampoline + // reads as "program ended". + const BasicBlock& last_block = cfg.blocks.at(cfg.block_order.back()); + bool runs_off_end = + last_block.exit_instr.type == ControlFlowType::None || + ((last_block.exit_instr.type == ControlFlowType::Branch || + last_block.exit_instr.type == ControlFlowType::Jump) && + last_block.successors.empty()); + bool is_reachable = last_block.is_entry || !last_block.predecessors.empty(); + if (runs_off_end && is_reachable) { + body_ss << fmt::format( + " cpu->pc = 0x{:08X}u; return; /* image-edge fallthrough: tail-transfer */\n", + last_block.end_addr + 4u); + } } body_ss << " ; /* label compatibility: C requires a statement after the last label */\n"; body_ss << "}\n"; @@ -3017,6 +3035,23 @@ std::vector CodeGenerator::generate_alias_group( body << fmt::format(" {}(cpu); /* fallthrough to next function */\n", fallthrough_name); } + } else if (!cfg.block_order.empty() && + live_blocks.count(cfg.block_order.back())) { + // Image-edge fallthrough (mirrors generate_function): no in-image next + // function exists, so a live final block that runs off the end must + // tail-transfer to its continuation PC instead of falling off the C + // body with cpu->pc still consumed to 0. + const BasicBlock& last_block = cfg.blocks.at(cfg.block_order.back()); + bool runs_off_end = + (last_block.exit_instr.type == ControlFlowType::None) || + ((last_block.exit_instr.type == ControlFlowType::Branch || + last_block.exit_instr.type == ControlFlowType::Jump) && + last_block.successors.empty()); + if (runs_off_end) { + body << fmt::format( + " cpu->pc = 0x{:08X}u; return; /* image-edge fallthrough: tail-transfer */\n", + last_block.end_addr + 4u); + } } body << " ; /* label compatibility */\n"; body << "}\n";