{"record":{"id":"19aeae34dfb5836c","repo":"ruby/ruby","slug":"can-only-split-memory-addresses","errorCode":null,"errorMessage":"Can only split memory addresses.","messagePattern":"Can only split memory addresses\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"zjit/src/backend/arm64/mod.rs","lineNumber":253,"sourceCode":"    {\n        /// When you're storing a register into a memory location or loading a\n        /// memory location into a register, the displacement from the base\n        /// register of the memory location must fit into 9 bits. If it doesn't,\n        /// then we need to load that memory address into a register first.\n        fn split_memory_address(asm: &mut Assembler, opnd: Opnd) -> Opnd {\n            match opnd {\n                Opnd::Mem(mem) => {\n                    if mem_disp_fits_bits(mem.disp) {\n                        opnd\n                    } else if asm.accept_scratch_reg {\n                        asm.lea_into(SCRATCH1_OPND, Opnd::Mem(Mem { num_bits: 64, ..mem }));\n                        Opnd::mem(mem.num_bits, SCRATCH1_OPND, 0)\n                    } else {\n                        let base = asm.lea(Opnd::Mem(Mem { num_bits: 64, ..mem }));\n                        Opnd::mem(mem.num_bits, base, 0)\n                    }\n                },\n                _ => unreachable!(\"Can only split memory addresses.\")\n            }\n        }\n\n        /// Any memory operands you're sending into an Op::Load instruction need\n        /// to be split in case their displacement doesn't fit into 9 bits.\n        fn split_load_operand(asm: &mut Assembler, opnd: Opnd) -> Opnd {\n            match opnd {\n                Opnd::Reg(_) | Opnd::VReg { .. } => opnd,\n                Opnd::Mem(_) => {\n                    let split_opnd = split_memory_address(asm, opnd);\n                    let out_opnd = asm.load(split_opnd);\n                    // Many Arm insns support only 32-bit or 64-bit operands. asm.load with fewer\n                    // bits zero-extends the value, so it's safe to recognize it as a 32-bit value.\n                    if out_opnd.rm_num_bits() < 32 {\n                        out_opnd.with_num_bits(32)\n                    } else {\n                        out_opnd\n                    }","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/zjit/src/backend/arm64/mod.rs#L235-L271","documentation":"Panic in the arm64 helper `split_memory_address`, which exists solely to rewrite a memory operand whose displacement does not fit the 9-bit unscaled-offset encoding (using `lea`/`lea_into` into a scratch register). Its match only accepts `Opnd::Mem`; any other operand variant is a caller bug — there is no address to split.","triggerScenarios":"Calling `split_memory_address(asm, opnd)` (directly or by adding a new call site) with an `Opnd::Reg`, `Opnd::UImm`, `Opnd::None`, etc. Note the sibling `split_load_operand` deliberately guards with `Opnd::Mem(_)` before calling it; new code that skips that guard hits the `unreachable!`.","commonSituations":"Refactoring operand splitting so non-Mem operands are routed into `split_memory_address`; writing a new instruction rule that assumes its operand is always memory without checking the IR can actually produce registers/immediates there.","solutions":["Guard the call: only invoke `split_memory_address` when `matches!(opnd, Opnd::Mem(_))`; pass other operands through unchanged.","Prefer the existing wrapper `split_load_operand`, which already passes Reg/VReg through and only splits Mem operands.","If a new instruction can take Mem or Reg in the same slot, mirror the `match opnd { Opnd::Mem(mem) => ..., _ => opnd }` shape used at this call site."],"exampleFix":"// before\nlet opnd = split_memory_address(asm, opnd); // opnd may be Opnd::Reg -> panics\n\n// after\nlet opnd = match opnd {\n    Opnd::Mem(_) => split_memory_address(asm, opnd),\n    other => other,\n};","handlingStrategy":"type-guard","validationCode":"let split = match opnd {\n    Opnd::Mem(_) => split_memory_address(asm, opnd),\n    other => other,\n};","typeGuard":"fn is_splittable_address(opnd: &Opnd) -> bool {\n    matches!(opnd, Opnd::Mem(_))\n}","tryCatchPattern":null,"preventionTips":["Only call split_memory_address under a matches!(opnd, Opnd::Mem(_)) guard.","Prefer split_load_operand, which already filters non-Mem operands.","When adding call sites, mirror the existing match/guard shape used by Store and Mov lowering."],"tags":["arm64","operand-splitting","memory-addressing","panic"],"backgroundTag":"non-memory-opnd-passed-to-address-split","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}