ruby/ruby · error
Can only split memory addresses.
Error message
Can only split memory addresses.
What it means
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.
Source
Thrown at zjit/src/backend/arm64/mod.rs:253
{
/// When you're storing a register into a memory location or loading a
/// memory location into a register, the displacement from the base
/// register of the memory location must fit into 9 bits. If it doesn't,
/// then we need to load that memory address into a register first.
fn split_memory_address(asm: &mut Assembler, opnd: Opnd) -> Opnd {
match opnd {
Opnd::Mem(mem) => {
if mem_disp_fits_bits(mem.disp) {
opnd
} else if asm.accept_scratch_reg {
asm.lea_into(SCRATCH1_OPND, Opnd::Mem(Mem { num_bits: 64, ..mem }));
Opnd::mem(mem.num_bits, SCRATCH1_OPND, 0)
} else {
let base = asm.lea(Opnd::Mem(Mem { num_bits: 64, ..mem }));
Opnd::mem(mem.num_bits, base, 0)
}
},
_ => unreachable!("Can only split memory addresses.")
}
}
/// Any memory operands you're sending into an Op::Load instruction need
/// to be split in case their displacement doesn't fit into 9 bits.
fn split_load_operand(asm: &mut Assembler, opnd: Opnd) -> Opnd {
match opnd {
Opnd::Reg(_) | Opnd::VReg { .. } => opnd,
Opnd::Mem(_) => {
let split_opnd = split_memory_address(asm, opnd);
let out_opnd = asm.load(split_opnd);
// Many Arm insns support only 32-bit or 64-bit operands. asm.load with fewer
// bits zero-extends the value, so it's safe to recognize it as a 32-bit value.
if out_opnd.rm_num_bits() < 32 {
out_opnd.with_num_bits(32)
} else {
out_opnd
}View on GitHub (pinned to 0e5b888e1c)
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.
Example fix
// before
let opnd = split_memory_address(asm, opnd); // opnd may be Opnd::Reg -> panics
// after
let opnd = match opnd {
Opnd::Mem(_) => split_memory_address(asm, opnd),
other => other,
}; Defensive patterns
Strategy: type-guard
Validate before calling
let split = match opnd {
Opnd::Mem(_) => split_memory_address(asm, opnd),
other => other,
}; Type guard
fn is_splittable_address(opnd: &Opnd) -> bool {
matches!(opnd, Opnd::Mem(_))
} Prevention
- 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.
When it happens
Trigger: 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!`.
Common situations: 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.
Related errors
- Unexpected Insn::Lea operand in arm64_emit: {opnd:?}
- Invalid operand combination to ands instruction.
- Invalid operand combination to asr instruction: asr {rd:?},
- Invalid operand to blr instruction.
- Invalid operand to br instruction.
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/19aeae34dfb5836c.
Report an issue: GitHub.