ruby/ruby · error
Invalid operand to br instruction.
Error message
Invalid operand to br instruction.
What it means
br (branch to register) mirrors blr in ZJIT's ARM64 assembler: only A64Opnd::Reg is accepted, and any other operand variant — address immediates, None, signed Imm — hits the catch-all arm and panics. ARM64 indirect jumps require the target to already live in a register.
Source
Thrown at zjit/src/asm/arm64/mod.rs:304
cb.write_bytes(&bytes);
}
/// BLR - branch with link to a register
pub fn blr(cb: &mut CodeBlock, rn: A64Opnd) {
let bytes: [u8; 4] = match rn {
A64Opnd::Reg(rn) => Branch::blr(rn.reg_no).into(),
_ => panic!("Invalid operand to blr instruction."),
};
cb.write_bytes(&bytes);
}
/// BR - branch to a register
pub fn br(cb: &mut CodeBlock, rn: A64Opnd) {
let bytes: [u8; 4] = match rn {
A64Opnd::Reg(rn) => Branch::br(rn.reg_no).into(),
_ => panic!("Invalid operand to br instruction."),
};
cb.write_bytes(&bytes);
}
/// BRK - create a breakpoint
pub fn brk(cb: &mut CodeBlock, imm16: A64Opnd) {
let bytes: [u8; 4] = match imm16 {
A64Opnd::None => Breakpoint::brk(0xf000).into(),
A64Opnd::UImm(imm16) => {
assert!(uimm_fits_bits(imm16, 16), "The immediate operand must be 16 bits or less.");
Breakpoint::brk(imm16 as u16).into()
},
_ => panic!("Invalid operand combination to brk instruction.")
};
cb.write_bytes(&bytes);
}View on GitHub (pinned to 0e5b888e1c)
Solutions
- Load the target into a register first, then br(cb, A64Opnd::Reg(reg))
- For in-range relative jumps use b/bl with an offset instead
- If hit while running stock Ruby with --zjit, update and report the backtrace to ruby-core
Example fix
// before br(cb, A64Opnd::UImm(target_addr)); // immediate operand -> panic // after mov(cb, x16, A64Opnd::UImm(target_addr)); br(cb, A64Opnd::Reg(x16));
Defensive patterns
Strategy: type-guard
Validate before calling
// br wants exactly one Reg operand
fn br_operand_ok(rn: &A64Opnd) -> bool {
matches!(rn, A64Opnd::Reg(_))
}
debug_assert!(br_operand_ok(&target)); Type guard
fn as_reg(o: A64Opnd) -> Option<Reg> {
if let A64Opnd::Reg(r) = o { Some(r) } else { None }
} // jump targets must already be in a register Try / catch
// #[should_panic(expected = "Invalid operand to br instruction.")] // unit-test that non-register operands are rejected loudly during development
Prevention
- Load jump-table or computed targets into registers before emitting br
- Encode the rule once in a jump_reg(cb, reg) wrapper used by all codegen
- Keep relative-branch offset checks (b_offset_fits_bits) in mind for long jumps
When it happens
Trigger: br(cb, A64Opnd::UImm(target_addr)) — jumping to a raw address; br(cb, A64Opnd::None) from a defaulted-operand code path; operand construction refactors that stopped producing Reg.
Common situations: Writing ZJIT dispatch/jump-table code on ARM64; porting jump logic from an x86 backend where absolute jumps are immediates.
Related errors
- Invalid operand combination to ands instruction.
- Invalid operand combination to asr instruction: asr {rd:?},
- Invalid operand to blr instruction.
- Invalid operand combination to brk instruction.
- Invalid operand combination to cmp instruction.
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/9dfa0a526cad94e9.
Report an issue: GitHub.