ruby/ruby · error
attempted to lower an Opnd::Mem with a MemBase::Stack/StackI
Error message
attempted to lower an Opnd::Mem with a MemBase::Stack/StackIndirect base
What it means
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.
Source
Thrown at zjit/src/backend/arm64/mod.rs:86
self.jmp_ptr_bytes() as i32 / 4 + 1
}
}
/// Map Opnd to A64Opnd
impl From<Opnd> for A64Opnd {
fn from(opnd: Opnd) -> Self {
match opnd {
Opnd::UImm(value) => A64Opnd::new_uimm(value),
Opnd::Imm(value) => A64Opnd::new_imm(value),
Opnd::Reg(reg) => A64Opnd::Reg(reg),
Opnd::Mem(Mem { base: MemBase::Reg(reg_no), num_bits, disp }) => {
A64Opnd::new_mem(num_bits, A64Opnd::Reg(A64Reg { num_bits, reg_no }), disp)
},
Opnd::Mem(Mem { base: MemBase::VReg(_), .. }) => {
panic!("attempted to lower an Opnd::Mem with a MemBase::VReg base")
},
Opnd::Mem(Mem { base: MemBase::Stack { .. } | MemBase::StackIndirect { .. }, .. }) => {
panic!("attempted to lower an Opnd::Mem with a MemBase::Stack/StackIndirect base")
},
Opnd::VReg { .. } => panic!("attempted to lower an Opnd::VReg"),
Opnd::Value(_) => panic!("attempted to lower an Opnd::Value"),
Opnd::None => panic!(
"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."
),
}
}
}
/// Also implement going from a reference to an operand for convenience.
impl From<&Opnd> for A64Opnd {
fn from(opnd: &Opnd) -> Self {
A64Opnd::from(*opnd)
}
}
fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr) {View on GitHub (pinned to 0e5b888e1c)
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.
Example fix
// before
let slot = Opnd::Mem(Mem { num_bits: 64, base: MemBase::Stack { slot: 2 }, disp: 0 });
asm.push_insn(Insn::Load { opnd: slot, out: dst }); // panics at lowering
// after: rewrite to frame-pointer-relative address before emit
let fp = Opnd::Reg(REG_FP);
let slot = Opnd::mem(64, fp, frame_offset_of_slot(2));
asm.push_insn(Insn::Load { opnd: slot, out: dst }); Defensive patterns
Strategy: validation
Validate before calling
fn stack_bases_lowered(insns: &[Insn]) -> bool {
insns.iter().all(|insn| insn.opnds().iter().all(|o| !matches!(o,
Opnd::Mem(Mem { base: MemBase::Stack { .. } | MemBase::StackIndirect { .. }, .. }))))
}
assert!(stack_bases_lowered(&insns)); Type guard
fn is_fp_relative_mem(opnd: &Opnd) -> bool {
matches!(opnd, Opnd::Mem(Mem { base: MemBase::Reg(no), .. }) if *no == FP_REG_NO)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- attempted to lower an Opnd::Mem with a MemBase::VReg base
- attempted to lower an Opnd::Value
- unexpected combination of operands in Insn::Mov: {dest:?}, {
- Unexpected source operand during arm64_emit: {src:?}
- Unexpected Insn::Lea operand in arm64_emit: {opnd:?}
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/603e05045d4113f0.
Report an issue: GitHub.