1use lamina_platform::{RiscvIsa, 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_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 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 pub fn word_bytes(self) -> i32 {
100 match self.xlen {
101 Xlen::Rv32 => 4,
102 Xlen::Rv64 => 8,
103 }
104 }
105
106 pub fn store_word(self) -> &'static str {
108 match self.xlen {
109 Xlen::Rv32 => "sw",
110 Xlen::Rv64 => "sd",
111 }
112 }
113
114 pub fn load_word(self) -> &'static str {
116 match self.xlen {
117 Xlen::Rv32 => "lw",
118 Xlen::Rv64 => "ld",
119 }
120 }
121
122 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 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}