diff --git a/src/backend/llvm/register_state.cpp b/src/backend/llvm/register_state.cpp index 1a9bd5d..f76697c 100644 --- a/src/backend/llvm/register_state.cpp +++ b/src/backend/llvm/register_state.cpp @@ -1,4 +1,5 @@ #include "backend/llvm/emitter.h" + #include "cpu/cpu.h" #include @@ -138,6 +139,43 @@ void FunctionEmitter::scanRegionLeaders() { region_leaders_[i] = true; if (i + 1u < source_.block_count && term.kind != DOLIR_TERM_FALLTHROUGH) region_leaders_[i + 1u] = true; + // A block that writes MSR can return to the dispatcher and resume at the + // NEXT instruction, so that instruction has to be enterable. Terminators + // are already covered above; this catches the fallthrough case, which is + // mtmsr. + // + // Keyed on an MSR write rather than on MAY_EXIT generally. MAY_EXIT is + // carried by a large number of instructions -- the broader rule fired + // 160,525 times on Colosseum -- and every extra leader fragments a region, + // which costs optimisation. Measured: the broad rule cost plain llvm + // 5-16% on the three titles that never needed it, while only a handful of + // addresses are ever actually re-entered. + // + // Without this, re-entry lands on a non-leader address, the entry dispatch + // has no case for it, and the runtime interprets from there until it + // reaches the next leader. Colosseum calls OSRestoreInterrupts roughly + // every 139 guest cycles, and its last two instructions sit exactly in that + // gap: 166M interpreted instructions, 92% of all dispatches in that arm. + // The C backend never pays this because it labels every address. + // + // REQUIRES the runtime to deliver pending external interrupts at the + // post-mtmsr boundary. emitStateWrite side-exits to guest_pc+4 when MSR[EE] + // goes 0->1; before this rule the address was not enterable, so the runtime + // interpreted from there and delivered the interrupt as a side effect of + // that detour. Removing the detour without the runtime half leaves the + // guest spinning with EE set on an interrupt that never arrives -- Colosseum + // advances 19 frames in 20s instead of 1069. native_exc is the tell: 312 + // while hung, 25,009 once delivery is restored. + if (i + 1u < source_.block_count) { + const DolIRBlock &body = source_.blocks[i]; + for (u32 n = 0; n < body.instruction_count; n++) { + if (dolir_state_mask_test(body.instructions[n].state_defs, + DOLIR_STATE_MSR)) { + region_leaders_[i + 1u] = true; + break; + } + } + } u32 count = term.kind == DOLIR_TERM_COND_BRANCH ? 2u : term.kind == DOLIR_TERM_BRANCH ? 1u : term.kind == DOLIR_TERM_INDIRECT ? 2u