Skip to main content

lamina_codegen/riscv/
target.rs

1//! Which RISC-V a target actually is: register width and extension set.
2//!
3//! `TargetArchitecture` only distinguishes `Riscv32` from `Riscv64`, so the backend had
4//! no way to say whether multiply and divide were available. It emitted `mul` and `div`
5//! unconditionally, which are M-extension instructions an rv64i core cannot execute.
6
7use lamina_platform::TargetArchitecture;
8
9/// Register width in bits.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Xlen {
12    Rv32,
13    Rv64,
14}
15
16impl Xlen {
17    pub fn bits(self) -> u32 {
18        match self {
19            Xlen::Rv32 => 32,
20            Xlen::Rv64 => 64,
21        }
22    }
23}
24
25/// Base integer ISA plus the standard extensions the backend cares about.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct RiscVTarget {
28    pub xlen: Xlen,
29    /// M: multiply and divide.
30    pub m: bool,
31    /// A: atomics.
32    pub a: bool,
33    /// F: single-precision float.
34    pub f: bool,
35    /// D: double-precision float, implies F.
36    pub d: bool,
37    /// C: compressed encodings.
38    pub c: bool,
39    pub v: bool,
40}
41
42impl RiscVTarget {
43    /// The base integer ISA on its own, rv32i or rv64i.
44    pub fn base(xlen: Xlen) -> Self {
45        Self {
46            xlen,
47            m: false,
48            a: false,
49            f: false,
50            d: false,
51            c: false,
52            v: false,
53        }
54    }
55
56    /// G, the conventional general-purpose set: IMAFD.
57    pub fn general(xlen: Xlen) -> Self {
58        Self {
59            xlen,
60            m: true,
61            a: true,
62            f: true,
63            d: true,
64            c: false,
65            v: false,
66        }
67    }
68
69    /// Default for a bare `riscv32`/`riscv64` target.
70    ///
71    /// G, matching what most toolchains assume, and what this backend already emitted
72    /// before extensions were modelled at all.
73    pub fn from_arch(arch: TargetArchitecture) -> Option<Self> {
74        match arch {
75            TargetArchitecture::Riscv32 => Some(Self::general(Xlen::Rv32)),
76            TargetArchitecture::Riscv64 => Some(Self::general(Xlen::Rv64)),
77            _ => None,
78        }
79    }
80
81    /// Bytes in a general-purpose register.
82    pub fn word_bytes(self) -> i32 {
83        match self.xlen {
84            Xlen::Rv32 => 4,
85            Xlen::Rv64 => 8,
86        }
87    }
88
89    /// Mnemonic for storing a whole register. `sd` has no rv32 encoding.
90    pub fn store_word(self) -> &'static str {
91        match self.xlen {
92            Xlen::Rv32 => "sw",
93            Xlen::Rv64 => "sd",
94        }
95    }
96
97    /// Mnemonic for loading a whole register.
98    pub fn load_word(self) -> &'static str {
99        match self.xlen {
100            Xlen::Rv32 => "lw",
101            Xlen::Rv64 => "ld",
102        }
103    }
104
105    /// Check the float extension a width needs is present.
106    ///
107    /// F covers f32, D covers f64. Emitting `fadd.d` on a target without D produces an
108    /// instruction the core traps on.
109    pub fn require_float(self, is_f32: bool) -> Result<(), String> {
110        let (have, needs) = if is_f32 { (self.f, 'F') } else { (self.d, 'D') };
111        if have {
112            return Ok(());
113        }
114        Err(format!(
115            "needs the {needs} extension, but the target is {}. Select an ISA with \
116             {needs} or lower the operation to soft float before codegen.",
117            self.isa_name()
118        ))
119    }
120
121    pub fn require_vector(self) -> Result<(), String> {
122        if self.v {
123            Ok(())
124        } else {
125            Err(format!(
126                "V extension is required for vectors on {}",
127                self.isa_name()
128            ))
129        }
130    }
131
132    pub fn require_i64(self) -> Result<(), String> {
133        if self.xlen == Xlen::Rv64 {
134            return Ok(());
135        }
136        Err(format!(
137            "type i64 is not supported on target {}. An i64 needs two rv32 registers or two \
138             words of spill storage; lower it to a register pair before codegen.",
139            self.isa_name()
140        ))
141    }
142
143    /// ISA string, for diagnostics.
144    pub fn isa_name(&self) -> String {
145        let mut s = format!("rv{}i", self.xlen.bits());
146        for (present, letter) in [
147            (self.m, 'm'),
148            (self.a, 'a'),
149            (self.f, 'f'),
150            (self.d, 'd'),
151            (self.c, 'c'),
152            (self.v, 'v'),
153        ] {
154            if present {
155                s.push(letter);
156            }
157        }
158        s
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn vectors_require_explicit_extension() {
168        let mut target = RiscVTarget::general(Xlen::Rv64);
169        assert!(target.require_vector().is_err());
170        target.v = true;
171        assert!(target.require_vector().is_ok());
172        assert_eq!(target.isa_name(), "rv64imafdv");
173    }
174
175    #[test]
176    fn base_has_no_multiply() {
177        assert!(!RiscVTarget::base(Xlen::Rv64).m);
178        assert!(RiscVTarget::general(Xlen::Rv64).m);
179    }
180
181    #[test]
182    fn float_widths_need_their_extension() {
183        let base = RiscVTarget::base(Xlen::Rv64);
184        assert!(base.require_float(true).is_err(), "rv64i has no F");
185        assert!(base.require_float(false).is_err(), "rv64i has no D");
186
187        let g = RiscVTarget::general(Xlen::Rv64);
188        assert!(g.require_float(true).is_ok());
189        assert!(g.require_float(false).is_ok());
190
191        let f_only = RiscVTarget {
192            f: true,
193            ..RiscVTarget::base(Xlen::Rv32)
194        };
195        assert!(f_only.require_float(true).is_ok(), "F covers f32");
196        let err = f_only
197            .require_float(false)
198            .expect_err("F does not cover f64");
199        assert!(err.contains('D'), "message should name D: {err}");
200        assert!(err.contains("rv32if"), "message should name the ISA: {err}");
201    }
202
203    #[test]
204    fn word_size_follows_xlen() {
205        let rv32 = RiscVTarget::general(Xlen::Rv32);
206        let rv64 = RiscVTarget::general(Xlen::Rv64);
207        assert_eq!(
208            (rv32.word_bytes(), rv32.store_word(), rv32.load_word()),
209            (4, "sw", "lw")
210        );
211        assert_eq!(
212            (rv64.word_bytes(), rv64.store_word(), rv64.load_word()),
213            (8, "sd", "ld")
214        );
215    }
216
217    #[test]
218    fn i64_requires_rv64() {
219        let result = RiscVTarget::general(Xlen::Rv32).require_i64();
220        assert!(result.is_err(), "rv32 cannot hold i64 in one register");
221        let Err(error) = result else {
222            return;
223        };
224        assert!(error.contains("i64"));
225        assert!(error.contains("rv32imafd"));
226        assert!(RiscVTarget::general(Xlen::Rv64).require_i64().is_ok());
227    }
228
229    #[test]
230    fn isa_names_read_the_usual_way() {
231        assert_eq!(RiscVTarget::base(Xlen::Rv32).isa_name(), "rv32i");
232        assert_eq!(RiscVTarget::general(Xlen::Rv64).isa_name(), "rv64imafd");
233    }
234
235    #[test]
236    fn bare_targets_default_to_general() {
237        let t = RiscVTarget::from_arch(TargetArchitecture::Riscv64).expect("riscv64");
238        assert_eq!(t.xlen, Xlen::Rv64);
239        assert!(
240            t.m,
241            "default must keep multiply, the backend already emitted it"
242        );
243        assert_eq!(RiscVTarget::from_arch(TargetArchitecture::X86_64), None);
244    }
245}