{"record":{"id":"1496bfbbb86d1ddd","repo":"ruby/ruby","slug":"invalid-operand-combination-to-cbz-instruction","errorCode":null,"errorMessage":"Invalid operand combination to cbz instruction.","messagePattern":"Invalid operand combination to cbz instruction\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"zjit/src/asm/arm64/mod.rs","lineNumber":1205,"sourceCode":"            } else {\n                imm.try_into()\n            }.unwrap();\n\n            LogicalImm::tst(rn.reg_no, bitmask_imm, rn.num_bits).into()\n        },\n        _ => panic!(\"Invalid operand combination to tst instruction.\"),\n    };\n\n    cb.write_bytes(&bytes);\n}\n\n/// CBZ - branch if a register is zero\npub fn cbz(cb: &mut CodeBlock, rt: A64Opnd, offset: InstructionOffset) {\n    assert!(imm_fits_bits(offset.into(), 19), \"jump offset for cbz must fit in 19 bits\");\n    let bytes: [u8; 4] = if let A64Opnd::Reg(rt) = rt {\n        cbz_cbnz(rt.num_bits, false, offset, rt.reg_no)\n    } else {\n        panic!(\"Invalid operand combination to cbz instruction.\")\n    };\n\n    cb.write_bytes(&bytes);\n}\n\n/// CBNZ - branch if a register is non-zero\npub fn cbnz(cb: &mut CodeBlock, rt: A64Opnd, offset: InstructionOffset) {\n    assert!(imm_fits_bits(offset.into(), 19), \"jump offset for cbz must fit in 19 bits\");\n    let bytes: [u8; 4] = if let A64Opnd::Reg(rt) = rt {\n        cbz_cbnz(rt.num_bits, true, offset, rt.reg_no)\n    } else {\n        panic!(\"Invalid operand combination to cbnz instruction.\")\n    };\n\n    cb.write_bytes(&bytes);\n}\n\n/// Encode Compare and Branch on Zero (CBZ) with `op=0` or Compare and Branch on Nonzero (CBNZ)","sourceCodeStart":1187,"sourceCodeEnd":1223,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/zjit/src/asm/arm64/mod.rs#L1187-L1223","documentation":"zjit's cbz() (compare and branch on zero) takes the tested value as A64Opnd::Reg and a typed InstructionOffset. If rt is not a register (Imm, UImm, Mem, None) the function panics with this message. The offset has its own guard: the assert! at function entry rejects offsets that do not fit in 19 bits before the shape check runs.","triggerScenarios":"Calling cbz(cb, rt, offset) where rt is a Mem operand (testing a value still in memory — arm64 has no memory-operand CBZ), an immediate (comparing a constant to zero is a compile-time fact), or None. Out-of-range offsets (>= ±2^18 instructions) fail the 19-bit imm_fits_bits assert first with a different message.","commonSituations":"Porting x86 'cmp [mem], 0 / je' patterns; IR lowering where a spilled virtual register is represented as a memory operand; null-check sequences on pointer values that arrive as Mem operands from the register allocator.","solutions":["Load the value into a register first (ldr), then cbz(cb, reg, offset).","Fold constant-to-zero tests at compile time instead of emitting cbz on an Imm.","Verify rt.is_reg() before emitting when the operand comes from generic IR.","Keep the branch offset within 19 bits; for distant targets, branch to a nearby trampoline."],"exampleFix":"// before: memory operand -> panic\ncbz(cb, A64Opnd::Mem(slot), offset);\n\n// after: load then test\nldr(cb, X9, A64Opnd::Mem(slot));\ncbz(cb, X9, offset);","handlingStrategy":"validation","validationCode":"if !rt.is_reg() {\n    // load into a scratch register first\n    ldr(cb, X9, rt_as_mem);\n    cbz(cb, X9, offset);\n} else {\n    cbz(cb, rt, offset);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Fold 'constant == 0' tests at compile time; never emit cbz on an Imm.","After register allocation, run a reload pass that turns any Mem test operand into a Reg.","Keep branch offsets small (trampoline distant targets) to avoid the separate 19-bit assert."],"tags":["arm64","assembler","operand-validation","branching"],"backgroundTag":"invalid-instruction-operands","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}