1use crate::extensions::{
4 STANDARD_EXTENSIONS, S_CATEGORY_NAMES, S_EXTENSIONS, Z_CATEGORY_NAMES, Z_EXTENSIONS,
5};
6
7#[derive(Debug, Clone)]
9pub struct ExtensionInfo {
10 pub name: String,
11 pub description: String,
12 pub category: String,
13 pub supported: bool,
14}
15
16#[must_use]
18pub fn strip_rv_prefix(base: &str) -> &str {
19 base.strip_prefix("rv64")
20 .or_else(|| base.strip_prefix("rv32"))
21 .unwrap_or(base)
22}
23
24fn isa_has_extension(isa: &str, pattern: &str) -> bool {
28 isa.split('_').any(|part| part == pattern)
29}
30
31#[must_use]
33pub fn parse_extensions_compact(isa: &str) -> String {
34 let isa = isa.to_lowercase();
35 let mut exts = Vec::new();
36
37 let base = isa.split('_').next().unwrap_or(&isa);
39 let ext_part = strip_rv_prefix(base);
40
41 let has_g = ext_part.contains('g');
43
44 let standard = [
47 ('i', "I", false), ('e', "E", false), ('m', "M", true),
50 ('a', "A", true),
51 ('f', "F", true),
52 ('d', "D", true),
53 ('q', "Q", false),
54 ('c', "C", false),
55 ('b', "B", false),
56 ('v', "V", false),
57 ('h', "H", false),
58 ];
59
60 for (ch, name, implied_by_g) in standard {
61 if ext_part.contains(ch) || (has_g && implied_by_g) {
62 exts.push(name);
63 }
64 }
65
66 if has_g && !exts.contains(&"I") && !exts.contains(&"E") {
68 exts.insert(0, "I");
69 }
70
71 exts.join(" ")
72}
73
74#[must_use]
76pub fn parse_z_extensions(isa: &str) -> String {
77 let isa = isa.to_lowercase();
78 let mut z_exts = Vec::new();
79
80 let base = isa.split('_').next().unwrap_or(&isa);
82 let ext_part = strip_rv_prefix(base);
83 let has_g = ext_part.contains('g');
84
85 if has_g {
87 z_exts.push("zicsr".to_string());
88 z_exts.push("zifencei".to_string());
89 }
90
91 for part in isa.split('_') {
93 if part.starts_with('z') && !z_exts.contains(&part.to_string()) {
94 z_exts.push(part.to_string());
95 }
96 }
97
98 z_exts.join(" ")
99}
100
101#[must_use]
103pub fn parse_s_extensions(isa: &str) -> String {
104 let isa = isa.to_lowercase();
105 let mut s_exts = Vec::new();
106
107 for part in isa.split('_') {
109 if part.starts_with('s') && !s_exts.contains(&part.to_string()) {
110 s_exts.push(part.to_string());
111 }
112 }
113
114 s_exts.join(" ")
115}
116
117#[must_use]
119pub fn parse_extensions_explained(isa: &str) -> Vec<(String, String)> {
120 let isa = isa.to_lowercase();
121 let base = isa.split('_').next().unwrap_or(&isa);
122 let ext_part = strip_rv_prefix(base);
123 let mut exts = Vec::new();
124
125 for &(ch, name, desc) in STANDARD_EXTENSIONS {
126 if ext_part.contains(ch) {
127 exts.push((name.to_string(), desc.to_string()));
128 }
129 }
130
131 exts
132}
133
134#[must_use]
136pub fn parse_z_extensions_explained(isa: &str) -> Vec<(String, String)> {
137 let isa = isa.to_lowercase();
138 let mut z_exts = Vec::new();
139
140 for &(pattern, name, desc, _category) in Z_EXTENSIONS {
141 if isa_has_extension(&isa, pattern) {
142 z_exts.push((name.to_string(), desc.to_string()));
143 }
144 }
145
146 z_exts
147}
148
149#[must_use]
151pub fn parse_s_extensions_explained(isa: &str) -> Vec<(String, String)> {
152 let isa = isa.to_lowercase();
153 let mut s_exts = Vec::new();
154
155 for &(pattern, name, desc, _category) in S_EXTENSIONS {
156 if isa_has_extension(&isa, pattern) {
157 s_exts.push((name.to_string(), desc.to_string()));
158 }
159 }
160
161 s_exts
162}
163
164#[must_use]
166pub fn parse_z_extensions_with_category(isa: &str) -> Vec<ExtensionInfo> {
167 let isa = isa.to_lowercase();
168 let mut z_exts = Vec::new();
169
170 let base = isa.split('_').next().unwrap_or(&isa);
172 let ext_part = strip_rv_prefix(base);
173 let has_g = ext_part.contains('g');
174
175 if has_g {
177 z_exts.push(ExtensionInfo {
178 name: "Zicsr".to_string(),
179 description: "CSR Instructions".to_string(),
180 category: "base".to_string(),
181 supported: true,
182 });
183 z_exts.push(ExtensionInfo {
184 name: "Zifencei".to_string(),
185 description: "Instruction-Fetch Fence".to_string(),
186 category: "base".to_string(),
187 supported: true,
188 });
189 }
190
191 for &(pattern, name, desc, category) in Z_EXTENSIONS {
192 if isa_has_extension(&isa, pattern) {
193 if !z_exts.iter().any(|e| e.name.eq_ignore_ascii_case(name)) {
195 z_exts.push(ExtensionInfo {
196 name: name.to_string(),
197 description: desc.to_string(),
198 category: category.to_string(),
199 supported: true,
200 });
201 }
202 }
203 }
204
205 z_exts
206}
207
208#[must_use]
210pub fn parse_s_extensions_with_category(isa: &str) -> Vec<ExtensionInfo> {
211 let isa = isa.to_lowercase();
212 let mut s_exts = Vec::new();
213
214 for &(pattern, name, desc, category) in S_EXTENSIONS {
215 if isa_has_extension(&isa, pattern) {
216 s_exts.push(ExtensionInfo {
217 name: name.to_string(),
218 description: desc.to_string(),
219 category: category.to_string(),
220 supported: true,
221 });
222 }
223 }
224
225 s_exts
226}
227
228#[must_use]
230pub fn get_z_category_name(category: &str) -> &'static str {
231 Z_CATEGORY_NAMES
232 .iter()
233 .find(|(id, _)| *id == category)
234 .map_or("Other", |(_, name)| *name)
235}
236
237#[must_use]
239pub fn get_s_category_name(category: &str) -> &'static str {
240 S_CATEGORY_NAMES
241 .iter()
242 .find(|(id, _)| *id == category)
243 .map_or("Other", |(_, name)| *name)
244}
245
246#[must_use]
248pub fn group_by_category(extensions: &[ExtensionInfo]) -> Vec<(String, Vec<&ExtensionInfo>)> {
249 use std::collections::BTreeMap;
250 let mut groups: BTreeMap<String, Vec<&ExtensionInfo>> = BTreeMap::new();
251
252 for ext in extensions {
253 groups.entry(ext.category.clone()).or_default().push(ext);
254 }
255
256 groups.into_iter().collect()
257}
258
259#[must_use]
261pub fn get_all_z_extensions_with_status(isa: &str) -> Vec<ExtensionInfo> {
262 let isa = isa.to_lowercase();
263 let base = isa.split('_').next().unwrap_or(&isa);
264 let ext_part = strip_rv_prefix(base);
265 let has_g = ext_part.contains('g');
266
267 Z_EXTENSIONS
268 .iter()
269 .map(|&(pattern, name, desc, category)| {
270 let supported = isa_has_extension(&isa, pattern)
271 || (has_g && (pattern == "zicsr" || pattern == "zifencei"));
272 ExtensionInfo {
273 name: name.to_string(),
274 description: desc.to_string(),
275 category: category.to_string(),
276 supported,
277 }
278 })
279 .collect()
280}
281
282#[must_use]
284pub fn get_all_s_extensions_with_status(isa: &str) -> Vec<ExtensionInfo> {
285 let isa = isa.to_lowercase();
286
287 S_EXTENSIONS
288 .iter()
289 .map(|&(pattern, name, desc, category)| {
290 let supported = isa_has_extension(&isa, pattern);
291 ExtensionInfo {
292 name: name.to_string(),
293 description: desc.to_string(),
294 category: category.to_string(),
295 supported,
296 }
297 })
298 .collect()
299}
300
301#[must_use]
303pub fn get_all_standard_extensions_with_status(isa: &str) -> Vec<(String, String, bool)> {
304 let isa = isa.to_lowercase();
305 let base = isa.split('_').next().unwrap_or(&isa);
306 let ext_part = strip_rv_prefix(base);
307 let has_g = ext_part.contains('g');
308
309 STANDARD_EXTENSIONS
310 .iter()
311 .map(|&(char, name, desc)| {
312 let supported =
313 ext_part.contains(char) || (has_g && matches!(char, 'i' | 'm' | 'a' | 'f' | 'd'));
314 (name.to_string(), desc.to_string(), supported)
315 })
316 .collect()
317}
318
319#[must_use]
322pub fn parse_vector_from_isa(isa: &str) -> Option<String> {
323 let isa = isa.to_lowercase();
324 let base = isa.split('_').next().unwrap_or(&isa);
325 let ext_part = strip_rv_prefix(base);
326
327 let has_zve = isa.split('_').any(|part| part.starts_with("zve"));
329 if !ext_part.contains('v') && !has_zve {
330 return None;
331 }
332
333 let mut details = vec!["Enabled".to_string()];
334
335 if isa_has_extension(&isa, "zvl65536b") {
338 details.push("VLEN>=65536".to_string());
339 } else if isa_has_extension(&isa, "zvl32768b") {
340 details.push("VLEN>=32768".to_string());
341 } else if isa_has_extension(&isa, "zvl16384b") {
342 details.push("VLEN>=16384".to_string());
343 } else if isa_has_extension(&isa, "zvl8192b") {
344 details.push("VLEN>=8192".to_string());
345 } else if isa_has_extension(&isa, "zvl4096b") {
346 details.push("VLEN>=4096".to_string());
347 } else if isa_has_extension(&isa, "zvl2048b") {
348 details.push("VLEN>=2048".to_string());
349 } else if isa_has_extension(&isa, "zvl1024b") {
350 details.push("VLEN>=1024".to_string());
351 } else if isa_has_extension(&isa, "zvl512b") {
352 details.push("VLEN>=512".to_string());
353 } else if isa_has_extension(&isa, "zvl256b") {
354 details.push("VLEN>=256".to_string());
355 } else if isa_has_extension(&isa, "zvl128b") {
356 details.push("VLEN>=128".to_string());
357 } else if isa_has_extension(&isa, "zvl64b") {
358 details.push("VLEN>=64".to_string());
359 } else if isa_has_extension(&isa, "zvl32b") {
360 details.push("VLEN>=32".to_string());
361 }
362 Some(details.join(", "))
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 const ISA_VISIONFIVE2: &str = "rv64imafdc_zicntr_zicsr_zifencei_zihpm_zba_zbb";
373 const ISA_SPACEMIT_K1: &str = "rv64imafdcv_zicbom_zicboz_zicntr_zicsr_zifencei_zihintpause_zihpm_zba_zbb_zbc_zbs_zkt_zvkt_zvl128b_zvl256b_zvl32b_zvl64b";
374 const ISA_MINIMAL: &str = "rv64imac";
375 const ISA_RV32: &str = "rv32imc";
376
377 #[test]
380 fn test_visionfive2() {
381 assert_eq!(parse_extensions_compact(ISA_VISIONFIVE2), "I M A F D C");
382 }
383
384 #[test]
385 fn test_spacemit() {
386 assert_eq!(parse_extensions_compact(ISA_SPACEMIT_K1), "I M A F D C V");
387 }
388
389 #[test]
390 fn test_minimal() {
391 assert_eq!(parse_extensions_compact(ISA_MINIMAL), "I M A C");
392 }
393
394 #[test]
395 fn test_rv32() {
396 assert_eq!(parse_extensions_compact(ISA_RV32), "I M C");
397 }
398
399 #[test]
400 fn test_unknown() {
401 assert_eq!(parse_extensions_compact("unknown"), "");
402 }
403
404 #[test]
405 fn test_case_insensitive() {
406 assert_eq!(
407 parse_extensions_compact("RV64IMAFDC"),
408 parse_extensions_compact("rv64imafdc")
409 );
410 }
411
412 #[test]
413 fn test_empty() {
414 assert_eq!(parse_extensions_compact(""), "");
415 }
416
417 #[test]
420 fn spec_g_expansion() {
421 assert_eq!(parse_extensions_compact("rv64gc"), "I M A F D C");
422 }
423
424 #[test]
425 fn spec_g_expansion_uppercase() {
426 assert_eq!(parse_extensions_compact("RV64GC"), "I M A F D C");
427 }
428
429 #[test]
430 fn spec_e_extension() {
431 assert_eq!(parse_extensions_compact("rv32e"), "E");
432 }
433
434 #[test]
435 fn spec_e_with_c() {
436 assert_eq!(parse_extensions_compact("rv32ec"), "E C");
437 }
438
439 #[test]
440 fn spec_with_vector() {
441 assert_eq!(parse_extensions_compact("rv64imafdcv"), "I M A F D C V");
442 }
443
444 #[test]
445 fn spec_rv64_prefix_not_vector() {
446 let result = parse_extensions_compact("rv64imafdc");
447 assert!(!result.contains('V'));
448 }
449
450 #[test]
451 fn spec_z_extensions_ignored() {
452 assert_eq!(
453 parse_extensions_compact("rv64imafdc_zba_zbb"),
454 "I M A F D C"
455 );
456 }
457
458 #[test]
459 fn spec_rv64_only() {
460 assert_eq!(parse_extensions_compact("rv64"), "");
461 }
462
463 #[test]
466 fn test_z_extensions_visionfive2() {
467 let result = parse_z_extensions(ISA_VISIONFIVE2);
468 assert!(result.contains("zicntr"));
469 assert!(result.contains("zicsr"));
470 assert!(result.contains("zifencei"));
471 assert!(result.contains("zba"));
472 assert!(result.contains("zbb"));
473 }
474
475 #[test]
476 fn test_z_extensions_spacemit() {
477 let result = parse_z_extensions(ISA_SPACEMIT_K1);
478 assert!(result.contains("zicbom"));
479 assert!(result.contains("zicboz"));
480 assert!(result.contains("zbc"));
481 assert!(result.contains("zbs"));
482 assert!(result.contains("zvl256b"));
483 }
484
485 #[test]
486 fn test_z_extensions_minimal() {
487 assert!(parse_z_extensions(ISA_MINIMAL).is_empty());
488 }
489
490 #[test]
491 fn spec_z_extensions_basic() {
492 assert_eq!(parse_z_extensions("rv64i_zicsr_zifencei"), "zicsr zifencei");
493 }
494
495 #[test]
496 fn spec_z_extensions_order() {
497 assert_eq!(parse_z_extensions("rv64i_zba_zbb_zbc"), "zba zbb zbc");
498 }
499
500 #[test]
501 fn spec_z_extensions_none() {
502 assert_eq!(parse_z_extensions("rv64imafdc"), "");
503 }
504
505 #[test]
506 fn spec_z_extensions_g_implies() {
507 assert_eq!(parse_z_extensions("rv64gc"), "zicsr zifencei");
508 }
509
510 #[test]
511 fn spec_z_extensions_case() {
512 assert_eq!(parse_z_extensions("rv64i_Zicsr"), "zicsr");
513 }
514
515 #[test]
518 fn spec_s_extensions() {
519 let result = parse_s_extensions("rv64i_sstc");
520 assert!(result.contains("sstc"));
521 }
522
523 #[test]
526 fn test_explained_visionfive2() {
527 let result = parse_extensions_explained(ISA_VISIONFIVE2);
528 assert_eq!(result.len(), 6); assert!(result.iter().any(|(n, _)| n == "I"));
530 assert!(result.iter().any(|(n, _)| n == "M"));
531 assert!(result.iter().any(|(n, _)| n == "F"));
532 assert!(result.iter().any(|(n, _)| n == "D"));
533 assert!(result.iter().any(|(n, _)| n == "C"));
534 }
535
536 #[test]
537 fn test_z_explained_spacemit() {
538 let result = parse_z_extensions_explained(ISA_SPACEMIT_K1);
539 assert!(result
540 .iter()
541 .any(|(n, d)| n == "Zba" && d == "Address Generation"));
542 assert!(result
543 .iter()
544 .any(|(n, d)| n == "Zbb" && d == "Basic Bit Manipulation"));
545 assert!(result
546 .iter()
547 .any(|(n, d)| n == "Zbc" && d == "Carry-less Multiply"));
548 }
549
550 #[test]
551 fn test_z_explained_ratified_2026_09() {
552 let isa = "rv64i_ziccid_zicfilp_zicfiss";
553 let result = parse_z_extensions_explained(isa);
554 assert!(result
555 .iter()
556 .any(|(n, d)| n == "Ziccid" && d == "Inst/Data Coherence"));
557 assert!(result
558 .iter()
559 .any(|(n, d)| n == "Zicfilp" && d == "CFI Landing Pads"));
560 assert!(result
561 .iter()
562 .any(|(n, d)| n == "Zicfiss" && d == "CFI Shadow Stack"));
563 }
564
565 #[test]
566 fn test_s_explained_sspmp() {
567 let isa = "rv64i_sspmp";
568 let result = parse_s_extensions_explained(isa);
569 assert!(result
570 .iter()
571 .any(|(n, d)| n == "Sspmp" && d == "S-mode Phys Mem Protection"));
572 }
573
574 #[test]
575 fn test_extension_names_no_prefix_collision() {
576 let isa_d_p = "rv64imafdc_ziccid_sspmp";
579 let z_result = parse_z_extensions_explained(isa_d_p);
580 let s_result = parse_s_extensions_explained(isa_d_p);
581 assert!(z_result
582 .iter()
583 .any(|(n, d)| n == "Ziccid" && d == "Inst/Data Coherence"));
584 assert!(s_result
585 .iter()
586 .any(|(n, d)| n == "Sspmp" && d == "S-mode Phys Mem Protection"));
587 assert!(!z_result.iter().any(|(n, _)| n == "Ziccif"));
588 assert!(!s_result.iter().any(|(n, _)| n == "Sspm"));
589
590 let isa_f_np = "rv64imafdc_ziccif_sspm";
591 let z_result2 = parse_z_extensions_explained(isa_f_np);
592 let s_result2 = parse_s_extensions_explained(isa_f_np);
593 assert!(z_result2
594 .iter()
595 .any(|(n, d)| n == "Ziccif" && d == "Inst Fetch Coherence"));
596 assert!(s_result2
597 .iter()
598 .any(|(n, d)| n == "Sspm" && d == "Pointer Masking"));
599 assert!(!z_result2.iter().any(|(n, _)| n == "Ziccid"));
600 assert!(!s_result2.iter().any(|(n, _)| n == "Sspmp"));
601 }
602
603 #[test]
606 fn test_vector_no_vector() {
607 assert!(parse_vector_from_isa(ISA_VISIONFIVE2).is_none());
608 }
609
610 #[test]
611 fn test_vector_with_v() {
612 let result = parse_vector_from_isa(ISA_SPACEMIT_K1);
613 assert!(result.is_some());
614 let detail = result.unwrap();
615 assert!(detail.contains("Enabled"));
616 assert!(detail.contains("VLEN>=256"));
617 }
618
619 #[test]
620 fn test_vector_zve_only() {
621 let result = parse_vector_from_isa("rv64imac_zve32x");
622 assert!(result.is_some());
623 assert!(result.unwrap().contains("Enabled"));
624 }
625
626 #[test]
627 fn spec_vector_with_v() {
628 let result = parse_vector_from_isa("rv64imafdcv");
629 assert!(result.is_some());
630 assert!(result.unwrap().contains("Enabled"));
631 }
632
633 #[test]
634 fn spec_vector_none() {
635 assert!(parse_vector_from_isa("rv64imafdc").is_none());
636 }
637
638 #[test]
639 fn spec_vector_vlen_256() {
640 let result = parse_vector_from_isa("rv64imafdcv_zvl256b");
641 assert!(result.is_some());
642 assert!(result.unwrap().contains("VLEN>=256"));
643 }
644
645 #[test]
646 fn spec_vector_vlen_largest() {
647 let result = parse_vector_from_isa("rv64imafdcv_zvl128b_zvl256b");
648 assert!(result.is_some());
649 assert!(result.unwrap().contains("VLEN>=256"));
650 }
651
652 #[test]
655 fn test_zk_does_not_false_match_zkn() {
656 let isa = "rv64i_zkn";
658 let result = parse_z_extensions_explained(isa);
659 assert!(
660 result.iter().any(|(n, _)| n == "Zkn"),
661 "Zkn should be found"
662 );
663 assert!(
664 !result.iter().any(|(n, _)| n == "Zk"),
665 "Zk should NOT be found (false positive)"
666 );
667 }
668
669 #[test]
670 fn test_zks_does_not_false_match_zksed() {
671 let isa = "rv64i_zksed";
673 let result = parse_z_extensions_explained(isa);
674 assert!(
675 result.iter().any(|(n, _)| n == "Zksed"),
676 "Zksed should be found"
677 );
678 assert!(
679 !result.iter().any(|(n, _)| n == "Zks"),
680 "Zks should NOT be found (false positive)"
681 );
682 assert!(
683 !result.iter().any(|(n, _)| n == "Zk"),
684 "Zk should NOT be found (false positive)"
685 );
686 }
687
688 #[test]
689 fn test_s_extension_no_collision_with_z() {
690 let isa = "rv64i_zbs_zks";
692 let result = parse_s_extensions_explained(isa);
693 assert!(
694 result.is_empty(),
695 "No S-extensions should be found in Z-only ISA: {:?}",
696 result
697 );
698 }
699
700 #[test]
701 fn test_s_extension_exact_match() {
702 let isa = "rv64i_sstc_svnapot";
704 let result = parse_s_extensions_explained(isa);
705 assert!(
706 result.iter().any(|(n, _)| n == "Sstc"),
707 "Sstc should be found"
708 );
709 assert!(
710 result.iter().any(|(n, _)| n == "Svnapot"),
711 "Svnapot should be found"
712 );
713 }
714
715 #[test]
716 fn test_zvks_does_not_false_match_zvksc() {
717 let isa = "rv64iv_zvksc";
719 let result = parse_z_extensions_explained(isa);
720 assert!(
721 result.iter().any(|(n, _)| n == "Zvksc"),
722 "Zvksc should be found"
723 );
724 assert!(
725 !result.iter().any(|(n, _)| n == "Zvks"),
726 "Zvks should NOT be found (false positive)"
727 );
728 }
729
730 #[test]
731 fn test_zicsr_not_false_positive_for_c() {
732 let isa = "rv64ima_zicsr";
735 let result = parse_extensions_compact(isa);
736 assert_eq!(result, "I M A", "C should not appear from zicsr");
737 }
738
739 #[test]
740 fn spec_vector_no_default_vlen() {
741 let result = parse_vector_from_isa("rv64imafdcv");
742 assert!(result.is_some());
743 let detail = result.unwrap();
744 assert!(detail.contains("Enabled"));
745 assert!(!detail.contains("VLEN"));
746 }
747}