{"record":{"id":"603e05045d4113f0","repo":"ruby/ruby","slug":"attempted-to-lower-an-opnd-mem-with-a-membase-st","errorCode":null,"errorMessage":"attempted to lower an Opnd::Mem with a MemBase::Stack/StackIndirect base","messagePattern":"attempted to lower an Opnd::Mem with a MemBase::Stack/StackIndirect base","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"zjit/src/backend/arm64/mod.rs","lineNumber":86,"sourceCode":"        self.jmp_ptr_bytes() as i32 / 4 + 1\n    }\n}\n\n/// Map Opnd to A64Opnd\nimpl From<Opnd> for A64Opnd {\n    fn from(opnd: Opnd) -> Self {\n        match opnd {\n            Opnd::UImm(value) => A64Opnd::new_uimm(value),\n            Opnd::Imm(value) => A64Opnd::new_imm(value),\n            Opnd::Reg(reg) => A64Opnd::Reg(reg),\n            Opnd::Mem(Mem { base: MemBase::Reg(reg_no), num_bits, disp }) => {\n                A64Opnd::new_mem(num_bits, A64Opnd::Reg(A64Reg { num_bits, reg_no }), disp)\n            },\n            Opnd::Mem(Mem { base: MemBase::VReg(_), .. }) => {\n                panic!(\"attempted to lower an Opnd::Mem with a MemBase::VReg base\")\n            },\n            Opnd::Mem(Mem { base: MemBase::Stack { .. } | MemBase::StackIndirect { .. }, .. }) => {\n                panic!(\"attempted to lower an Opnd::Mem with a MemBase::Stack/StackIndirect base\")\n            },\n            Opnd::VReg { .. } => panic!(\"attempted to lower an Opnd::VReg\"),\n            Opnd::Value(_) => panic!(\"attempted to lower an Opnd::Value\"),\n            Opnd::None => panic!(\n                \"Attempted to lower an Opnd::None. This often happens when an out operand was not allocated for an instruction because the output of the instruction was not used. Please ensure you are using the output.\"\n            ),\n        }\n    }\n}\n\n/// Also implement going from a reference to an operand for convenience.\nimpl From<&Opnd> for A64Opnd {\n    fn from(opnd: &Opnd) -> Self {\n        A64Opnd::from(*opnd)\n    }\n}\n\nfn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr) {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/zjit/src/backend/arm64/mod.rs#L68-L104","documentation":"Panic in `impl From<Opnd> for A64Opnd` when lowering an `Opnd::Mem` whose base is `MemBase::Stack` or `MemBase::StackIndirect`. Stack-slot references are IR-level placeholders: before emission, earlier passes must rewrite them into concrete frame-pointer (X29)-relative addresses with a computed displacement. A stack base surviving to `A64Opnd::from` means that stack-lowering pass never ran on this operand, so there is no physical address to encode.","triggerScenarios":"Emitting an instruction that references a spill slot or stack-local directly in `arm64_emit` (the emit match handles only `MemBase::Reg`); pushing a hand-built `Insn` with a Stack-based Mem into the instruction stream after the stack-slot rewriting pass; adding a new Insn variant whose operands are not visited by the stack-lowering pass.","commonSituations":"Extending the backend with a new instruction that takes memory operands and forgetting to route it through the stack-slot lowering pass; test harnesses that build small instruction lists by hand and call the emitter directly; refactors that reorder passes so stack rewriting happens after emit.","solutions":["Run the pass that rewrites `MemBase::Stack`/`StackIndirect` to frame-pointer-relative `MemBase::Reg` before emission (check the pass pipeline order in the arm64 backend).","If you are emitting post-regalloc code by hand, address slots via explicit X29-relative `Opnd::mem(bits, X29_OPND, offset)` built from the frame layout.","When adding a new Insn kind, list its Mem operands for the stack-lowering pass so they get rewritten like existing instructions."],"exampleFix":"// before\nlet slot = Opnd::Mem(Mem { num_bits: 64, base: MemBase::Stack { slot: 2 }, disp: 0 });\nasm.push_insn(Insn::Load { opnd: slot, out: dst }); // panics at lowering\n\n// after: rewrite to frame-pointer-relative address before emit\nlet fp = Opnd::Reg(REG_FP);\nlet slot = Opnd::mem(64, fp, frame_offset_of_slot(2));\nasm.push_insn(Insn::Load { opnd: slot, out: dst });","handlingStrategy":"validation","validationCode":"fn stack_bases_lowered(insns: &[Insn]) -> bool {\n    insns.iter().all(|insn| insn.opnds().iter().all(|o| !matches!(o,\n        Opnd::Mem(Mem { base: MemBase::Stack { .. } | MemBase::StackIndirect { .. }, .. }))))\n}\nassert!(stack_bases_lowered(&insns));","typeGuard":"fn is_fp_relative_mem(opnd: &Opnd) -> bool {\n    matches!(opnd, Opnd::Mem(Mem { base: MemBase::Reg(no), .. }) if *no == FP_REG_NO)\n}","tryCatchPattern":null,"preventionTips":["Keep stack-slot rewriting ahead of emission in the pass pipeline.","When emitting hand-written frames, build X29-relative Opnd::mem directly from the frame layout.","Register new Insn kinds with the stack-lowering pass so their Mem operands get rewritten."],"tags":["arm64","stack-slot","operand-lowering","frame-layout","panic"],"backgroundTag":"stack-slot-not-lowered","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}