use crate::extensions::{
STANDARD_EXTENSIONS, S_CATEGORY_NAMES, S_EXTENSIONS, Z_CATEGORY_NAMES, Z_EXTENSIONS,
};
#[derive(Debug, Clone)]
pub struct ExtensionInfo {
pub name: String,
pub description: String,
pub category: String,
pub supported: bool,
}
#[must_use]
pub fn strip_rv_prefix(base: &str) -> &str {
base.strip_prefix("rv64")
.or_else(|| base.strip_prefix("rv32"))
.unwrap_or(base)
}
fn isa_has_extension(isa: &str, pattern: &str) -> bool {
isa.split('_').any(|part| part == pattern)
}
#[must_use]
pub fn parse_extensions_compact(isa: &str) -> String {
let isa = isa.to_lowercase();
let mut exts = Vec::new();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let has_g = ext_part.contains('g');
let standard = [
('i', "I", false), ('e', "E", false), ('m', "M", true),
('a', "A", true),
('f', "F", true),
('d', "D", true),
('q', "Q", false),
('c', "C", false),
('b', "B", false),
('v', "V", false),
('h', "H", false),
];
for (ch, name, implied_by_g) in standard {
if ext_part.contains(ch) || (has_g && implied_by_g) {
exts.push(name);
}
}
if has_g && !exts.contains(&"I") && !exts.contains(&"E") {
exts.insert(0, "I");
}
exts.join(" ")
}
#[must_use]
pub fn parse_z_extensions(isa: &str) -> String {
let isa = isa.to_lowercase();
let mut z_exts = Vec::new();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let has_g = ext_part.contains('g');
if has_g {
z_exts.push("zicsr".to_string());
z_exts.push("zifencei".to_string());
}
for part in isa.split('_') {
if part.starts_with('z') && !z_exts.contains(&part.to_string()) {
z_exts.push(part.to_string());
}
}
z_exts.join(" ")
}
#[must_use]
pub fn parse_s_extensions(isa: &str) -> String {
let isa = isa.to_lowercase();
let mut s_exts = Vec::new();
for part in isa.split('_') {
if part.starts_with('s') && !s_exts.contains(&part.to_string()) {
s_exts.push(part.to_string());
}
}
s_exts.join(" ")
}
#[must_use]
pub fn parse_extensions_explained(isa: &str) -> Vec<(String, String)> {
let isa = isa.to_lowercase();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let mut exts = Vec::new();
for &(ch, name, desc) in STANDARD_EXTENSIONS {
if ext_part.contains(ch) {
exts.push((name.to_string(), desc.to_string()));
}
}
exts
}
#[must_use]
pub fn parse_z_extensions_explained(isa: &str) -> Vec<(String, String)> {
let isa = isa.to_lowercase();
let mut z_exts = Vec::new();
for &(pattern, name, desc, _category) in Z_EXTENSIONS {
if isa_has_extension(&isa, pattern) {
z_exts.push((name.to_string(), desc.to_string()));
}
}
z_exts
}
#[must_use]
pub fn parse_s_extensions_explained(isa: &str) -> Vec<(String, String)> {
let isa = isa.to_lowercase();
let mut s_exts = Vec::new();
for &(pattern, name, desc, _category) in S_EXTENSIONS {
if isa_has_extension(&isa, pattern) {
s_exts.push((name.to_string(), desc.to_string()));
}
}
s_exts
}
#[must_use]
pub fn parse_z_extensions_with_category(isa: &str) -> Vec<ExtensionInfo> {
let isa = isa.to_lowercase();
let mut z_exts = Vec::new();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let has_g = ext_part.contains('g');
if has_g {
z_exts.push(ExtensionInfo {
name: "Zicsr".to_string(),
description: "CSR Instructions".to_string(),
category: "base".to_string(),
supported: true,
});
z_exts.push(ExtensionInfo {
name: "Zifencei".to_string(),
description: "Instruction-Fetch Fence".to_string(),
category: "base".to_string(),
supported: true,
});
}
for &(pattern, name, desc, category) in Z_EXTENSIONS {
if isa_has_extension(&isa, pattern) {
if !z_exts.iter().any(|e| e.name.eq_ignore_ascii_case(name)) {
z_exts.push(ExtensionInfo {
name: name.to_string(),
description: desc.to_string(),
category: category.to_string(),
supported: true,
});
}
}
}
z_exts
}
#[must_use]
pub fn parse_s_extensions_with_category(isa: &str) -> Vec<ExtensionInfo> {
let isa = isa.to_lowercase();
let mut s_exts = Vec::new();
for &(pattern, name, desc, category) in S_EXTENSIONS {
if isa_has_extension(&isa, pattern) {
s_exts.push(ExtensionInfo {
name: name.to_string(),
description: desc.to_string(),
category: category.to_string(),
supported: true,
});
}
}
s_exts
}
#[must_use]
pub fn get_z_category_name(category: &str) -> &'static str {
Z_CATEGORY_NAMES
.iter()
.find(|(id, _)| *id == category)
.map_or("Other", |(_, name)| *name)
}
#[must_use]
pub fn get_s_category_name(category: &str) -> &'static str {
S_CATEGORY_NAMES
.iter()
.find(|(id, _)| *id == category)
.map_or("Other", |(_, name)| *name)
}
#[must_use]
pub fn group_by_category(extensions: &[ExtensionInfo]) -> Vec<(String, Vec<&ExtensionInfo>)> {
use std::collections::BTreeMap;
let mut groups: BTreeMap<String, Vec<&ExtensionInfo>> = BTreeMap::new();
for ext in extensions {
groups.entry(ext.category.clone()).or_default().push(ext);
}
groups.into_iter().collect()
}
#[must_use]
pub fn get_all_z_extensions_with_status(isa: &str) -> Vec<ExtensionInfo> {
let isa = isa.to_lowercase();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let has_g = ext_part.contains('g');
Z_EXTENSIONS
.iter()
.map(|&(pattern, name, desc, category)| {
let supported = isa_has_extension(&isa, pattern)
|| (has_g && (pattern == "zicsr" || pattern == "zifencei"));
ExtensionInfo {
name: name.to_string(),
description: desc.to_string(),
category: category.to_string(),
supported,
}
})
.collect()
}
#[must_use]
pub fn get_all_s_extensions_with_status(isa: &str) -> Vec<ExtensionInfo> {
let isa = isa.to_lowercase();
S_EXTENSIONS
.iter()
.map(|&(pattern, name, desc, category)| {
let supported = isa_has_extension(&isa, pattern);
ExtensionInfo {
name: name.to_string(),
description: desc.to_string(),
category: category.to_string(),
supported,
}
})
.collect()
}
#[must_use]
pub fn get_all_standard_extensions_with_status(isa: &str) -> Vec<(String, String, bool)> {
let isa = isa.to_lowercase();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let has_g = ext_part.contains('g');
STANDARD_EXTENSIONS
.iter()
.map(|&(char, name, desc)| {
let supported =
ext_part.contains(char) || (has_g && matches!(char, 'i' | 'm' | 'a' | 'f' | 'd'));
(name.to_string(), desc.to_string(), supported)
})
.collect()
}
#[must_use]
pub fn parse_vector_from_isa(isa: &str) -> Option<String> {
let isa = isa.to_lowercase();
let base = isa.split('_').next().unwrap_or(&isa);
let ext_part = strip_rv_prefix(base);
let has_zve = isa.split('_').any(|part| part.starts_with("zve"));
if !ext_part.contains('v') && !has_zve {
return None;
}
let mut details = vec!["Enabled".to_string()];
if isa_has_extension(&isa, "zvl65536b") {
details.push("VLEN>=65536".to_string());
} else if isa_has_extension(&isa, "zvl32768b") {
details.push("VLEN>=32768".to_string());
} else if isa_has_extension(&isa, "zvl16384b") {
details.push("VLEN>=16384".to_string());
} else if isa_has_extension(&isa, "zvl8192b") {
details.push("VLEN>=8192".to_string());
} else if isa_has_extension(&isa, "zvl4096b") {
details.push("VLEN>=4096".to_string());
} else if isa_has_extension(&isa, "zvl2048b") {
details.push("VLEN>=2048".to_string());
} else if isa_has_extension(&isa, "zvl1024b") {
details.push("VLEN>=1024".to_string());
} else if isa_has_extension(&isa, "zvl512b") {
details.push("VLEN>=512".to_string());
} else if isa_has_extension(&isa, "zvl256b") {
details.push("VLEN>=256".to_string());
} else if isa_has_extension(&isa, "zvl128b") {
details.push("VLEN>=128".to_string());
} else if isa_has_extension(&isa, "zvl64b") {
details.push("VLEN>=64".to_string());
} else if isa_has_extension(&isa, "zvl32b") {
details.push("VLEN>=32".to_string());
}
Some(details.join(", "))
}
#[cfg(test)]
mod tests {
use super::*;
const ISA_VISIONFIVE2: &str = "rv64imafdc_zicntr_zicsr_zifencei_zihpm_zba_zbb";
const ISA_SPACEMIT_K1: &str = "rv64imafdcv_zicbom_zicboz_zicntr_zicsr_zifencei_zihintpause_zihpm_zba_zbb_zbc_zbs_zkt_zvkt_zvl128b_zvl256b_zvl32b_zvl64b";
const ISA_MINIMAL: &str = "rv64imac";
const ISA_RV32: &str = "rv32imc";
#[test]
fn test_visionfive2() {
assert_eq!(parse_extensions_compact(ISA_VISIONFIVE2), "I M A F D C");
}
#[test]
fn test_spacemit() {
assert_eq!(parse_extensions_compact(ISA_SPACEMIT_K1), "I M A F D C V");
}
#[test]
fn test_minimal() {
assert_eq!(parse_extensions_compact(ISA_MINIMAL), "I M A C");
}
#[test]
fn test_rv32() {
assert_eq!(parse_extensions_compact(ISA_RV32), "I M C");
}
#[test]
fn test_unknown() {
assert_eq!(parse_extensions_compact("unknown"), "");
}
#[test]
fn test_case_insensitive() {
assert_eq!(
parse_extensions_compact("RV64IMAFDC"),
parse_extensions_compact("rv64imafdc")
);
}
#[test]
fn test_empty() {
assert_eq!(parse_extensions_compact(""), "");
}
#[test]
fn spec_g_expansion() {
assert_eq!(parse_extensions_compact("rv64gc"), "I M A F D C");
}
#[test]
fn spec_g_expansion_uppercase() {
assert_eq!(parse_extensions_compact("RV64GC"), "I M A F D C");
}
#[test]
fn spec_e_extension() {
assert_eq!(parse_extensions_compact("rv32e"), "E");
}
#[test]
fn spec_e_with_c() {
assert_eq!(parse_extensions_compact("rv32ec"), "E C");
}
#[test]
fn spec_with_vector() {
assert_eq!(parse_extensions_compact("rv64imafdcv"), "I M A F D C V");
}
#[test]
fn spec_rv64_prefix_not_vector() {
let result = parse_extensions_compact("rv64imafdc");
assert!(!result.contains('V'));
}
#[test]
fn spec_z_extensions_ignored() {
assert_eq!(
parse_extensions_compact("rv64imafdc_zba_zbb"),
"I M A F D C"
);
}
#[test]
fn spec_rv64_only() {
assert_eq!(parse_extensions_compact("rv64"), "");
}
#[test]
fn test_z_extensions_visionfive2() {
let result = parse_z_extensions(ISA_VISIONFIVE2);
assert!(result.contains("zicntr"));
assert!(result.contains("zicsr"));
assert!(result.contains("zifencei"));
assert!(result.contains("zba"));
assert!(result.contains("zbb"));
}
#[test]
fn test_z_extensions_spacemit() {
let result = parse_z_extensions(ISA_SPACEMIT_K1);
assert!(result.contains("zicbom"));
assert!(result.contains("zicboz"));
assert!(result.contains("zbc"));
assert!(result.contains("zbs"));
assert!(result.contains("zvl256b"));
}
#[test]
fn test_z_extensions_minimal() {
assert!(parse_z_extensions(ISA_MINIMAL).is_empty());
}
#[test]
fn spec_z_extensions_basic() {
assert_eq!(parse_z_extensions("rv64i_zicsr_zifencei"), "zicsr zifencei");
}
#[test]
fn spec_z_extensions_order() {
assert_eq!(parse_z_extensions("rv64i_zba_zbb_zbc"), "zba zbb zbc");
}
#[test]
fn spec_z_extensions_none() {
assert_eq!(parse_z_extensions("rv64imafdc"), "");
}
#[test]
fn spec_z_extensions_g_implies() {
assert_eq!(parse_z_extensions("rv64gc"), "zicsr zifencei");
}
#[test]
fn spec_z_extensions_case() {
assert_eq!(parse_z_extensions("rv64i_Zicsr"), "zicsr");
}
#[test]
fn spec_s_extensions() {
let result = parse_s_extensions("rv64i_sstc");
assert!(result.contains("sstc"));
}
#[test]
fn test_explained_visionfive2() {
let result = parse_extensions_explained(ISA_VISIONFIVE2);
assert_eq!(result.len(), 6); assert!(result.iter().any(|(n, _)| n == "I"));
assert!(result.iter().any(|(n, _)| n == "M"));
assert!(result.iter().any(|(n, _)| n == "F"));
assert!(result.iter().any(|(n, _)| n == "D"));
assert!(result.iter().any(|(n, _)| n == "C"));
}
#[test]
fn test_z_explained_spacemit() {
let result = parse_z_extensions_explained(ISA_SPACEMIT_K1);
assert!(result
.iter()
.any(|(n, d)| n == "Zba" && d == "Address Generation"));
assert!(result
.iter()
.any(|(n, d)| n == "Zbb" && d == "Basic Bit Manipulation"));
assert!(result
.iter()
.any(|(n, d)| n == "Zbc" && d == "Carry-less Multiply"));
}
#[test]
fn test_vector_no_vector() {
assert!(parse_vector_from_isa(ISA_VISIONFIVE2).is_none());
}
#[test]
fn test_vector_with_v() {
let result = parse_vector_from_isa(ISA_SPACEMIT_K1);
assert!(result.is_some());
let detail = result.unwrap();
assert!(detail.contains("Enabled"));
assert!(detail.contains("VLEN>=256"));
}
#[test]
fn test_vector_zve_only() {
let result = parse_vector_from_isa("rv64imac_zve32x");
assert!(result.is_some());
assert!(result.unwrap().contains("Enabled"));
}
#[test]
fn spec_vector_with_v() {
let result = parse_vector_from_isa("rv64imafdcv");
assert!(result.is_some());
assert!(result.unwrap().contains("Enabled"));
}
#[test]
fn spec_vector_none() {
assert!(parse_vector_from_isa("rv64imafdc").is_none());
}
#[test]
fn spec_vector_vlen_256() {
let result = parse_vector_from_isa("rv64imafdcv_zvl256b");
assert!(result.is_some());
assert!(result.unwrap().contains("VLEN>=256"));
}
#[test]
fn spec_vector_vlen_largest() {
let result = parse_vector_from_isa("rv64imafdcv_zvl128b_zvl256b");
assert!(result.is_some());
assert!(result.unwrap().contains("VLEN>=256"));
}
#[test]
fn test_zk_does_not_false_match_zkn() {
let isa = "rv64i_zkn";
let result = parse_z_extensions_explained(isa);
assert!(
result.iter().any(|(n, _)| n == "Zkn"),
"Zkn should be found"
);
assert!(
!result.iter().any(|(n, _)| n == "Zk"),
"Zk should NOT be found (false positive)"
);
}
#[test]
fn test_zks_does_not_false_match_zksed() {
let isa = "rv64i_zksed";
let result = parse_z_extensions_explained(isa);
assert!(
result.iter().any(|(n, _)| n == "Zksed"),
"Zksed should be found"
);
assert!(
!result.iter().any(|(n, _)| n == "Zks"),
"Zks should NOT be found (false positive)"
);
assert!(
!result.iter().any(|(n, _)| n == "Zk"),
"Zk should NOT be found (false positive)"
);
}
#[test]
fn test_s_extension_no_collision_with_z() {
let isa = "rv64i_zbs_zks";
let result = parse_s_extensions_explained(isa);
assert!(
result.is_empty(),
"No S-extensions should be found in Z-only ISA: {:?}",
result
);
}
#[test]
fn test_s_extension_exact_match() {
let isa = "rv64i_sstc_svnapot";
let result = parse_s_extensions_explained(isa);
assert!(
result.iter().any(|(n, _)| n == "Sstc"),
"Sstc should be found"
);
assert!(
result.iter().any(|(n, _)| n == "Svnapot"),
"Svnapot should be found"
);
}
#[test]
fn test_zvks_does_not_false_match_zvksc() {
let isa = "rv64iv_zvksc";
let result = parse_z_extensions_explained(isa);
assert!(
result.iter().any(|(n, _)| n == "Zvksc"),
"Zvksc should be found"
);
assert!(
!result.iter().any(|(n, _)| n == "Zvks"),
"Zvks should NOT be found (false positive)"
);
}
#[test]
fn test_zicsr_not_false_positive_for_c() {
let isa = "rv64ima_zicsr";
let result = parse_extensions_compact(isa);
assert_eq!(result, "I M A", "C should not appear from zicsr");
}
#[test]
fn spec_vector_no_default_vlen() {
let result = parse_vector_from_isa("rv64imafdcv");
assert!(result.is_some());
let detail = result.unwrap();
assert!(detail.contains("Enabled"));
assert!(!detail.contains("VLEN"));
}
}