{"record":{"id":"5b51e330ed3a915d","repo":"ruby/ruby","slug":"target-sideexit-should-have-been-compiled-by-comp","errorCode":null,"errorMessage":"Target::SideExit should have been compiled by compile_exits","messagePattern":"Target::SideExit should have been compiled by compile_exits","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"zjit/src/backend/arm64/mod.rs","lineNumber":991,"sourceCode":"\n                // We need to make sure we have at least 6 instructions for\n                // every kind of jump for invalidation purposes, so we're\n                // going to write out padding nop instructions here.\n                assert!(num_insns <= cb.conditional_jump_insns());\n                (num_insns..cb.conditional_jump_insns()).for_each(|_| nop(cb));\n            }\n\n            let label = match target {\n                Target::CodePtr(dst_ptr) => {\n                    let dst_addr = dst_ptr.as_offset();\n                    let src_addr = cb.get_write_ptr().as_offset();\n                    generate_branch::<CONDITION>(cb, src_addr, dst_addr);\n                    return;\n                },\n                Target::Label(l) => l,\n                Target::Block(ref edge) => asm.block_label(edge.target),\n                Target::SideExit(..) => {\n                    unreachable!(\"Target::SideExit should have been compiled by compile_exits\")\n                },\n            };\n            // Try to use a single B.cond instruction\n            cb.label_ref(label, 4, |cb, src_addr, dst_addr| {\n                // +1 since src_addr is after the instruction while A64\n                // counts the offset relative to the start.\n                let offset = (dst_addr - src_addr) / 4 + 1;\n                if bcond_offset_fits_bits(offset) {\n                    bcond(cb, CONDITION, InstructionOffset::from_insns(offset as i32));\n                    Ok(())\n                } else {\n                    Err(())\n                }\n            });\n        }\n\n        /// Emit a CBZ or CBNZ which branches when a register is zero or non-zero\n        fn emit_cmp_zero_jump(cb: &mut CodeBlock, reg: A64Opnd, branch_if_zero: bool, target: Target) {","sourceCodeStart":973,"sourceCodeEnd":1009,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/zjit/src/backend/arm64/mod.rs#L973-L1009","documentation":"Panic in `emit_conditional_jump` on arm64 when the branch target is still a `Target::SideExit`. The pipeline invariant is that `compile_exits` runs before final emission and rewrites every side-exit target into a concrete label or code pointer. A `SideExit` target reaching this matcher means that pass was skipped or does not cover the instruction that produced this branch.","triggerScenarios":"Emitting code containing a conditional branch to `Target::SideExit(..)` without running `compile_exits` first; adding a new branch-producing Insn whose side-exit targets are not rewritten by `compile_exits`; reordering backend phases so exit compilation happens after `arm64_emit`.","commonSituations":"Backend pipeline refactors or new conditional-jump instructions added during feature work; unit tests invoking the emitter on partial IR that still carries side-exit edges.","solutions":["Run `compile_exits` before `arm64_emit` (verify phase order in the compile pipeline).","If adding a new branch instruction with side-exit targets, extend `compile_exits` to rewrite its targets like existing jumps.","In tests, lower side-exit targets to labels or code pointers before invoking the emitter."],"exampleFix":"// before\nlet insns = build_ir_with_side_exits();\nbackend.arm64_emit(&insns);   // panics: SideExit not compiled\n\n// after\nlet insns = build_ir_with_side_exits();\nbackend.compile_exits(&mut insns); // rewrites SideExit -> label/code ptr\nbackend.arm64_emit(&insns);","handlingStrategy":"validation","validationCode":"fn side_exits_compiled(insns: &[Insn]) -> bool {\n    insns.iter().all(|insn| insn.branch_target()\n        .map(|t| !matches!(t, Target::SideExit(..))).unwrap_or(true))\n}\nassert!(side_exits_compiled(&linearized), \"run compile_exits before emit\");","typeGuard":"fn is_emittable_target(t: &Target) -> bool {\n    !matches!(t, Target::SideExit(..))\n}","tryCatchPattern":null,"preventionTips":["Keep compile_exits strictly before arm64_emit in the pipeline.","When adding branch instructions, extend compile_exits to rewrite their side-exit targets.","In tests, lower side exits to labels/code pointers before invoking the emitter."],"tags":["arm64","side-exit","branch-target","compile-pipeline","panic"],"backgroundTag":"side-exit-not-compiled","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}