1use std::{fmt, str::FromStr};
7
8use crate::arch_table::{ArchSpec, FloatAggregateAbi};
9use crate::cpu_level::X86CpuLevel;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct RiscvIsa {
14 pub xlen: u32,
15 pub m: bool,
17 pub a: bool,
19 pub f: bool,
21 pub d: bool,
23 pub c: bool,
25 pub v: bool,
27}
28
29impl RiscvIsa {
30 pub const fn base(xlen: u32) -> Self {
32 Self {
33 xlen,
34 m: false,
35 a: false,
36 f: false,
37 d: false,
38 c: false,
39 v: false,
40 }
41 }
42
43 pub const fn general(xlen: u32) -> Self {
45 Self {
46 xlen,
47 m: true,
48 a: true,
49 f: true,
50 d: true,
51 c: false,
52 v: false,
53 }
54 }
55
56 pub const fn has_float(self) -> bool {
57 self.f || self.d
58 }
59
60 pub fn name(self) -> String {
62 let mut s = format!("rv{}i", self.xlen);
63 for (present, letter) in [
64 (self.m, 'm'),
65 (self.a, 'a'),
66 (self.f, 'f'),
67 (self.d, 'd'),
68 (self.c, 'c'),
69 (self.v, 'v'),
70 ] {
71 if present {
72 s.push(letter);
73 }
74 }
75 s
76 }
77
78 pub fn march(self) -> String {
80 let mut s = format!("rv{}", self.xlen);
81 if self.m && self.a && self.f && self.d {
82 s.push('g');
83 } else {
84 s.push('i');
85 for (present, letter) in [(self.m, 'm'), (self.a, 'a'), (self.f, 'f'), (self.d, 'd')] {
86 if present {
87 s.push(letter);
88 }
89 }
90 }
91 if self.c {
92 s.push('c');
93 }
94 if self.v {
95 s.push('v');
96 }
97 s
98 }
99}
100
101impl FromStr for RiscvIsa {
102 type Err = &'static str;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 let rest = s.strip_prefix("rv").ok_or("expected an rv… ISA string")?;
107 let split = rest
108 .find(|c: char| !c.is_ascii_digit())
109 .ok_or("ISA string has no base after the width")?;
110 let (width, letters) = rest.split_at(split);
111 let xlen = match width {
112 "32" => 32,
113 "64" => 64,
114 "128" => 128,
115 _ => return Err("RISC-V width must be 32, 64 or 128"),
116 };
117 let mut isa = Self::base(xlen);
118 let mut seen_base = false;
119 for letter in letters.chars() {
120 match letter {
121 'i' => seen_base = true,
122 'g' => {
123 seen_base = true;
124 isa.m = true;
125 isa.a = true;
126 isa.f = true;
127 isa.d = true;
128 }
129 'm' => isa.m = true,
130 'a' => isa.a = true,
131 'f' => isa.f = true,
132 'd' => {
133 isa.f = true;
134 isa.d = true;
135 }
136 'c' => isa.c = true,
137 'v' => isa.v = true,
138 _ => return Err("unknown RISC-V extension letter"),
139 }
140 }
141 if !seen_base {
142 return Err("RISC-V ISA string needs a base, 'i' or 'g'");
143 }
144 Ok(isa)
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum Armv7Profile {
152 A,
153 R,
154 M,
155}
156
157impl Armv7Profile {
158 pub const fn arch_name(self) -> &'static str {
160 match self {
161 Self::A => "armv7-a",
162 Self::R => "armv7-r",
163 Self::M => "armv7-m",
164 }
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub struct Armv7Variant {
171 pub profile: Armv7Profile,
172 pub fpu: Option<&'static str>,
176 pub neon: bool,
177 pub hardware_divide: bool,
179}
180
181impl Armv7Variant {
182 pub const fn cortex_m3() -> Self {
185 Self {
186 profile: Armv7Profile::M,
187 fpu: None,
188 neon: false,
189 hardware_divide: true,
190 }
191 }
192
193 pub const fn cortex_a7() -> Self {
195 Self {
196 profile: Armv7Profile::A,
197 fpu: Some("vfpv3-d16"),
198 neon: false,
199 hardware_divide: true,
200 }
201 }
202}
203
204impl Default for Armv7Variant {
205 fn default() -> Self {
206 Self::cortex_a7()
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
212pub enum Aarch64Level {
213 #[default]
214 V8_0,
215 V8_1,
216 V8_2,
217}
218
219impl Aarch64Level {
220 pub const fn march(self) -> &'static str {
222 match self {
223 Self::V8_0 => "armv8-a",
224 Self::V8_1 => "armv8.1-a",
225 Self::V8_2 => "armv8.2-a",
226 }
227 }
228
229 pub const fn has_lse_atomics(self) -> bool {
232 match self {
233 Self::V8_0 => false,
234 Self::V8_1 | Self::V8_2 => true,
235 }
236 }
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
241pub enum PowerLevel {
242 #[default]
243 Power8,
244 Power9,
245 Power10,
246}
247
248impl PowerLevel {
249 pub const fn name(self) -> &'static str {
250 match self {
251 Self::Power8 => "power8",
252 Self::Power9 => "power9",
253 Self::Power10 => "power10",
254 }
255 }
256
257 pub const fn has_modulo_instructions(self) -> bool {
259 match self {
260 Self::Power8 => false,
261 Self::Power9 | Self::Power10 => true,
262 }
263 }
264}
265
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
268pub enum ArchVariant {
269 #[default]
271 None,
272 Riscv(RiscvIsa),
273 Armv7(Armv7Variant),
274 Aarch64(Aarch64Level),
275 PowerPC64(PowerLevel),
276 X86(X86CpuLevel),
277}
278
279impl ArchVariant {
280 pub fn riscv(self) -> Option<RiscvIsa> {
281 match self {
282 Self::Riscv(isa) => Some(isa),
283 Self::None | Self::Armv7(_) | Self::Aarch64(_) | Self::PowerPC64(_) | Self::X86(_) => {
284 None
285 }
286 }
287 }
288
289 pub fn armv7(self) -> Option<Armv7Variant> {
290 match self {
291 Self::Armv7(v) => Some(v),
292 Self::None | Self::Riscv(_) | Self::Aarch64(_) | Self::PowerPC64(_) | Self::X86(_) => {
293 None
294 }
295 }
296 }
297
298 pub fn aarch64(self) -> Option<Aarch64Level> {
299 match self {
300 Self::Aarch64(level) => Some(level),
301 Self::None | Self::Riscv(_) | Self::Armv7(_) | Self::PowerPC64(_) | Self::X86(_) => {
302 None
303 }
304 }
305 }
306
307 pub fn x86(self) -> Option<X86CpuLevel> {
308 match self {
309 Self::X86(level) => Some(level),
310 Self::None
311 | Self::Riscv(_)
312 | Self::Armv7(_)
313 | Self::Aarch64(_)
314 | Self::PowerPC64(_) => None,
315 }
316 }
317
318 pub fn refine(self, mut spec: ArchSpec) -> ArchSpec {
324 match self {
325 Self::Riscv(isa) => {
326 if !isa.d {
327 spec.interpreters.gnu = spec.interpreters.gnu_soft_float;
328 spec.float_aggregates = FloatAggregateAbi::Refused;
329 }
330 spec
331 }
332 Self::None | Self::Armv7(_) | Self::Aarch64(_) | Self::PowerPC64(_) | Self::X86(_) => {
333 spec
334 }
335 }
336 }
337
338 pub fn powerpc64(self) -> Option<PowerLevel> {
339 match self {
340 Self::PowerPC64(level) => Some(level),
341 Self::None | Self::Riscv(_) | Self::Armv7(_) | Self::Aarch64(_) | Self::X86(_) => None,
342 }
343 }
344}
345
346impl fmt::Display for ArchVariant {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 match self {
349 Self::None => write!(f, "default"),
350 Self::Riscv(isa) => write!(f, "{}", isa.name()),
351 Self::Armv7(v) => write!(
352 f,
353 "{}{}",
354 v.profile.arch_name(),
355 if v.neon { "+neon" } else { "" }
356 ),
357 Self::Aarch64(level) => write!(f, "{}", level.march()),
358 Self::PowerPC64(level) => write!(f, "{}", level.name()),
359 Self::X86(level) => write!(f, "{level}"),
360 }
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn isa_strings_round_trip() {
370 let gc: RiscvIsa = "rv64gc".parse().expect("rv64gc");
371 assert_eq!(gc.name(), "rv64imafdc");
372 assert_eq!(gc.march(), "rv64gc");
373 let imac: RiscvIsa = "rv32imac".parse().expect("rv32imac");
374 assert!(imac.m && imac.a && imac.c);
375 assert!(!imac.has_float());
376 assert_eq!(imac.march(), "rv32imac");
377 assert_eq!(RiscvIsa::general(64).march(), "rv64g");
378 assert!("rv16gc".parse::<RiscvIsa>().is_err());
379 assert!("rv64gz".parse::<RiscvIsa>().is_err());
380 assert!("rv64".parse::<RiscvIsa>().is_err());
381 assert!("rv64mac".parse::<RiscvIsa>().is_err());
382 let gcv: RiscvIsa = "rv64gcv".parse().expect("rv64gcv");
383 assert!(gcv.v);
384 assert_eq!(gcv.name(), "rv64imafdcv");
385 assert_eq!(gcv.march(), "rv64gcv");
386 assert!(!gc.v);
387 }
388
389 #[test]
390 fn bare_riscv_keeps_meaning_imafd() {
391 let g = RiscvIsa::general(64);
392 assert!(g.m && g.a && g.f && g.d && !g.c && !g.v);
393 assert_eq!(g.name(), "rv64imafd");
394 }
395
396 #[test]
397 fn armv7_default_is_what_the_backend_hardcodes() {
398 let v = Armv7Variant::default();
399 assert_eq!(v.profile.arch_name(), "armv7-a");
400 assert_eq!(v.fpu, Some("vfpv3-d16"));
401 assert!(v.hardware_divide && !v.neon);
402 }
403
404 #[test]
405 fn levels_gate_their_instructions() {
406 assert!(!Aarch64Level::default().has_lse_atomics());
407 assert!(Aarch64Level::V8_1.has_lse_atomics());
408 assert_eq!(Aarch64Level::default().march(), "armv8-a");
409 assert!(!PowerLevel::default().has_modulo_instructions());
410 assert!(PowerLevel::Power9.has_modulo_instructions());
411 }
412
413 #[test]
414 fn variant_accessors_pick_their_family() {
415 let v = ArchVariant::Riscv(RiscvIsa::general(32));
416 assert_eq!(v.riscv().map(|i| i.xlen), Some(32));
417 assert!(v.armv7().is_none());
418 assert!(ArchVariant::None.aarch64().is_none());
419 assert_eq!(
420 ArchVariant::PowerPC64(PowerLevel::Power10).to_string(),
421 "power10"
422 );
423 }
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
433pub enum VariantAxis {
434 None,
436 Riscv(RiscvIsa),
437 Armv7(Armv7Variant),
438 Aarch64(Aarch64Level),
439 Power(PowerLevel),
440 X86(X86CpuLevel),
441}
442
443impl VariantAxis {
444 pub const fn default_variant(self) -> ArchVariant {
447 match self {
448 Self::None => ArchVariant::None,
449 Self::Riscv(isa) => ArchVariant::Riscv(isa),
450 Self::Armv7(variant) => ArchVariant::Armv7(variant),
451 Self::Aarch64(level) => ArchVariant::Aarch64(level),
452 Self::Power(level) => ArchVariant::PowerPC64(level),
453 Self::X86(level) => ArchVariant::X86(level),
454 }
455 }
456
457 pub const fn accept(self, chosen: ArchVariant) -> ArchVariant {
462 let belongs = match (self, chosen) {
463 (Self::Riscv(_), ArchVariant::Riscv(_))
464 | (Self::Armv7(_), ArchVariant::Armv7(_))
465 | (Self::Aarch64(_), ArchVariant::Aarch64(_))
466 | (Self::Power(_), ArchVariant::PowerPC64(_))
467 | (Self::X86(_), ArchVariant::X86(_))
468 | (Self::None, ArchVariant::None) => true,
469 (Self::None, _)
470 | (Self::Riscv(_), _)
471 | (Self::Armv7(_), _)
472 | (Self::Aarch64(_), _)
473 | (Self::Power(_), _)
474 | (Self::X86(_), _) => false,
475 };
476 if belongs {
477 chosen
478 } else {
479 self.default_variant()
480 }
481 }
482}