ruby/ruby · error

Invalid operand combination to msr instruction

Error message

Invalid operand combination to msr instruction

What it means

Panic from the msr helper in zjit's ARM64 assembler (zjit/src/asm/arm64/mod.rs:776). msr (write general-purpose register to a system register) matches only A64Opnd::Reg(rt); any other rt variant hits the wildcard arm and panics 'Invalid operand combination to msr instruction'. The first parameter is the SystemRegister enum itself, so the failure is always the register-side operand: it must be Reg, not Imm/UImm/Mem/None.

Source

Thrown at zjit/src/asm/arm64/mod.rs:776

/// MRS - move a system register into a general-purpose register
pub fn mrs(cb: &mut CodeBlock, rt: A64Opnd, systemregister: SystemRegister) {
    let bytes: [u8; 4] = match rt {
        A64Opnd::Reg(rt) => {
            SysReg::mrs(rt.reg_no, systemregister).into()
        },
        _ => panic!("Invalid operand combination to mrs instruction")
    };

    cb.write_bytes(&bytes);
}

/// MSR - move a general-purpose register into a system register
pub fn msr(cb: &mut CodeBlock, systemregister: SystemRegister, rt: A64Opnd) {
    let bytes: [u8; 4] = match rt {
        A64Opnd::Reg(rt) => {
            SysReg::msr(systemregister, rt.reg_no).into()
        },
        _ => panic!("Invalid operand combination to msr instruction")
    };

    cb.write_bytes(&bytes);
}

/// MUL - multiply two registers, put the result in a third register
pub fn mul(cb: &mut CodeBlock, rd: A64Opnd, rn: A64Opnd, rm: A64Opnd) {
    let bytes: [u8; 4] = match (rd, rn, rm) {
        (A64Opnd::Reg(rd), A64Opnd::Reg(rn), A64Opnd::Reg(rm)) => {
            assert!(rd.num_bits == rn.num_bits && rn.num_bits == rm.num_bits, "Expected registers to be the same size");

            MAdd::mul(rd.reg_no, rn.reg_no, rm.reg_no, rd.num_bits).into()
        },
        _ => panic!("Invalid operand combination to mul instruction")
    };

    cb.write_bytes(&bytes);
}

View on GitHub (pinned to 0e5b888e1c)

Solutions

  1. Load the constant into a register first, then msr with a Reg operand: movz(cb, x0, A64Opnd::new_uimm(0), 0); msr(cb, SystemRegister::FPCR, x0).
  2. Ensure rt is A64Opnd::Reg — check with is_reg() when the operand is computed.
  3. Verify argument order: msr(cb, SystemRegister, rt), the reverse of mrs(cb, rt, SystemRegister).

Example fix

// before
msr(cb, SystemRegister::FPCR, A64Opnd::new_uimm(0)); // panics: UImm rt

// after
movz(cb, x0, A64Opnd::new_uimm(0), 0);
msr(cb, SystemRegister::FPCR, x0);
Defensive patterns

Strategy: type-guard

Validate before calling

assert!(rt.is_reg(), "msr value must be a register operand");
msr(cb, SystemRegister::FPCR, rt);

Type guard

fn msr_operand_ok(rt: A64Opnd) -> bool { rt.is_reg() }

Prevention

When it happens

Trigger: Calling msr(cb, SystemRegister::FPCR, A64Opnd::UImm(0)) hoping to write a constant directly; passing a Mem operand as the value; passing None.

Common situations: Setting floating-point or debug flags (FPCR, DACR) from a JIT; assuming an immediate-to-system-register form exists like some macro-assembler shorthands; swapping mrs/msr argument orders during refactors.

Related errors


AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21). Data as JSON: /api/errors/746db49f15f41c31. Report an issue: GitHub.