Skip to main content

lamina_platform/
arch_variant.rs

1//! Sub-architecture variants: the part of a target the bare architecture name leaves out.
2//!
3//! [`crate::X86CpuLevel`] does this for x86-64. The same idea for the other families: a
4//! value chosen by the target string, defaulting to what the bare name has always meant.
5
6use std::{fmt, str::FromStr};
7
8use crate::arch_table::ArchSpec;
9use crate::cpu_level::X86CpuLevel;
10
11/// RISC-V base width plus the standard extensions the backend decides on.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct RiscvIsa {
14    pub xlen: u32,
15    /// M: multiply and divide.
16    pub m: bool,
17    /// A: atomics.
18    pub a: bool,
19    /// F: single-precision float.
20    pub f: bool,
21    /// D: double-precision float, implies F.
22    pub d: bool,
23    /// C: compressed encodings.
24    pub c: bool,
25    /// V: the vector extension.
26    pub v: bool,
27}
28
29impl RiscvIsa {
30    /// The base integer ISA alone.
31    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    /// G, the conventional set: IMAFD. What a bare `riscv32` or `riscv64` means.
44    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    /// Canonical ISA string, the spelling the variant parses from.
61    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    /// `-march=` string for an assembler. `g` is the accepted shorthand for IMAFD.
79    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    /// `rv32imac`, `rv64gc`, `rv64g`, `rv32i`. `g` expands to `imafd`.
105    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/// The armv7 profile. A is the application profile with an MMU; R is real-time; M is the
149/// microcontroller profile, which is Thumb-only.
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub enum Armv7Profile {
152    A,
153    R,
154    M,
155}
156
157impl Armv7Profile {
158    /// `.arch` directive name.
159    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/// Everything the armv7 backend hardcodes today, named.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub struct Armv7Variant {
171    pub profile: Armv7Profile,
172    /// The VFP unit, for the `.fpu` directive. `None` when the profile has no FPU.
173    /// Whether floats are *passed* in those registers is the environment's answer,
174    /// [`crate::TargetEnvironment::hardware_float`], not the variant's.
175    pub fpu: Option<&'static str>,
176    pub neon: bool,
177    /// Hardware `sdiv`/`udiv`. Cortex-A7 and later in the A profile; always in R and M.
178    pub hardware_divide: bool,
179}
180
181impl Armv7Variant {
182    /// What a bare `thumbv7m` means: a Cortex-M3, which has `sdiv`/`udiv` and no FPU.
183    /// An M4F or M7 adds a single-precision VFP, which this backend does not use.
184    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    /// What a bare `armv7`, `armv7a` or `armv7hf` has always meant.
194    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/// AArch64 architecture revision. LSE atomics arrive in v8.1.
211#[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    /// `-march=` string for an assembler.
221    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    /// Large System Extensions: single-instruction atomics instead of a load/store
230    /// exclusive loop.
231    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/// POWER processor generation for ppc64le.
240#[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    /// `modsd` / `modud`, which replace the divide-multiply-subtract remainder sequence.
258    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/// The sub-architecture a target selected, for the families that have one.
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
268pub enum ArchVariant {
269    /// The architecture has no sub-architecture axis, or the target named none.
270    #[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    /// The architecture's spec as this variant refines it.
319    ///
320    /// The single place a sub-architecture changes a property the architecture stated.
321    /// A RISC-V ISA without `d` is the soft-float ABI, which takes a different ELF
322    /// interpreter than the one the bare architecture names.
323    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                }
329                spec
330            }
331            Self::None | Self::Armv7(_) | Self::Aarch64(_) | Self::PowerPC64(_) | Self::X86(_) => {
332                spec
333            }
334        }
335    }
336
337    pub fn powerpc64(self) -> Option<PowerLevel> {
338        match self {
339            Self::PowerPC64(level) => Some(level),
340            Self::None | Self::Riscv(_) | Self::Armv7(_) | Self::Aarch64(_) | Self::X86(_) => None,
341        }
342    }
343}
344
345impl fmt::Display for ArchVariant {
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        match self {
348            Self::None => write!(f, "default"),
349            Self::Riscv(isa) => write!(f, "{}", isa.name()),
350            Self::Armv7(v) => write!(
351                f,
352                "{}{}",
353                v.profile.arch_name(),
354                if v.neon { "+neon" } else { "" }
355            ),
356            Self::Aarch64(level) => write!(f, "{}", level.march()),
357            Self::PowerPC64(level) => write!(f, "{}", level.name()),
358            Self::X86(level) => write!(f, "{level}"),
359        }
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn isa_strings_round_trip() {
369        let gc: RiscvIsa = "rv64gc".parse().expect("rv64gc");
370        assert_eq!(gc.name(), "rv64imafdc");
371        assert_eq!(gc.march(), "rv64gc");
372        let imac: RiscvIsa = "rv32imac".parse().expect("rv32imac");
373        assert!(imac.m && imac.a && imac.c);
374        assert!(!imac.has_float());
375        assert_eq!(imac.march(), "rv32imac");
376        assert_eq!(RiscvIsa::general(64).march(), "rv64g");
377        assert!("rv16gc".parse::<RiscvIsa>().is_err());
378        assert!("rv64gz".parse::<RiscvIsa>().is_err());
379        assert!("rv64".parse::<RiscvIsa>().is_err());
380        assert!("rv64mac".parse::<RiscvIsa>().is_err());
381        let gcv: RiscvIsa = "rv64gcv".parse().expect("rv64gcv");
382        assert!(gcv.v);
383        assert_eq!(gcv.name(), "rv64imafdcv");
384        assert_eq!(gcv.march(), "rv64gcv");
385        assert!(!gc.v);
386    }
387
388    #[test]
389    fn bare_riscv_keeps_meaning_imafd() {
390        let g = RiscvIsa::general(64);
391        assert!(g.m && g.a && g.f && g.d && !g.c && !g.v);
392        assert_eq!(g.name(), "rv64imafd");
393    }
394
395    #[test]
396    fn armv7_default_is_what_the_backend_hardcodes() {
397        let v = Armv7Variant::default();
398        assert_eq!(v.profile.arch_name(), "armv7-a");
399        assert_eq!(v.fpu, Some("vfpv3-d16"));
400        assert!(v.hardware_divide && !v.neon);
401    }
402
403    #[test]
404    fn levels_gate_their_instructions() {
405        assert!(!Aarch64Level::default().has_lse_atomics());
406        assert!(Aarch64Level::V8_1.has_lse_atomics());
407        assert_eq!(Aarch64Level::default().march(), "armv8-a");
408        assert!(!PowerLevel::default().has_modulo_instructions());
409        assert!(PowerLevel::Power9.has_modulo_instructions());
410    }
411
412    #[test]
413    fn variant_accessors_pick_their_family() {
414        let v = ArchVariant::Riscv(RiscvIsa::general(32));
415        assert_eq!(v.riscv().map(|i| i.xlen), Some(32));
416        assert!(v.armv7().is_none());
417        assert!(ArchVariant::None.aarch64().is_none());
418        assert_eq!(
419            ArchVariant::PowerPC64(PowerLevel::Power10).to_string(),
420            "power10"
421        );
422    }
423}
424
425/// The sub-architecture axis an architecture has, carrying the value a bare architecture
426/// name selects.
427///
428/// Stated once per architecture in the arch table. Giving an architecture an axis it does
429/// not have yet is one more arm here and one changed line there; nothing else in the tree
430/// has to know.
431#[derive(Debug, Clone, Copy, PartialEq, Eq)]
432pub enum VariantAxis {
433    /// The architecture has no sub-architecture axis.
434    None,
435    Riscv(RiscvIsa),
436    Armv7(Armv7Variant),
437    Aarch64(Aarch64Level),
438    Power(PowerLevel),
439    X86(X86CpuLevel),
440}
441
442impl VariantAxis {
443    /// Sub-architecture a bare architecture name selects. Each value is what that name
444    /// has always meant to the backend.
445    pub const fn default_variant(self) -> ArchVariant {
446        match self {
447            Self::None => ArchVariant::None,
448            Self::Riscv(isa) => ArchVariant::Riscv(isa),
449            Self::Armv7(variant) => ArchVariant::Armv7(variant),
450            Self::Aarch64(level) => ArchVariant::Aarch64(level),
451            Self::Power(level) => ArchVariant::PowerPC64(level),
452            Self::X86(level) => ArchVariant::X86(level),
453        }
454    }
455
456    /// `chosen` when it belongs to this axis, the axis default otherwise.
457    ///
458    /// Every route into a [`crate::Target`] goes through this, so a target cannot hold a
459    /// variant of a family its architecture does not have.
460    pub const fn accept(self, chosen: ArchVariant) -> ArchVariant {
461        let belongs = match (self, chosen) {
462            (Self::Riscv(_), ArchVariant::Riscv(_))
463            | (Self::Armv7(_), ArchVariant::Armv7(_))
464            | (Self::Aarch64(_), ArchVariant::Aarch64(_))
465            | (Self::Power(_), ArchVariant::PowerPC64(_))
466            | (Self::X86(_), ArchVariant::X86(_))
467            | (Self::None, ArchVariant::None) => true,
468            (Self::None, _)
469            | (Self::Riscv(_), _)
470            | (Self::Armv7(_), _)
471            | (Self::Aarch64(_), _)
472            | (Self::Power(_), _)
473            | (Self::X86(_), _) => false,
474        };
475        if belongs {
476            chosen
477        } else {
478            self.default_variant()
479        }
480    }
481}