1use lamina_platform::TargetArchitecture;
8
9#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct RiscVTarget {
28 pub xlen: Xlen,
29 pub m: bool,
31 pub a: bool,
33 pub f: bool,
35 pub d: bool,
37 pub c: bool,
39 pub v: bool,
40}
41
42impl RiscVTarget {
43 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 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 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 pub fn word_bytes(self) -> i32 {
83 match self.xlen {
84 Xlen::Rv32 => 4,
85 Xlen::Rv64 => 8,
86 }
87 }
88
89 pub fn store_word(self) -> &'static str {
91 match self.xlen {
92 Xlen::Rv32 => "sw",
93 Xlen::Rv64 => "sd",
94 }
95 }
96
97 pub fn load_word(self) -> &'static str {
99 match self.xlen {
100 Xlen::Rv32 => "lw",
101 Xlen::Rv64 => "ld",
102 }
103 }
104
105 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 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}