{"record":{"id":"5d392937135fb112","repo":"ruby/ruby","slug":"invalid-operand-combination-to-mov-instruction-r","errorCode":null,"errorMessage":"Invalid operand combination to mov instruction: {rd:?}, {rm:?}","messagePattern":"Invalid operand combination to mov instruction: (.+?), (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"zjit/src/asm/arm64/mod.rs","lineNumber":709,"sourceCode":"        },\n        (A64Opnd::Reg(rd), A64Opnd::Reg(rm)) => {\n            assert!(rd.num_bits == rm.num_bits, \"Expected registers to be the same size\");\n\n            LogicalReg::mov(rd.reg_no, rm.reg_no, rd.num_bits).into()\n        },\n        (A64Opnd::Reg(rd), A64Opnd::UImm(0)) => {\n            LogicalReg::mov(rd.reg_no, XZR_REG.reg_no, rd.num_bits).into()\n        },\n        (A64Opnd::Reg(rd), A64Opnd::UImm(imm)) => {\n            let bitmask_imm = if rd.num_bits == 32 {\n                BitmaskImmediate::new_32b_reg(imm.try_into().unwrap())\n            } else {\n                imm.try_into()\n            }.unwrap();\n\n            LogicalImm::mov(rd.reg_no, bitmask_imm, rd.num_bits).into()\n        },\n        _ => panic!(\"Invalid operand combination to mov instruction: {rd:?}, {rm:?}\")\n    };\n\n    cb.write_bytes(&bytes);\n}\n\n/// MOVK - move a 16 bit immediate into a register, keep the other bits in place\npub fn movk(cb: &mut CodeBlock, rd: A64Opnd, imm16: A64Opnd, shift: u8) {\n    let bytes: [u8; 4] = match (rd, imm16) {\n        (A64Opnd::Reg(rd), A64Opnd::UImm(imm16)) => {\n            assert!(uimm_fits_bits(imm16, 16), \"The immediate operand must be 16 bits or less.\");\n\n            Mov::movk(rd.reg_no, imm16 as u16, shift, rd.num_bits).into()\n        },\n        _ => panic!(\"Invalid operand combination to movk instruction.\")\n    };\n\n    cb.write_bytes(&bytes);\n}","sourceCodeStart":691,"sourceCodeEnd":727,"githubUrl":"https://github.com/ruby/ruby/blob/0e5b888e1c355f3f728f2659f085820937dada48/zjit/src/asm/arm64/mod.rs#L691-L727","documentation":"Panic from the catch-all '_' arm of the 'mov' emitter. The matched shapes are register-to-register (including special-cased SP/XZR handling) and (Reg, UImm) for immediate moves, where UImm(0) maps to a move from XZR and other values go through BitmaskImmediate encoding. The catch-all fires when rm is a signed Imm(i64), a Mem, or None — or when rd is not a register. Note: a UImm that is not a legal ARM64 bitmask immediate panics separately inside BitmaskImmediate::new(..).unwrap().","triggerScenarios":"mov(cb, X0, A64Opnd::new_imm(42)) panics: the immediate arms match only UImm. mov(cb, X0, A64Opnd::new_mem(64, X1, 0)) or rm = None also panic. mov(cb, X0, A64Opnd::new_uimm(0x30001)) matches the shape but panics inside the bitmask unwrap because 0x30001 is not bitmask-encodable.","commonSituations":"The single most common hit: constants flow through IR as signed i64 and get wrapped with new_imm. Also, materialising arbitrary 64-bit constants (addresses, hashes) that no bitmask immediate can represent — these need a movz/movk sequence or literal load instead of a single mov.","solutions":["Wrap constants as UImm: mov(cb, X0, A64Opnd::new_uimm(42)); convert i64 only after checking v >= 0.","Pre-check bitmask encodability before emitting; if the value cannot be encoded, emit movk/movz-style sequences (movk exists in this module) or use a literal-pool load.","Keep rd as a register operand (W/X constants); never pass Mem or None as rd.","For register-to-register moves keep both operands Reg with compatible widths (the SP/XZR special case expects 64-bit rm)."],"exampleFix":"// before\nmov(cb, X0, A64Opnd::new_imm(0x30001)); // Imm not UImm; also not bitmask-encodable\n\n// after\nlet v: u64 = 0x30001;\nmov(cb, X0, A64Opnd::new_uimm(v & 0xffff));      // movz x0, #(v & 0xffff)\nmovk(cb, X0, A64Opnd::new_uimm(v >> 16), 16);    // movk x0, #(v >> 16), lsl #16","handlingStrategy":"validation","validationCode":"fn mov_ok(rd: &A64Opnd, rm: &A64Opnd) -> bool {\n    if !matches!(rd, A64Opnd::Reg(_)) { return false; }\n    match rm {\n        A64Opnd::Reg(_) => true,\n        A64Opnd::UImm(0) => true,\n        A64Opnd::UImm(v) => bitmask_encodable(*v), // reject values BitmaskImmediate cannot encode\n        _ => false, // Imm, Mem, None all panic\n    }\n}\n// bitmask_encodable: true if v == 0, all-ones per width, or a replicable pattern\n// (e.g. via the same N/imms logic BitmaskImmediate uses)","typeGuard":"fn as_reg_or_uimm(o: &A64Opnd) -> Option<Either<A64Reg, u64>> {\n    match o {\n        A64Opnd::Reg(r) => Some(Either::Reg(*r)),\n        A64Opnd::UImm(v) => Some(Either::Imm(*v)),\n        _ => None,\n    }\n}","tryCatchPattern":"use std::panic::{catch_unwind, AssertUnwindSafe};\nlet mark = cb_len(cb);\nif catch_unwind(AssertUnwindSafe(|| asm::mov(cb, rd, rm))).is_err() {\n    cb_truncate(cb, mark);\n    return Err(CodegenError::BadOperands(\"mov\"));\n}","preventionTips":["Never wrap constants for mov with new_imm; use new_uimm after asserting the value is non-negative.","Pre-check bitmask encodability; for arbitrary 64-bit constants emit a movz/movk sequence (movk exists in this module) or a literal load.","Reject Imm/Mem/None source operands in your lowering validator before they reach the emitter.","Add tests for UImm(0) (moves from XZR) and known bitmask patterns (0xff, 0xffff, repeating masks)."],"tags":["arm64","assembler","jit","rust","operand-mismatch","panic","immediate-encoding","bitmask-immediate"],"backgroundTag":"invalid-instruction-operands","analyzedSha":"0e5b888e1c355f3f728f2659f085820937dada48","analyzedAt":"2026-08-21T14:25:43.473Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}