pub mod common;
use common::{Failures, Rng, entries};
use disc_v::{Decoder, Extension, Extensions, Isa, MarchError, decode};
#[track_caller]
fn check(dec: Decoder, inst: u32, expected: &str) {
assert_eq!(
dec.decode(0, inst as u64).to_string(),
expected,
"{dec:?} {inst:#010x}"
);
}
#[test]
fn free_functions_use_the_default_extensions() {
let mut rng = Rng::new(4);
for isa in [Isa::Rv32, Isa::Rv64, Isa::Rv128] {
let dec = Decoder::new(isa);
assert_eq!(dec.extensions(), Extensions::DEFAULT);
for _ in 0..100_000 {
let inst = rng.next_u32() as u64;
assert_eq!(dec.decode(0x1000, inst), decode(isa, 0x1000, inst));
}
}
}
#[test]
fn presets() {
let czero = 0x0ec5d533; check(Decoder::RV64GC, czero, ".insn 4, 0x0ec5d533");
check(Decoder::RVA23U64, czero, "czero.eqz a0,a1,a2");
check(Decoder::RV64GC, 0x02b50553, "fadd.d fa0,fa0,fa1,rne");
check(Decoder::RV32GC, 0x00053503, ".insn 4, 0x00053503");
check(Decoder::RVA23U64, 0x00051507, "flh fa0,0(a0)");
check(Decoder::RVA23U64, 0x04b57553, ".insn 4, 0x04b57553"); check(Decoder::RVA23U64, 0x0000100f, ".insn 4, 0x100f"); check(Decoder::RVA23S64, 0x0000100f, "fence.i");
check(Decoder::RVA23U64, 0x22b50073, ".insn 4, 0x22b50073"); check(Decoder::RVA23S64, 0x22b50073, "hfence.vvma a0,a1");
}
#[test]
fn aliases_need_their_extensions() {
let lpad = 0x00012017; check(Decoder::RVA23U64, lpad, "auipc zero,0x12");
check(
Decoder::RVA23U64.with(Extension::Zicfilp).unwrap(),
lpad,
"lpad 0x12",
);
let pause = 0x0100000f; check(Decoder::RV64GC, pause, "fence w,unknown");
check(Decoder::RVA23U64, pause, "pause");
let prefetch = 0x02156013; check(Decoder::RV64GC, prefetch, "ori zero,a0,33");
check(Decoder::RVA23U64, prefetch, "prefetch.r 32(a0)");
let ntl = 0x00200033; check(Decoder::RV64GC, ntl, "add zero,zero,sp");
check(Decoder::RVA23U64, ntl, "ntl.p1");
let sspush = 0xce104073; check(Decoder::RVA23U64, sspush, "mop.rr.7 zero,zero,ra");
check(
Decoder::RVA23U64.with(Extension::Zicfiss).unwrap(),
sspush,
"sspush ra",
);
}
#[test]
fn with_without_and_implications() {
let dec = Decoder::RV64GC.without(Extension::F);
assert!(!dec.extensions().contains(Extension::D));
check(dec, 0x00053507, ".insn 4, 0x00053507"); check(dec, 0x2108, ".insn 2, 0x2108");
let dec = Decoder::RV64GC.with(Extension::Zfh).unwrap();
assert!(dec.extensions().contains(Extension::Zfhmin));
check(dec, 0x04b57553, "fadd.h fa0,fa0,fa1");
assert_eq!(
Decoder::RV64GC.try_with(Extension::Zba),
Decoder::RV64GC.with(Extension::Zba).unwrap()
);
}
#[test]
fn shared_instructions() {
let zbkb = Decoder::with_only(Isa::Rv32, [Extension::Zbkb]).unwrap();
check(zbkb, 0x60c59533, "rol a0,a1,a2");
check(zbkb, 0x0805c533, "zext.h a0,a1");
check(zbkb, 0x60059513, ".insn 4, 0x60059513"); let zbb = Decoder::with_only(Isa::Rv32, [Extension::Zbb]).unwrap();
check(zbb, 0x0805c533, "zext.h a0,a1");
check(zbb, 0x08c5c533, ".insn 4, 0x08c5c533"); }
#[test]
fn conflicts() {
use Extension::*;
let push = 0xb862;
let gc = Decoder::RV32GC;
assert!(gc.extensions().contains(Zcd));
let conflict = gc.with(Zcmp).unwrap_err();
assert_eq!(
(conflict.extension(), conflict.conflicts_with()),
(Zcmp, Zcd)
);
assert_eq!(gc.try_with(Zcmp), gc);
assert_eq!(gc.try_with(Zcmt), gc);
check(gc, push, "fsd fs8,48(sp)");
let dec = gc.without(Zcd).with(Zcmp).unwrap();
assert!(dec.extensions().contains(D) && dec.extensions().contains(Zca));
assert!(!dec.extensions().contains(C));
check(dec, push, "cm.push {ra,s0-s1},-16");
check(dec, 0x2108, ".insn 2, 0x2108"); check(dec, 0x4505, "li a0,1");
assert!(!Extensions::DEFAULT.contains(Zcmp));
check(Decoder::new(Isa::Rv32), push, "fsd fs8,48(sp)");
assert!(Decoder::with_only(Isa::Rv32, [C, D, Zcmp]).is_err());
}
#[test]
fn push_pop_and_table_jumps() {
let rv32 = Decoder::from_march("rv32i_zca_zcmp_zcmt").unwrap();
let rv64 = Decoder::from_march("rv64i_zca_zcmp_zcmt").unwrap();
check(rv32, 0xb862, "cm.push {ra,s0-s1},-16");
check(rv64, 0xb862, "cm.push {ra,s0-s1},-32");
check(rv32, 0xba56, "cm.pop {ra,s0},32");
check(rv32, 0xbe4e, "cm.popret {ra},64");
check(rv32, 0xbc6a, "cm.popretz {ra,s0-s1},48");
check(rv64, 0xbc6a, "cm.popretz {ra,s0-s1},64");
check(rv32, 0xb802, ".insn 2, 0xb802"); check(rv32, 0xac26, "cm.mvsa01 s0,s1");
check(rv32, 0xac62, "cm.mva01s s0,s0");
check(rv32, 0xac22, ".insn 2, 0xac22"); check(rv32, 0xa07e, "cm.jt 31");
check(rv32, 0xa082, "cm.jalt 32");
}
#[test]
fn rv32e() {
let i = Decoder::from_march("rv32imc_zcmp").unwrap();
let e = Decoder::from_march("rv32emc_zcmp").unwrap();
check(e, 0x00508793, "addi a5,ra,5");
check(i, 0x00580093, "addi ra,a6,5");
check(e, 0x00580093, ".insn 4, 0x00580093");
check(e, 0x4841, ".insn 2, 0x4841"); check(e, 0xb862, "cm.push {ra,s0-s1},-16");
check(i, 0xb872, "cm.push {ra,s0-s2},-16");
check(e, 0xb872, ".insn 2, 0xb872");
}
#[test]
fn load_and_store_pairs() {
use Extension::*;
let rv32 = Decoder::new(Isa::Rv32);
check(rv32, 0x0085b503, "ld a0,8(a1)");
check(rv32, 0x0085b583, ".insn 4, 0x0085b583"); check(rv32, 0x00c5b423, "sd a2,8(a1)");
check(rv32, 0x0005b423, "sd zero,8(a1)");
check(Decoder::RV32GC, 0x0085b503, ".insn 4, 0x0085b503"); check(Decoder::new(Isa::Rv64), 0x0085b583, "ld a1,8(a1)");
let conflict = Decoder::RV32GC.with(Zclsd).unwrap_err();
assert_eq!(conflict.conflicts_with(), Zcf);
let dec = Decoder::RV32GC.without(Zcf).with(Zclsd).unwrap();
check(Decoder::RV32GC, 0x6188, "flw fa0,0(a1)");
check(dec, 0x6188, "ld a0,0(a1)");
check(dec, 0xe188, "sd a0,0(a1)");
check(dec, 0x6522, "ld a0,8(sp)");
check(dec, 0x6002, ".insn 2, 0x6002"); check(dec, 0xe42a, "sd a0,8(sp)");
check(dec, 0xe436, ".insn 2, 0xe436"); assert_eq!(dec.decode(0, 0x6188).without_aliases().mnemonic(), "c.ld");
}
#[test]
fn floating_point_in_integer_registers() {
use Extension::*;
let conflict = Decoder::RV64GC.with(Zfinx).unwrap_err();
assert_eq!(conflict.conflicts_with(), F);
let rv64 = Decoder::from_march("rv64imac_zicsr_zifencei_zdinx_zhinx").unwrap();
assert_eq!(
rv64,
Decoder::RV64GC
.without(F)
.with(Zdinx)
.unwrap()
.with(Zhinx)
.unwrap()
);
check(rv64, 0x00b57553, "fadd.s a0,a0,a1");
check(rv64, 0x02b57553, "fadd.d a0,a0,a1");
check(rv64, 0x04b57553, "fadd.h a0,a0,a1");
check(rv64, 0xa2b52553, "feq.d a0,a0,a1");
check(rv64, 0x20b58553, "fmv.s a0,a1");
assert_eq!(
rv64.decode(0, 0x20b58553).without_aliases().to_string(),
"fsgnj.s a0,a1,a1"
);
check(rv64, 0x00302573, "frcsr a0");
check(rv64, 0x00052007, ".insn 4, 0x00052007"); check(rv64, 0xe0050553, ".insn 4, 0xe0050553"); check(rv64, 0x6188, "ld a0,0(a1)");
let rv32 = Decoder::from_march("rv32imac_zicsr_zdinx_zhinx").unwrap();
check(rv32, 0x02c50553, "fadd.d a0,a0,a2,rne");
check(rv32, 0x02b57553, ".insn 4, 0x02b57553"); check(rv32, 0xd2058553, "fcvt.d.w a0,a1");
check(rv32, 0xd20585d3, ".insn 4, 0xd20585d3"); check(rv32, 0x42258653, "fcvt.d.h a2,a1");
check(rv32, 0x00b57553, "fadd.s a0,a0,a1");
check(rv32, 0x6188, ".insn 2, 0x6188");
let min = Decoder::from_march("rv32i_zhinxmin").unwrap();
check(min, 0x4405f553, "fcvt.h.s a0,a1");
check(min, 0x04b57553, ".insn 4, 0x04b57553");
let e = Decoder::from_march("rv32ec_zfinx").unwrap();
check(e, 0x00b57553, "fadd.s a0,a0,a1");
check(e, 0x00b57853, ".insn 4, 0x00b57853");
let v = Decoder::with_only(Isa::Rv64, [V]).unwrap();
check(v, 0x42101557, "vfmv.f.s fa0,v1");
check(v.with(Zfinx).unwrap(), 0x42101557, ".insn 4, 0x42101557");
}
#[test]
fn vendor_extensions() {
use Extension::*;
check(Decoder::new(Isa::Rv64), 0x02c5950b, ".insn 4, 0x02c5950b");
let thead = Decoder::new(Isa::Rv64).with(XTheadBa).unwrap();
check(thead, 0x02c5950b, "th.addsl a0,a1,a2,1");
assert_eq!(
Decoder::from_march("rv64gc_xtheadba"),
Ok(Decoder::RV64GC.with(XTheadBa).unwrap())
);
let thead = Decoder::with_only(Isa::Rv64, Extensions::GC | Extensions::XTHEAD).unwrap();
check(thead, 0x5c85c50b, "th.lwia a0,(a1),8,2");
check(thead, 0x5805c58b, ".insn 4, 0x5805c58b"); check(thead, 0xfab6450b, "th.ldd a0,a1,(a2),1,4");
check(thead, 0x1c35a50b, "th.ext a0,a1,7,3");
check(thead, 0x0180000b, "th.sync");
let rv32 = Decoder::with_only(Isa::Rv32, Extensions::GC | Extensions::XTHEAD).unwrap();
check(rv32, 0x1285950b, "th.srri a0,a1,40");
check(rv32, 0xfab6450b, ".insn 4, 0xfab6450b");
let corev =
Decoder::with_only(Isa::Rv32, Extensions::from([M, C, Zicsr]) | Extensions::XCV).unwrap();
check(corev, 0x0105b50b, "cv.elw a0,16(a1)");
check(corev, 0x0055640b, "cv.beqimm a0,5,0x8");
check(corev, 0x0045850b, "cv.lb a0,(a1),4");
check(corev, 0x00c5a42b, "cv.sw a2,(a1),8");
check(corev, 0x00c5857b, "cv.add.h a0,a1,a2");
check(corev, 0x03f5e57b, "cv.add.sci.h a0,a1,-1");
let rv64 = Decoder::with_only(Isa::Rv64, Extensions::XCV).unwrap();
check(rv64, 0x00c5857b, ".insn 4, 0x00c5857b");
let conflict = Decoder::from_march("rv32imc_xcvmem_xtheadba").unwrap_err();
assert_eq!(conflict.to_string(), "xcvmem conflicts with xtheadba");
let dec = Decoder::new(Isa::Rv32).with(XCValu).unwrap();
assert!(dec.with(XTheadBa).is_ok());
assert!(Decoder::with_only(Isa::Rv64, [Zfinx, XTheadFMemIdx]).is_err());
}
#[test]
fn compressed_bundle() {
use Extension::*;
let exts = Extensions::from([C, F, D]);
assert!(exts.contains(Zca) && exts.contains(Zcf) && exts.contains(Zcd));
let no_d = exts.without(D);
assert!(no_d.contains(C) && no_d.contains(Zcf) && !no_d.contains(Zcd));
assert!(no_d.with(D).contains(Zcd));
let no_c = Extensions::from([C, F, Zcb]).without(C);
assert!(!no_c.contains(Zca) && !no_c.contains(Zcf) && !no_c.contains(Zcb));
}
#[test]
fn with_only() {
let dec = Decoder::with_only(Isa::Rv32, [Extension::M, Extension::C]).unwrap();
assert_eq!(dec.isa(), Isa::Rv32);
check(dec, 0x02b50533, "mul a0,a0,a1");
check(dec, 0x4505, "li a0,1");
check(dec, 0x00b57553, ".insn 4, 0x00b57553"); check(dec, 0x30002573, ".insn 4, 0x30002573");
let dec = Decoder::with_only(Isa::Rv64, Extensions::EMPTY).unwrap();
check(dec, 0x00a50513, "addi a0,a0,10");
check(dec, 0x4505, ".insn 2, 0x4505");
}
#[test]
fn from_march() {
let gc = Decoder::RV64GC;
assert_eq!(Decoder::from_march("rv64gc"), Ok(gc));
assert_eq!(Decoder::from_march("RV64GC"), Ok(gc));
assert_eq!(Decoder::from_march("rv64imafdc_zicsr_zifencei"), Ok(gc));
assert_eq!(
Decoder::from_march("rv64i2p1_m2p0_a_f_d_c_zicsr2p0_zifencei"),
Ok(gc)
);
assert_eq!(Decoder::from_march("rv32gc"), Ok(Decoder::RV32GC));
assert_eq!(
Decoder::from_march("rv64gcb_zicond"),
Ok(Decoder::RV64GC
.with(Extension::Zba)
.and_then(|d| d.with(Extension::Zbb))
.and_then(|d| d.with(Extension::Zbs))
.and_then(|d| d.with(Extension::Zicond))
.unwrap()),
);
assert_eq!(
Decoder::from_march("rv64gc_zicntr_zkt_sstc_zvl128b"),
Ok(gc)
);
assert_eq!(
Decoder::from_march("rv32i_zmmul_zca").map(|d| d.extensions()),
Ok(Extensions::from([Extension::M, Extension::Zca])),
);
assert_eq!(
Decoder::from_march("rv64gc_zve64d").map(|d| d.extensions()),
Ok(Extensions::GC.with(Extension::V)),
);
assert_eq!(Decoder::from_march("x86"), Err(MarchError::InvalidBase));
assert_eq!(Decoder::from_march("rv64m"), Err(MarchError::InvalidBase));
assert_eq!(
Decoder::from_march("rv32emc").map(|d| d.extensions()),
Ok(Extensions::from([Extension::E, Extension::M, Extension::C])),
);
assert_eq!(
Decoder::from_march("rv64gc_xventanacondops"),
Err(MarchError::UnsupportedExtension("xventanacondops"))
);
let mc = Ok(Extensions::from([Extension::M, Extension::C]));
for march in ["rv32imc2", "rv32imc2p0", "rv32i2p1m2c", "rv32i2p1m2p0c2p0"] {
assert_eq!(
Decoder::from_march(march).map(|d| d.extensions()),
mc,
"{march}"
);
}
assert_eq!(
Decoder::from_march("rv32i_é"),
Err(MarchError::UnsupportedExtension("é"))
);
assert_eq!(
Decoder::from_march("rv32imé"),
Err(MarchError::UnsupportedExtension("é"))
);
assert_eq!(Decoder::from_march("rv32é"), Err(MarchError::InvalidBase));
assert_eq!(
Decoder::from_march("rv32gc_zcmp").map_err(|e| e.to_string()),
Err("zcmp conflicts with zcd".to_string()),
);
assert_eq!(
Decoder::from_march("rv32imac_zcmp").map(|d| d.extensions()),
Ok(Extensions::from([
Extension::M,
Extension::A,
Extension::C,
Extension::Zcmp
])),
);
assert_eq!(
Decoder::from_march("rv32imaf_zce").map(|d| d.extensions()),
Ok(Extensions::from([
Extension::M,
Extension::A,
Extension::F,
Extension::Zca,
Extension::Zcb,
Extension::Zcmp,
Extension::Zcmt
])),
);
assert_eq!(
Decoder::from_march("rv64gc_zkn").map(|d| d.extensions()),
Ok(Extensions::GC
| Extensions::from([
Extension::Zbkb,
Extension::Zbkc,
Extension::Zbkx,
Extension::Zkne,
Extension::Zknd,
Extension::Zknh,
])),
);
assert_eq!(
Decoder::from_march("rv64gcp"),
Err(MarchError::UnsupportedExtension("p"))
);
}
#[test]
fn from_march_never_panics() {
let pieces = [
"rv32", "rv64", "rv128", "i", "e", "g", "m", "c", "_", "z", "s", "x", "zba", "2p1", "p",
"0", "9", "é", "€", "𝔵", "", "zve", "zvl", "sstc",
];
let mut rng = Rng::new(7);
for _ in 0..100_000 {
let n = rng.next_u32() % 8;
let march: String = (0..n)
.map(|_| pieces[rng.next_u32() as usize % pieces.len()])
.collect();
let _ = Decoder::from_march(&march);
}
}
#[test]
fn disassemble_uses_the_decoder() {
let code = [0x33, 0xd5, 0xc5, 0x0e, 0x05, 0x45]; let text: Vec<String> = Decoder::RV64GC
.disassemble(0, &code)
.map(|i| i.to_string())
.collect();
assert_eq!(text, [".insn 4, 0x0ec5d533", "li a0,1"]);
}
#[test]
fn instructions_need_their_extensions() {
let entries = entries();
let mut rng = Rng::new(5);
let mut failures = Failures::default();
for isa in [Isa::Rv32, Isa::Rv64] {
for e in entries
.iter()
.filter(|e| e.applies_to(isa) && e.base.is_none())
{
for group in &e.extensions {
let dec = group.iter().fold(Decoder::new(isa), |dec, name| {
let ext =
Extension::from_name(name).unwrap_or_else(|| panic!("unknown {name}"));
dec.without(ext)
});
for inst in e.samples(&mut rng, 16) {
let ins = dec.decode(0, inst as u64).without_aliases();
if !ins.is_illegal() && ins.mnemonic() == e.name {
failures.add(
format_args!("{isa:?} {} decoded without {}", e.name, group.join("|")),
format_args!("{inst:#010x}"),
);
}
}
}
}
}
failures.assert_none();
}
#[test]
fn riscv_opcodes_covers_every_extension() {
use Extension::*;
let aliases_only = [
C,
E,
Zilsd,
Zclsd,
Zfinx,
Zdinx,
Zhinx,
Zhinxmin,
Zicbop,
Zihintntl,
Zihintpause,
Zicfilp,
Zvknhb,
];
let named: Vec<&str> = entries()
.iter()
.filter(|e| e.base.is_none())
.flat_map(|e| e.extensions.concat())
.collect();
for ext in Extensions::DEFAULT
.iter()
.chain([Zcmp, Zcmt, Zclsd, E, Zfinx, Zdinx, Zhinx, Zhinxmin])
{
assert_eq!(
named.contains(&ext.name()),
!aliases_only.contains(&ext),
"riscv-opcodes instructions for {ext}"
);
}
}