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::{RiscvIsa, 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    /// The ISA a target string selected.
70    pub fn from_isa(isa: RiscvIsa) -> Self {
71        Self {
72            xlen: if isa.xlen >= 64 {
73                Xlen::Rv64
74            } else {
75                Xlen::Rv32
76            },
77            m: isa.m,
78            a: isa.a,
79            f: isa.f,
80            d: isa.d,
81            c: isa.c,
82            v: isa.v,
83        }
84    }
85
86    /// Default for a bare `riscv32`/`riscv64` target.
87    ///
88    /// G, matching what most toolchains assume, and what this backend already emitted
89    /// before extensions were modelled at all.
90    pub fn from_arch(arch: TargetArchitecture) -> Option<Self> {
91        match arch {
92            TargetArchitecture::Riscv32 => Some(Self::general(Xlen::Rv32)),
93            TargetArchitecture::Riscv64 => Some(Self::general(Xlen::Rv64)),
94            _ => None,
95        }
96    }
97
98    /// Bytes in a general-purpose register.
99    pub fn word_bytes(self) -> i32 {
100        match self.xlen {
101            Xlen::Rv32 => 4,
102            Xlen::Rv64 => 8,
103        }
104    }
105
106    /// Mnemonic for storing a whole register. `sd` has no rv32 encoding.
107    pub fn store_word(self) -> &'static str {
108        match self.xlen {
109            Xlen::Rv32 => "sw",
110            Xlen::Rv64 => "sd",
111        }
112    }
113
114    /// Mnemonic for loading a whole register.
115    pub fn load_word(self) -> &'static str {
116        match self.xlen {
117            Xlen::Rv32 => "lw",
118            Xlen::Rv64 => "ld",
119        }
120    }
121
122    /// Check the float extension a width needs is present.
123    ///
124    /// F covers f32, D covers f64. Emitting `fadd.d` on a target without D produces an
125    /// instruction the core traps on.
126    pub fn require_float(self, is_f32: bool) -> Result<(), String> {
127        let (have, needs) = if is_f32 { (self.f, 'F') } else { (self.d, 'D') };
128        if have {
129            return Ok(());
130        }
131        Err(format!(
132            "needs the {needs} extension, but the target is {}. Select an ISA with \
133             {needs} or lower the operation to soft float before codegen.",
134            self.isa_name()
135        ))
136    }
137
138    pub fn require_vector(self) -> Result<(), String> {
139        if self.v {
140            Ok(())
141        } else {
142            Err(format!(
143                "V extension is required for vectors on {}",
144                self.isa_name()
145            ))
146        }
147    }
148
149    pub fn require_i64(self) -> Result<(), String> {
150        if self.xlen == Xlen::Rv64 {
151            return Ok(());
152        }
153        Err(format!(
154            "type i64 is not supported on target {}. An i64 needs two rv32 registers or two \
155             words of spill storage; lower it to a register pair before codegen.",
156            self.isa_name()
157        ))
158    }
159
160    /// ISA string, for diagnostics.
161    pub fn isa_name(&self) -> String {
162        let mut s = format!("rv{}i", self.xlen.bits());
163        for (present, letter) in [
164            (self.m, 'm'),
165            (self.a, 'a'),
166            (self.f, 'f'),
167            (self.d, 'd'),
168            (self.c, 'c'),
169            (self.v, 'v'),
170        ] {
171            if present {
172                s.push(letter);
173            }
174        }
175        s
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn vectors_require_explicit_extension() {
185        let mut target = RiscVTarget::general(Xlen::Rv64);
186        assert!(target.require_vector().is_err());
187        target.v = true;
188        assert!(target.require_vector().is_ok());
189        assert_eq!(target.isa_name(), "rv64imafdv");
190    }
191
192    #[test]
193    fn base_has_no_multiply() {
194        assert!(!RiscVTarget::base(Xlen::Rv64).m);
195        assert!(RiscVTarget::general(Xlen::Rv64).m);
196    }
197
198    #[test]
199    fn float_widths_need_their_extension() {
200        let base = RiscVTarget::base(Xlen::Rv64);
201        assert!(base.require_float(true).is_err(), "rv64i has no F");
202        assert!(base.require_float(false).is_err(), "rv64i has no D");
203
204        let g = RiscVTarget::general(Xlen::Rv64);
205        assert!(g.require_float(true).is_ok());
206        assert!(g.require_float(false).is_ok());
207
208        let f_only = RiscVTarget {
209            f: true,
210            ..RiscVTarget::base(Xlen::Rv32)
211        };
212        assert!(f_only.require_float(true).is_ok(), "F covers f32");
213        let err = f_only
214            .require_float(false)
215            .expect_err("F does not cover f64");
216        assert!(err.contains('D'), "message should name D: {err}");
217        assert!(err.contains("rv32if"), "message should name the ISA: {err}");
218    }
219
220    #[test]
221    fn word_size_follows_xlen() {
222        let rv32 = RiscVTarget::general(Xlen::Rv32);
223        let rv64 = RiscVTarget::general(Xlen::Rv64);
224        assert_eq!(
225            (rv32.word_bytes(), rv32.store_word(), rv32.load_word()),
226            (4, "sw", "lw")
227        );
228        assert_eq!(
229            (rv64.word_bytes(), rv64.store_word(), rv64.load_word()),
230            (8, "sd", "ld")
231        );
232    }
233
234    #[test]
235    fn i64_requires_rv64() {
236        let result = RiscVTarget::general(Xlen::Rv32).require_i64();
237        assert!(result.is_err(), "rv32 cannot hold i64 in one register");
238        let Err(error) = result else {
239            return;
240        };
241        assert!(error.contains("i64"));
242        assert!(error.contains("rv32imafd"));
243        assert!(RiscVTarget::general(Xlen::Rv64).require_i64().is_ok());
244    }
245
246    #[test]
247    fn isa_names_read_the_usual_way() {
248        assert_eq!(RiscVTarget::base(Xlen::Rv32).isa_name(), "rv32i");
249        assert_eq!(RiscVTarget::general(Xlen::Rv64).isa_name(), "rv64imafd");
250    }
251
252    #[test]
253    fn bare_targets_default_to_general() {
254        let t = RiscVTarget::from_arch(TargetArchitecture::Riscv64).expect("riscv64");
255        assert_eq!(t.xlen, Xlen::Rv64);
256        assert!(
257            t.m,
258            "default must keep multiply, the backend already emitted it"
259        );
260        assert_eq!(RiscVTarget::from_arch(TargetArchitecture::X86_64), None);
261    }
262}