From 5b01928b26b0c803605efb1bb95d59f1adca1db9 Mon Sep 17 00:00:00 2001 From: Douglas Whittingham Date: Fri, 21 Aug 2026 12:28:58 -1000 Subject: [PATCH] Route fallback blocks through per-edge trampolines so the phi matches the CFG The LLVM backend emits invalid IR on Skyward Sword (SOUE01, Broadway). All 9917 chunk objects emit, then the module verifier rejects it: PHI node entries do not match predecessors! %fallback_pc = phi i32 [ %entry_pc, %cold_entry ], [ -2143668204, %fallback_edge563 ], ... label %fallback_edge563 label %indirect_taken562 Instruction does not dominate all uses! (x3, cascading, on %state.0) emitter.cpp aliased every DOLIR_TERM_FALLBACK block onto the single shared fallback block, whose first instruction is the fallback_pc phi. Only fallbackDestination() and fallbackEdge() register an incoming value, so any code branching to blocks_[i] -- notably the switch built for DOLIR_TERM_INDIRECT -- created a predecessor with no phi entry. Hand out a per-edge trampoline instead of the raw shared block, so every edge carries its own guest pc by construction rather than depending on each call site remembering to register one. fallbackDestination() becomes a thin wrapper over fallbackEdge(), which removes the fragile GetInsertBlock()-at-call-time logic. Why only this title: GM4E01, GLME01 and GC6E01 all build clean on the same backend. SOUE01 is 1,266,744 instructions -- 1.7x Mario Kart -- and Wii/Broadway code carries far more indirect branches, so it is the first to exercise the path. tests/llvm_fixture.cpp gains a case covering indirect dispatch into a fallback continuation with modified state; it fails on the old emitter and passes here. Verified: ctest 27/28 before and after on the same tree (the one failure was the pre-existing module_abi AArch64 enum truncation, since fixed upstream by 528a204); Skyward Sword now emits 9917 objects with no verifier errors; Mario Kart still emits 5803 clean. --- src/backend/llvm/branch_targets.cpp | 4 +--- src/backend/llvm/emitter.cpp | 2 +- tests/llvm_fixture.cpp | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/backend/llvm/branch_targets.cpp b/src/backend/llvm/branch_targets.cpp index 43ed05c..37e8421 100644 --- a/src/backend/llvm/branch_targets.cpp +++ b/src/backend/llvm/branch_targets.cpp @@ -112,9 +112,7 @@ BasicBlock *FunctionEmitter::exitDestination(u32 pc) { BasicBlock * FunctionEmitter::fallbackDestination(const DolIRTerminator &terminator) { - BasicBlock *source = builder_.GetInsertBlock(); - fallback_pc_->addIncoming(builder_.getInt32(terminator.guest_pc), source); - return fallback_block_; + return fallbackEdge(terminator.guest_pc); } BasicBlock *FunctionEmitter::fallbackEdge(u32 pc) { diff --git a/src/backend/llvm/emitter.cpp b/src/backend/llvm/emitter.cpp index e26aa07..b28bea4 100644 --- a/src/backend/llvm/emitter.cpp +++ b/src/backend/llvm/emitter.cpp @@ -80,7 +80,7 @@ bool FunctionEmitter::emit(raw_ostream &diagnostics) { BasicBlock *region = nullptr; for (u32 i = 0; i < source_.block_count; i++) { if (source_.blocks[i].terminator.kind == DOLIR_TERM_FALLBACK) { - blocks_[i] = fallback_block_; + blocks_[i] = fallbackEdge(source_.blocks[i].guest_address); region = nullptr; continue; } diff --git a/tests/llvm_fixture.cpp b/tests/llvm_fixture.cpp index a6b8b59..8c3cbf2 100644 --- a/tests/llvm_fixture.cpp +++ b/tests/llvm_fixture.cpp @@ -42,6 +42,10 @@ static u32 psq_dform(bool store, u8 reg, u8 base, u16 displacement) { (displacement & 0x0fffu); } +static u32 branch(bool link, u32 delta) { + return 0x48000000u | (delta & 0x03fffffcu) | (link ? 1u : 0u); +} + static bool add_chunk(DolIRModule *module, const u32 *words, u32 count, u32 address) { PPCInst *instructions = new PPCInst[count]; @@ -339,6 +343,17 @@ int main(int argc, char **argv) { CHECK(add_chunk(&module, return_continuation_words, 3, 0x80003B08u)); CHECK(add_chunk(&module, return_callee_words, 2, 0x80003C00u)); + // Both indirect returns can dispatch to the linked branch's fallback + // continuation. The mtctr/bctr path also carries modified state into it. + const u32 indirect_fallback_words[] = { + branch(true, 0x10u), + 0x00000000u, + mtspr(3, 9), + 0x4E800420u, + 0x4E800020u, + }; + CHECK(add_chunk(&module, indirect_fallback_words, 5, 0x80003D00u)); + CHECK(dolir_verify(&module, stderr)); DolLLVMOptions options{}; options.optimization_level = 2;