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, FloatAggregateAbi};
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                    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/// The sub-architecture axis an architecture has, carrying the value a bare architecture
427/// name selects.
428///
429/// Stated once per architecture in the arch table. Giving an architecture an axis it does
430/// not have yet is one more arm here and one changed line there; nothing else in the tree
431/// has to know.
432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
433pub enum VariantAxis {
434    /// The architecture has no sub-architecture axis.
435    None,
436    Riscv(RiscvIsa),
437    Armv7(Armv7Variant),
438    Aarch64(Aarch64Level),
439    Power(PowerLevel),
440    X86(X86CpuLevel),
441}
442
443impl VariantAxis {
444    /// Sub-architecture a bare architecture name selects. Each value is what that name
445    /// has always meant to the backend.
446    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    /// `chosen` when it belongs to this axis, the axis default otherwise.
458    ///
459    /// Every route into a [`crate::Target`] goes through this, so a target cannot hold a
460    /// variant of a family its architecture does not have.
461    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}