#![expect(clippy::identity_op, reason = "Test readability")]
use crate::instructions::Instruction;
use crate::instructions::rv64::zce::zcmp::{Rv64ZcmpInstruction, ZcmpUrlist};
use crate::registers::general_purpose::{EReg, Reg};
const fn make_push_pop(op_sel: u16, urlist: u16, spimm: u16) -> u32 {
let inst: u16 =
(0b101 << 13) | (0b11 << 11) | (op_sel << 9) | (urlist << 4) | (spimm << 2) | 0b10;
u32::from(inst)
}
const OP_PUSH: u16 = 0b00;
const OP_POP: u16 = 0b01;
const OP_POPRETZ: u16 = 0b10;
const OP_POPRET: u16 = 0b11;
const fn make_mv_pair(funct2: u16, r1s: u16, r2s: u16) -> u32 {
let inst: u16 = (0b101 << 13) | (0b011 << 10) | (r1s << 7) | (funct2 << 5) | (r2s << 2) | 0b10;
u32::from(inst)
}
const MV_FUNCT2_MVA01S: u16 = 0b11;
const MV_FUNCT2_MVSA01: u16 = 0b01;
fn expected_stack_adj(urlist_raw: u8, spimm: u32) -> u32 {
ZcmpUrlist::<Reg<u64>>::try_from_raw(urlist_raw)
.unwrap()
.stack_adj_base()
+ spimm * 16
}
#[test]
fn test_cm_push_ra_only() {
let inst = make_push_pop(OP_PUSH, 4, 0);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(4).unwrap(),
stack_adj: 16,
}
);
}
#[test]
fn test_cm_push_ra_s0_s11() {
let inst = make_push_pop(OP_PUSH, 15, 3);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(15).unwrap(),
stack_adj: 160,
}
);
}
#[test]
fn test_cm_push_all_valid_urlists() {
for urlist in 4u16..=15 {
let inst = make_push_pop(OP_PUSH, urlist, 0);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(urlist as u8).unwrap(),
stack_adj: expected_stack_adj(urlist as u8, 0),
},
"urlist={urlist}"
);
}
}
#[test]
fn test_cm_push_reserved_urlist() {
for urlist in 0u16..4 {
let inst = make_push_pop(OP_PUSH, urlist, 0);
assert!(
Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none(),
"urlist={urlist} should be reserved"
);
}
}
#[test]
fn test_cm_push_binutils_reference_encodings() {
let cases = &[
(0xb842u32, 4, 16),
(0xb84e, 4, 64),
(0xb882, 8, 48),
(0xb88e, 8, 96),
(0xb8f2, 15, 112),
(0xb8fe, 15, 160),
];
for &(raw, urlist_raw, stack_adj) in cases {
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(raw).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPush {
urlist: ZcmpUrlist::try_from_raw(urlist_raw).unwrap(),
stack_adj,
},
"raw={raw:#06x}"
);
}
}
#[test]
fn test_cm_pop_basic() {
let inst = make_push_pop(OP_POP, 5, 1);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPop {
urlist: ZcmpUrlist::try_from_raw(5).unwrap(),
stack_adj: 32,
}
);
}
#[test]
fn test_cm_pop_ra_s0_s9() {
let inst = make_push_pop(OP_POP, 14, 2);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPop {
urlist: ZcmpUrlist::try_from_raw(14).unwrap(),
stack_adj: 128,
}
);
}
#[test]
fn test_cm_pop_binutils_reference_encodings() {
let cases = &[(0xba56u32, 5, 32), (0xbaea, 14, 128)];
for &(raw, urlist_raw, stack_adj) in cases {
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(raw).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPop {
urlist: ZcmpUrlist::try_from_raw(urlist_raw).unwrap(),
stack_adj,
},
"raw={raw:#06x}"
);
}
}
#[test]
fn test_cm_popretz_basic() {
let inst = make_push_pop(OP_POPRETZ, 4, 0);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPopretz {
urlist: ZcmpUrlist::try_from_raw(4).unwrap(),
stack_adj: 16,
}
);
}
#[test]
fn test_cm_popretz_binutils_reference_encodings() {
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(0xbc42).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPopretz {
urlist: ZcmpUrlist::try_from_raw(4).unwrap(),
stack_adj: 16,
}
);
}
#[test]
fn test_cm_popret_basic() {
let inst = make_push_pop(OP_POPRET, 6, 0);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPopret {
urlist: ZcmpUrlist::try_from_raw(6).unwrap(),
stack_adj: 32,
}
);
}
#[test]
fn test_cm_popret_all_spimm_values() {
let expected_adjs = [48, 64, 80, 96];
for (spimm, &expected) in expected_adjs.iter().enumerate() {
let inst = make_push_pop(OP_POPRET, 8, spimm as u16);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPopret {
urlist: ZcmpUrlist::try_from_raw(8).unwrap(),
stack_adj: expected,
},
"spimm={spimm}"
);
}
}
#[test]
fn test_cm_popret_binutils_reference_encodings() {
let cases = &[
(0xbe42u32, 4, 16),
(0xbe62, 6, 32),
(0xbe66, 6, 48),
(0xbe82, 8, 48),
(0xbe8e, 8, 96),
];
for &(raw, urlist_raw, stack_adj) in cases {
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(raw).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmPopret {
urlist: ZcmpUrlist::try_from_raw(urlist_raw).unwrap(),
stack_adj,
},
"raw={raw:#06x}"
);
}
}
#[test]
fn test_push_pop_op_sel_distinct_variants() {
let urlist = 4u16;
let spimm = 0u16;
assert!(matches!(
Rv64ZcmpInstruction::<Reg<u64>>::try_decode(make_push_pop(OP_PUSH, urlist, spimm)).unwrap(),
Rv64ZcmpInstruction::CmPush { .. }
));
assert!(matches!(
Rv64ZcmpInstruction::<Reg<u64>>::try_decode(make_push_pop(OP_POP, urlist, spimm)).unwrap(),
Rv64ZcmpInstruction::CmPop { .. }
));
assert!(matches!(
Rv64ZcmpInstruction::<Reg<u64>>::try_decode(make_push_pop(OP_POPRETZ, urlist, spimm))
.unwrap(),
Rv64ZcmpInstruction::CmPopretz { .. }
));
assert!(matches!(
Rv64ZcmpInstruction::<Reg<u64>>::try_decode(make_push_pop(OP_POPRET, urlist, spimm))
.unwrap(),
Rv64ZcmpInstruction::CmPopret { .. }
));
}
#[test]
fn test_cm_mva01s_s0_s1() {
let inst = make_mv_pair(MV_FUNCT2_MVA01S, 0, 1);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmMva01s {
r1s: Reg::S0,
r2s: Reg::S1,
}
);
}
#[test]
fn test_cm_mva01s_same_reg() {
let inst = make_mv_pair(MV_FUNCT2_MVA01S, 2, 2);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmMva01s {
r1s: Reg::S2,
r2s: Reg::S2,
}
);
}
#[test]
fn test_cm_mva01s_all_s_regs() {
let expected_regs = [
Reg::S0,
Reg::S1,
Reg::S2,
Reg::S3,
Reg::S4,
Reg::S5,
Reg::S6,
Reg::S7,
];
for (field, &expected) in expected_regs.iter().enumerate() {
let inst = make_mv_pair(MV_FUNCT2_MVA01S, field as u16, 0);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
if let Rv64ZcmpInstruction::CmMva01s { r1s, .. } = decoded {
assert_eq!(r1s, expected, "field={field}");
} else {
panic!("wrong variant for field={field}");
}
}
}
#[test]
fn test_cm_mva01s_binutils_reference_encodings() {
let cases: &[(u32, Reg<u64>, Reg<u64>)] = &[
(0xac7e, Reg::S0, Reg::S7),
(0xac7a, Reg::S0, Reg::S6),
(0xacfe, Reg::S1, Reg::S7),
(0xacfa, Reg::S1, Reg::S6),
(0xafee, Reg::S7, Reg::S3),
(0xade2, Reg::S3, Reg::S0),
(0xaef2, Reg::S5, Reg::S4),
(0xaefa, Reg::S5, Reg::S6),
];
for &(raw, r1s, r2s) in cases {
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(raw).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmMva01s { r1s, r2s },
"raw={raw:#06x}"
);
}
}
#[test]
fn test_cm_mvsa01_distinct_regs() {
let inst = make_mv_pair(MV_FUNCT2_MVSA01, 0, 2);
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmMvsa01 {
r1s: Reg::S0,
r2s: Reg::S2,
}
);
}
#[test]
fn test_cm_mvsa01_reserved_same_reg() {
let inst = make_mv_pair(MV_FUNCT2_MVSA01, 3, 3);
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_cm_mvsa01_binutils_reference_encodings() {
let cases: &[(u32, Reg<u64>, Reg<u64>)] = &[
(0xafa2, Reg::S7, Reg::S0),
(0xaf22, Reg::S6, Reg::S0),
(0xafa6, Reg::S7, Reg::S1),
(0xaf26, Reg::S6, Reg::S1),
(0xadbe, Reg::S3, Reg::S7),
(0xada2, Reg::S3, Reg::S0),
(0xaeb2, Reg::S5, Reg::S4),
(0xaeba, Reg::S5, Reg::S6),
];
for &(raw, r1s, r2s) in cases {
let decoded = Rv64ZcmpInstruction::<Reg<u64>>::try_decode(raw).unwrap();
assert_eq!(
decoded,
Rv64ZcmpInstruction::CmMvsa01 { r1s, r2s },
"raw={raw:#06x}"
);
}
}
#[test]
fn test_cm_mv_reserved_funct2_00() {
let inst = make_mv_pair(0b00, 0, 1);
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_cm_mv_reserved_funct2_10() {
let inst = make_mv_pair(0b10, 0, 1);
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_cm_mv_reserved_bit10_zero() {
let inst: u16 = (0b101 << 13) | (0b01 << 11) | (0b11 << 5) | 0b10;
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(u32::from(inst)).is_none());
}
#[test]
fn test_non_zcmp_q00_returns_none() {
let inst = (0b101 << 13) | 0b00;
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_non_zcmp_q01_returns_none() {
let inst = (0b101 << 13) | 0b01;
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_non_zcmp_funct3_mismatch() {
let inst = (0b100 << 13) | (0b11 << 11) | (0b00 << 9) | (4 << 4) | 0b10;
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_reserved_funct2_00_returns_none() {
let inst = (0b101 << 13) | (0b00 << 11) | 0b10;
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_reserved_funct2_10_returns_none() {
let inst = (0b101 << 13) | (0b10 << 11) | 0b10;
assert!(Rv64ZcmpInstruction::<Reg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_stack_adj_base_rv64() {
let cases = &[
(4, 16),
(5, 16),
(6, 32),
(7, 32),
(8, 48),
(9, 48),
(10, 64),
(11, 64),
(12, 80),
(13, 80),
(14, 96),
(15, 112),
];
for &(raw, expected) in cases {
let urlist = ZcmpUrlist::<Reg<u64>>::try_from_raw(raw).unwrap();
assert_eq!(urlist.stack_adj_base(), expected, "urlist={raw}");
}
}
#[test]
fn test_reg_list_ra_only() {
let urlist = ZcmpUrlist::<Reg<u64>>::try_from_raw(4).unwrap();
assert!(urlist.reg_list().eq([Reg::Ra]));
}
#[test]
fn test_reg_list_ra_s0_s11() {
let urlist = ZcmpUrlist::<Reg<u64>>::try_from_raw(15).unwrap();
assert!(urlist.reg_list().eq([
Reg::Ra,
Reg::S0,
Reg::S1,
Reg::S2,
Reg::S3,
Reg::S4,
Reg::S5,
Reg::S6,
Reg::S7,
Reg::S8,
Reg::S9,
Reg::S10,
Reg::S11,
]));
}
#[test]
fn test_reg_list_count_matches_urlist() {
let expected_counts = [1usize, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13];
for (i, &expected) in expected_counts.iter().enumerate() {
let raw = (i + 4) as u8;
let urlist = ZcmpUrlist::<Reg<u64>>::try_from_raw(raw).unwrap();
let count = urlist.reg_list().count();
assert_eq!(count, expected, "urlist={raw}");
}
}
#[test]
fn test_rve_urlist_max_is_ra_s0_s1() {
assert!(ZcmpUrlist::<EReg<u64>>::try_from_raw(6).is_some());
assert!(ZcmpUrlist::<EReg<u64>>::try_from_raw(7).is_none());
}
#[test]
fn test_rve_push_reserved_urlist() {
let inst = make_push_pop(OP_PUSH, 7, 0);
assert!(Rv64ZcmpInstruction::<EReg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_rve_mva01s_accessible_regs() {
let inst = make_mv_pair(MV_FUNCT2_MVA01S, 0, 1);
assert!(Rv64ZcmpInstruction::<EReg<u64>>::try_decode(inst).is_some());
}
#[test]
fn test_rve_mva01s_inaccessible_reg_returns_none() {
let inst = make_mv_pair(MV_FUNCT2_MVA01S, 2, 0);
assert!(Rv64ZcmpInstruction::<EReg<u64>>::try_decode(inst).is_none());
}
#[test]
fn test_rve_mvsa01_accessible_regs() {
let inst = make_mv_pair(MV_FUNCT2_MVSA01, 0, 1);
assert!(Rv64ZcmpInstruction::<EReg<u64>>::try_decode(inst).is_some());
}
#[test]
fn test_rve_mvsa01_inaccessible_reg_returns_none() {
let inst = make_mv_pair(MV_FUNCT2_MVSA01, 0, 2);
assert!(Rv64ZcmpInstruction::<EReg<u64>>::try_decode(inst).is_none());
}