use lamina_platform::TargetArchitecture;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Xlen {
Rv32,
Rv64,
}
impl Xlen {
pub fn bits(self) -> u32 {
match self {
Xlen::Rv32 => 32,
Xlen::Rv64 => 64,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RiscVTarget {
pub xlen: Xlen,
pub m: bool,
pub a: bool,
pub f: bool,
pub d: bool,
pub c: bool,
pub v: bool,
}
impl RiscVTarget {
pub fn base(xlen: Xlen) -> Self {
Self {
xlen,
m: false,
a: false,
f: false,
d: false,
c: false,
v: false,
}
}
pub fn general(xlen: Xlen) -> Self {
Self {
xlen,
m: true,
a: true,
f: true,
d: true,
c: false,
v: false,
}
}
pub fn from_arch(arch: TargetArchitecture) -> Option<Self> {
match arch {
TargetArchitecture::Riscv32 => Some(Self::general(Xlen::Rv32)),
TargetArchitecture::Riscv64 => Some(Self::general(Xlen::Rv64)),
_ => None,
}
}
pub fn word_bytes(self) -> i32 {
match self.xlen {
Xlen::Rv32 => 4,
Xlen::Rv64 => 8,
}
}
pub fn store_word(self) -> &'static str {
match self.xlen {
Xlen::Rv32 => "sw",
Xlen::Rv64 => "sd",
}
}
pub fn load_word(self) -> &'static str {
match self.xlen {
Xlen::Rv32 => "lw",
Xlen::Rv64 => "ld",
}
}
pub fn require_float(self, is_f32: bool) -> Result<(), String> {
let (have, needs) = if is_f32 { (self.f, 'F') } else { (self.d, 'D') };
if have {
return Ok(());
}
Err(format!(
"needs the {needs} extension, but the target is {}. Select an ISA with \
{needs} or lower the operation to soft float before codegen.",
self.isa_name()
))
}
pub fn require_vector(self) -> Result<(), String> {
if self.v {
Ok(())
} else {
Err(format!(
"V extension is required for vectors on {}",
self.isa_name()
))
}
}
pub fn require_i64(self) -> Result<(), String> {
if self.xlen == Xlen::Rv64 {
return Ok(());
}
Err(format!(
"type i64 is not supported on target {}. An i64 needs two rv32 registers or two \
words of spill storage; lower it to a register pair before codegen.",
self.isa_name()
))
}
pub fn isa_name(&self) -> String {
let mut s = format!("rv{}i", self.xlen.bits());
for (present, letter) in [
(self.m, 'm'),
(self.a, 'a'),
(self.f, 'f'),
(self.d, 'd'),
(self.c, 'c'),
(self.v, 'v'),
] {
if present {
s.push(letter);
}
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vectors_require_explicit_extension() {
let mut target = RiscVTarget::general(Xlen::Rv64);
assert!(target.require_vector().is_err());
target.v = true;
assert!(target.require_vector().is_ok());
assert_eq!(target.isa_name(), "rv64imafdv");
}
#[test]
fn base_has_no_multiply() {
assert!(!RiscVTarget::base(Xlen::Rv64).m);
assert!(RiscVTarget::general(Xlen::Rv64).m);
}
#[test]
fn float_widths_need_their_extension() {
let base = RiscVTarget::base(Xlen::Rv64);
assert!(base.require_float(true).is_err(), "rv64i has no F");
assert!(base.require_float(false).is_err(), "rv64i has no D");
let g = RiscVTarget::general(Xlen::Rv64);
assert!(g.require_float(true).is_ok());
assert!(g.require_float(false).is_ok());
let f_only = RiscVTarget {
f: true,
..RiscVTarget::base(Xlen::Rv32)
};
assert!(f_only.require_float(true).is_ok(), "F covers f32");
let err = f_only
.require_float(false)
.expect_err("F does not cover f64");
assert!(err.contains('D'), "message should name D: {err}");
assert!(err.contains("rv32if"), "message should name the ISA: {err}");
}
#[test]
fn word_size_follows_xlen() {
let rv32 = RiscVTarget::general(Xlen::Rv32);
let rv64 = RiscVTarget::general(Xlen::Rv64);
assert_eq!(
(rv32.word_bytes(), rv32.store_word(), rv32.load_word()),
(4, "sw", "lw")
);
assert_eq!(
(rv64.word_bytes(), rv64.store_word(), rv64.load_word()),
(8, "sd", "ld")
);
}
#[test]
fn i64_requires_rv64() {
let result = RiscVTarget::general(Xlen::Rv32).require_i64();
assert!(result.is_err(), "rv32 cannot hold i64 in one register");
let Err(error) = result else {
return;
};
assert!(error.contains("i64"));
assert!(error.contains("rv32imafd"));
assert!(RiscVTarget::general(Xlen::Rv64).require_i64().is_ok());
}
#[test]
fn isa_names_read_the_usual_way() {
assert_eq!(RiscVTarget::base(Xlen::Rv32).isa_name(), "rv32i");
assert_eq!(RiscVTarget::general(Xlen::Rv64).isa_name(), "rv64imafd");
}
#[test]
fn bare_targets_default_to_general() {
let t = RiscVTarget::from_arch(TargetArchitecture::Riscv64).expect("riscv64");
assert_eq!(t.xlen, Xlen::Rv64);
assert!(
t.m,
"default must keep multiply, the backend already emitted it"
);
assert_eq!(RiscVTarget::from_arch(TargetArchitecture::X86_64), None);
}
}