use core::fmt;
use crate::{Disassembler, Extension, Extensions, Instruction, Isa, inst_length};
const CONFLICTS: &[(Extension, Extension)] = &[
(Extension::Zcmp, Extension::Zcd),
(Extension::Zcmt, Extension::Zcd),
(Extension::Zclsd, Extension::Zcf),
(Extension::Zfinx, Extension::F),
(Extension::XCVmem, Extension::XTheadBa),
(Extension::XCVmem, Extension::XTheadBb),
(Extension::XCVmem, Extension::XTheadBs),
(Extension::XCVmem, Extension::XTheadCmo),
(Extension::XCVmem, Extension::XTheadCondMov),
(Extension::XCVmem, Extension::XTheadFmv),
(Extension::XCVmem, Extension::XTheadInt),
(Extension::XCVmem, Extension::XTheadMac),
(Extension::XCVmem, Extension::XTheadMemIdx),
(Extension::XCVmem, Extension::XTheadMemPair),
(Extension::XCVmem, Extension::XTheadSync),
(Extension::XCVbi, Extension::XTheadFMemIdx),
(Extension::XCVelw, Extension::XTheadBb),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Decoder {
isa: Isa,
extensions: Extensions,
}
impl Decoder {
pub const RV32GC: Decoder = Decoder::unchecked(Isa::Rv32, Extensions::GC);
pub const RV64GC: Decoder = Decoder::unchecked(Isa::Rv64, Extensions::GC);
pub const RVA23U64: Decoder = Decoder::unchecked(Isa::Rv64, Extensions::RVA23U64);
pub const RVA23S64: Decoder = Decoder::unchecked(Isa::Rv64, Extensions::RVA23S64);
pub const fn new(isa: Isa) -> Decoder {
Decoder::unchecked(isa, Extensions::DEFAULT)
}
const fn unchecked(isa: Isa, extensions: Extensions) -> Decoder {
Decoder { isa, extensions }
}
pub fn with_only(isa: Isa, extensions: impl Into<Extensions>) -> Result<Decoder, Conflict> {
let extensions = extensions.into();
match find_conflict(extensions) {
Some(conflict) => Err(conflict),
None => Ok(Decoder { isa, extensions }),
}
}
pub fn with(self, ext: Extension) -> Result<Decoder, Conflict> {
Decoder::with_only(self.isa, self.extensions.with(ext))
}
pub fn try_with(self, ext: Extension) -> Decoder {
self.with(ext).unwrap_or(self)
}
pub fn without(self, ext: Extension) -> Decoder {
Decoder {
extensions: self.extensions.without(ext),
..self
}
}
pub fn isa(&self) -> Isa {
self.isa
}
pub fn extensions(&self) -> Extensions {
self.extensions
}
pub fn decode(&self, pc: u64, inst: u64) -> Instruction {
crate::decode::decode(self.isa, self.extensions, pc, inst)
}
pub fn decode_bytes(&self, pc: u64, bytes: &[u8]) -> Option<Instruction> {
let first = u16::from_le_bytes([*bytes.first()?, *bytes.get(1)?]);
let len = inst_length(first).unwrap_or(2);
let mut word = [0; 8];
word[..len].copy_from_slice(bytes.get(..len)?);
Some(self.decode(pc, u64::from_le_bytes(word)))
}
pub fn disassemble<'a>(&self, pc: u64, bytes: &'a [u8]) -> Disassembler<'a> {
Disassembler {
decoder: *self,
pc,
bytes,
}
}
pub fn from_march(march: &str) -> Result<Decoder, MarchError<'_>> {
let (isa, rest) = [
("rv32", Isa::Rv32),
("rv64", Isa::Rv64),
("rv128", Isa::Rv128),
]
.into_iter()
.find_map(|(prefix, isa)| {
let (head, rest) = march.split_at_checked(prefix.len())?;
head.eq_ignore_ascii_case(prefix).then_some((isa, rest))
})
.ok_or(MarchError::InvalidBase)?;
let mut extensions = Extensions::EMPTY;
for (i, token) in rest.split(['_']).enumerate() {
let first = token.chars().next().map(|c| c.to_ascii_lowercase());
match first {
Some('z' | 's' | 'x') if i > 0 => {
extensions = extensions.union(multi_letter(token)?);
}
_ => {
if i == 0 && !matches!(first, Some('i' | 'g' | 'e')) {
return Err(MarchError::InvalidBase);
}
extensions = extensions.union(single_letters(token)?);
}
}
}
Decoder::with_only(isa, extensions).map_err(MarchError::Conflict)
}
}
fn single_letters(token: &str) -> Result<Extensions, MarchError<'_>> {
use Extension::*;
let mut extensions = Extensions::EMPTY;
let mut rest = token;
while let Some(c) = rest.chars().next() {
let Some((name, after)) = rest.split_at_checked(c.len_utf8()) else {
break;
};
rest = strip_version(after);
extensions = extensions.union(match c.to_ascii_lowercase() {
'i' => Extensions::EMPTY,
'e' => E.into(),
'g' => Extensions::of(&[M, A, F, D, Zicsr, Zifencei]),
'b' => Extensions::of(&[Zba, Zbb, Zbs]),
c => match Extension::from_name(name) {
Some(ext) if c.is_ascii_alphabetic() => ext.into(),
_ => return Err(MarchError::UnsupportedExtension(name)),
},
});
}
Ok(extensions)
}
fn multi_letter(token: &str) -> Result<Extensions, MarchError<'_>> {
use Extension::*;
let name = token.strip_suffix(trailing_version(token)).unwrap_or(token);
if let Some(ext) = Extension::from_name(name) {
return Ok(ext.into());
}
let lower = |s: &str| name.eq_ignore_ascii_case(s);
let has_prefix = |p: &str| {
name.get(..p.len())
.is_some_and(|h| h.eq_ignore_ascii_case(p))
};
Ok(if lower("zmmul") {
M.into()
} else if lower("zaamo") || lower("zalrsc") {
A.into()
} else if lower("zce") {
Extensions::of(&[Zca, Zcb, Zcmp, Zcmt])
} else if has_prefix("zve") {
V.into()
} else if lower("zkn") {
Extensions::of(&[Zbkb, Zbkc, Zbkx, Zkne, Zknd, Zknh])
} else if lower("zks") {
Extensions::of(&[Zbkb, Zbkc, Zbkx, Zksed, Zksh])
} else if lower("zk") {
Extensions::of(&[Zbkb, Zbkc, Zbkx, Zkne, Zknd, Zknh])
} else if lower("sha") {
H.into()
} else if lower("smctr") {
Ssctr.into()
} else if has_prefix("zvl") || NO_INSTRUCTIONS.iter().any(|n| lower(n)) {
Extensions::EMPTY
} else if has_prefix("s") {
Extensions::EMPTY
} else {
return Err(MarchError::UnsupportedExtension(name));
})
}
const NO_INSTRUCTIONS: &[&str] = &[
"zicntr", "zihpm", "zkt", "zvkt", "zvfh", "zvfhmin", "ztso", "za64rs", "za128rs", "zama16b",
"zic64b", "ziccamoa", "ziccif", "zicclsm", "ziccrse", "supm", "zkr",
];
fn strip_version(s: &str) -> &str {
s.strip_prefix(leading_version(s)).unwrap_or(s)
}
fn leading_version(s: &str) -> &str {
fn skip_digits(s: &str) -> &str {
s.trim_start_matches(|c: char| c.is_ascii_digit())
}
let after_major = skip_digits(s);
if after_major.len() == s.len() {
return "";
}
let after = match after_major.strip_prefix(['p', 'P']) {
Some(minor) if minor.starts_with(|c: char| c.is_ascii_digit()) => skip_digits(minor),
_ => after_major,
};
s.strip_suffix(after).unwrap_or("")
}
fn trailing_version(s: &str) -> &str {
fn skip_digits(s: &str) -> &str {
s.trim_end_matches(|c: char| c.is_ascii_digit())
}
let before_minor = skip_digits(s);
if before_minor.len() == s.len() {
return "";
}
let before = match before_minor.strip_suffix(['p', 'P']) {
Some(major) if major.ends_with(|c: char| c.is_ascii_digit()) => skip_digits(major),
_ => before_minor,
};
s.strip_prefix(before).unwrap_or("")
}
fn find_conflict(extensions: Extensions) -> Option<Conflict> {
CONFLICTS
.iter()
.find(|(a, b)| extensions.contains(*a) && extensions.contains(*b))
.map(|&(extension, conflicts_with)| Conflict {
extension,
conflicts_with,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Conflict {
extension: Extension,
conflicts_with: Extension,
}
impl Conflict {
pub fn extension(&self) -> Extension {
self.extension
}
pub fn conflicts_with(&self) -> Extension {
self.conflicts_with
}
}
impl fmt::Display for Conflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} conflicts with {}",
self.extension, self.conflicts_with
)
}
}
impl core::error::Error for Conflict {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MarchError<'a> {
InvalidBase,
UnsupportedExtension(&'a str),
Conflict(Conflict),
}
impl fmt::Display for MarchError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MarchError::InvalidBase => {
f.write_str("ISA string does not start with rv32, rv64 or rv128 and i, e or g")
}
MarchError::UnsupportedExtension(name) => write!(f, "unsupported extension {name}"),
MarchError::Conflict(conflict) => conflict.fmt(f),
}
}
}
impl core::error::Error for MarchError<'_> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_suffixes() {
assert_eq!(leading_version("2p1m"), "2p1");
assert_eq!(leading_version("2m"), "2");
assert_eq!(leading_version("2pm"), "2");
assert_eq!(leading_version("mc2"), "");
assert_eq!(strip_version("2p1mc"), "mc");
assert_eq!(strip_version("mc2"), "mc2");
assert_eq!(trailing_version("zba1p0"), "1p0");
assert_eq!(trailing_version("zba1"), "1");
assert_eq!(trailing_version("zba"), "");
assert_eq!(trailing_version("zvl128b"), "");
}
#[test]
#[cfg(all(feature = "vendor-thead", feature = "vendor-corev"))]
fn vendor_conflicts_are_the_overlapping_encodings() {
use crate::opcodes::{MaskedOpcode, xcv, xthead};
let tables: [&[MaskedOpcode]; 2] = [&xthead::CUSTOM_0, &xcv::CUSTOM];
let entries = || tables.into_iter().flatten();
let vendor = |e: &MaskedOpcode| *e.op.requires.iter().find(|x| x.is_vendor()).unwrap();
let conflicts =
|a: Extension, b: Extension| CONFLICTS.contains(&(a, b)) || CONFLICTS.contains(&(b, a));
for (i, a) in entries().enumerate() {
for b in entries().skip(i + 1) {
let overlap =
(a.bits ^ b.bits) & a.mask & b.mask == 0 && a.op.isas & b.op.isas != 0;
if !overlap {
continue;
}
let (va, vb) = (vendor(a), vendor(b));
assert_ne!(va, vb, "{} and {} overlap", a.op.name, b.op.name);
assert!(conflicts(va, vb), "{} and {} overlap", a.op.name, b.op.name);
}
}
for &(a, b) in CONFLICTS.iter().filter(|(a, _)| a.is_vendor()) {
assert!(
entries().any(|x| vendor(x) == a
&& entries().any(|y| vendor(y) == b
&& (x.bits ^ y.bits) & x.mask & y.mask == 0
&& x.op.isas & y.op.isas != 0)),
"{a} and {b} do not overlap"
);
}
}
}