ruby/ruby · error
Invalid operand combination to tst instruction.
Error message
Invalid operand combination to tst instruction.
What it means
zjit's tst() accepts (Reg, Reg) register masks and (Reg, UImm) immediate masks; any other pair — signed Imm, Mem, None — panics here. For the UImm path the immediate must additionally be a legal A64 logical bitmask immediate (a repeating one-bit pattern); an unencodable value fails inside BitmaskImmediate::new_32b_reg(...).unwrap() with a different panic.
Source
Thrown at zjit/src/asm/arm64/mod.rs:1193
/// TST - test the bits of a register against a mask, then update flags
pub fn tst(cb: &mut CodeBlock, rn: A64Opnd, rm: A64Opnd) {
let bytes: [u8; 4] = match (rn, rm) {
(A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => {
assert!(rn.num_bits == rm.num_bits, "All operands must be of the same size.");
LogicalReg::tst(rn.reg_no, rm.reg_no, rn.num_bits).into()
},
(A64Opnd::Reg(rn), A64Opnd::UImm(imm)) => {
let bitmask_imm = if rn.num_bits == 32 {
BitmaskImmediate::new_32b_reg(imm.try_into().unwrap())
} else {
imm.try_into()
}.unwrap();
LogicalImm::tst(rn.reg_no, bitmask_imm, rn.num_bits).into()
},
_ => panic!("Invalid operand combination to tst instruction."),
};
cb.write_bytes(&bytes);
}
/// CBZ - branch if a register is zero
pub fn cbz(cb: &mut CodeBlock, rt: A64Opnd, offset: InstructionOffset) {
assert!(imm_fits_bits(offset.into(), 19), "jump offset for cbz must fit in 19 bits");
let bytes: [u8; 4] = if let A64Opnd::Reg(rt) = rt {
cbz_cbnz(rt.num_bits, false, offset, rt.reg_no)
} else {
panic!("Invalid operand combination to cbz instruction.")
};
cb.write_bytes(&bytes);
}
/// CBNZ - branch if a register is non-zeroView on GitHub (pinned to 0e5b888e1c)
Solutions
- Build the mask as unsigned: A64Opnd::new_uimm(mask).
- For masks that are not a valid bitmask immediate (e.g. 0x101), materialize the mask into a register and use tst(cb, Reg, Reg).
- Load the tested value into a register first if it is in memory.
Example fix
// before: signed Imm mask -> panic tst(cb, W0, A64Opnd::new_imm(0xFF)); // after: unsigned mask, or register form for irregular masks tst(cb, W0, A64Opnd::new_uimm(0xFF)); // or: movz w1, #0x101-ish irregular mask into w1, then tst(cb, W0, W1);
Defensive patterns
Strategy: validation
Validate before calling
fn can_tst(rn: &A64Opnd, rm: &A64Opnd) -> bool {
matches!((rn, rm), (A64Opnd::Reg(_), A64Opnd::Reg(_)) | (A64Opnd::Reg(_), A64Opnd::UImm(_)))
} Prevention
- Convert i64 mask constants to u64 (new_uimm(mask as u64)) before tst.
- For non-repeating masks, prefer the register form: materialize mask, then tst(Reg, Reg).
- Keep a can_encode_bitmask(mask, width) check next to your mask-producing code.
When it happens
Trigger: Calling tst(cb, rn, A64Opnd::new_imm(0xFF)) with a signed Imm instead of new_uimm(0xFF); passing a Mem operand as the mask; testing a memory operand directly. For 32-bit registers the mask must be encodable via new_32b_reg; masks like 0x80 (single bit) are fine, arbitrary values like 0x101 are not and fail the unwrap instead.
Common situations: Lowering 'x & mask == 0' checks from an IR whose constant nodes default to signed i64 (new_imm); reusing x86 'test' idioms where the mask came from a computation that produced i64 -1 (0xFFFFFFFF...) rather than u64 !0; testing flag words before they were loaded into a register.
Related errors
- Invalid operand combination to subs instruction.
- Invalid operand combination to mov instruction: {rd:?}, {rm:
- Invalid operand combination to orr instruction.
- Invalid operand combination to sturh instruction: {rt:?}, {r
- Invalid operand combination to sturb instruction: {rt:?}, {r
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/d9777403dcd87cf0.
Report an issue: GitHub.