riscv_target_parser/
lib.rs

1pub mod extension;
2pub use extension::{Extension, Extensions};
3
4/// Error variants for the RISC-V target parser.
5#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6pub enum Error<'a> {
7    InvalidTriple(&'a str),
8    InvalidArch(&'a str),
9    InvalidWidth(usize),
10    UnknownExtension(&'a str),
11    UnknownTargetFeature(&'a str),
12}
13
14/// Helper struct to parse and store a target triple.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct TargetTriple<'a> {
17    arch: &'a str,
18    vendor: &'a str,
19    os: &'a str,
20    bin: Option<&'a str>,
21}
22
23impl<'a> TryFrom<&'a str> for TargetTriple<'a> {
24    type Error = Error<'a>;
25
26    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
27        let mut parts = value.split('-');
28
29        let arch = parts.next().ok_or(Error::InvalidTriple(value))?;
30        let vendor = parts.next().ok_or(Error::InvalidTriple(value))?;
31        let os = parts.next().ok_or(Error::InvalidTriple(value))?;
32        let bin = parts.next();
33
34        Ok(Self {
35            arch,
36            vendor,
37            os,
38            bin,
39        })
40    }
41}
42
43impl std::fmt::Display for TargetTriple<'_> {
44    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
45        write!(f, "{}-{}-{}", self.arch, self.vendor, self.os)?;
46        if let Some(bin) = self.bin {
47            write!(f, "-{bin}")?;
48        }
49        Ok(())
50    }
51}
52
53/// The width of the RISC-V architecture.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum Width {
56    /// 32-bit RISC-V architecture.
57    W32,
58    /// 64-bit RISC-V architecture.
59    W64,
60    /// 128-bit RISC-V architecture.
61    W128,
62}
63
64impl std::fmt::Display for Width {
65    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66        match self {
67            Self::W32 => write!(f, "32"),
68            Self::W64 => write!(f, "64"),
69            Self::W128 => write!(f, "128"),
70        }
71    }
72}
73
74macro_rules! impl_try_from_width {
75    ($($t:ty),*) => {
76        $(
77            impl TryFrom<$t> for Width {
78                type Error = Error<'static>;
79                fn try_from(bits: $t) -> Result<Self, Self::Error> {
80                    match bits {
81                        32 => Ok(Self::W32),
82                        64 => Ok(Self::W64),
83                        128 => Ok(Self::W128),
84                        _ => Err(Self::Error::InvalidWidth(bits as usize)),
85                    }
86                }
87            }
88            impl From<Width> for $t {
89                fn from(width: Width) -> Self {
90                    match width {
91                        Width::W32 => 32,
92                        Width::W64 => 64,
93                        Width::W128 => 128,
94                    }
95                }
96            }
97        )*
98    };
99}
100impl_try_from_width!(u8, u16, u32, u64, u128, usize, i16, i32, i64, i128, isize);
101
102/// Struct that represents a RISC-V target.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct RiscvTarget {
105    width: Width,
106    extensions: Extensions,
107}
108
109// Returns whether a target feature _might_ be an ISA extension according to a non-exhaustive list
110// of known unrelated features flags.
111fn is_isa_extension(feature: &str) -> bool {
112    feature != "relax"
113}
114
115impl RiscvTarget {
116    /// Builds a RISC-V target from a target triple and cargo flags.
117    /// This function is expected to be called from a build script.
118    ///
119    /// The target triple is expected to be in the form `riscv{width}{extensions}-vendor-os[-bin]`.
120    /// If the target triple is invalid, an error is returned.
121    ///
122    /// # Example
123    ///
124    /// ```no_run
125    ///
126    /// // In build.rs
127    /// let target = std::env::var("TARGET").unwrap();
128    /// let cargo_flags = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();
129    /// let target = riscv_target_parser::RiscvTarget::build(&target, &cargo_flags).unwrap(); // This will panic if the target is invalid
130    /// ```
131    pub fn build<'a>(target: &'a str, cargo_flags: &'a str) -> Result<Self, Error<'a>> {
132        let triple = TargetTriple::try_from(target)?;
133        let mut target = Self::try_from(triple)?;
134
135        for target_feature in cargo_flags
136            .split(0x1fu8 as char)
137            .filter(|arg| arg.starts_with("target-feature="))
138            .flat_map(|arg| {
139                let arg = arg.trim_start_matches("target-feature=");
140                arg.split(',')
141            })
142        {
143            if let Some(feature) = target_feature.strip_prefix('+') {
144                if is_isa_extension(feature) {
145                    let extension = Extension::try_from(feature)?;
146                    target.extensions.insert(extension);
147                }
148            } else if let Some(feature) = target_feature.strip_prefix('-') {
149                if is_isa_extension(feature) {
150                    let extension = Extension::try_from(feature)?;
151                    target.extensions.remove(&extension);
152                }
153            } else {
154                return Err(Error::UnknownTargetFeature(target_feature));
155            }
156        }
157        Ok(target)
158    }
159
160    /// Returns a list of flags to pass to `rustc` for the given RISC-V target.
161    /// This function is expected to be called from a build script.
162    ///
163    /// # Example
164    ///
165    /// ```no_run
166    /// let target = std::env::var("TARGET").unwrap();
167    /// let cargo_flags = std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap();
168    /// let target = riscv_target_parser::RiscvTarget::build(&target, &cargo_flags).unwrap();
169    /// for flag in target.rustc_flags() {
170    ///    println!("cargo:rustc-check-cfg=cfg({})", flag);
171    ///    println!("cargo:rustc-cfg={}", flag);
172    /// }
173    ///
174    pub fn rustc_flags(&self) -> Vec<String> {
175        self.extensions
176            .extensions()
177            .iter()
178            .map(|e| format!("riscv{e}"))
179            .collect::<Vec<_>>()
180    }
181
182    /// Returns the LLVM base ISA for the given RISC-V target.
183    pub fn llvm_base_isa(&self) -> String {
184        match (self.width, self.extensions.base_extension()) {
185            (Width::W32, Some(Extension::I)) => String::from("rv32i"),
186            (Width::W32, Some(Extension::E)) => String::from("rv32e"),
187            (Width::W64, Some(Extension::I)) => String::from("rv64i"),
188            (Width::W64, Some(Extension::E)) => String::from("rv64e"),
189            (_, None) => panic!("RISC-V target must have a base extension"),
190            _ => panic!("LLVM does not support this base ISA"),
191        }
192    }
193
194    /// Returns the arch code to patch LLVM spurious errors.
195    ///
196    /// # Note
197    ///
198    /// This is a provisional patch and is limited to work for the riscv-rt crate only.
199    ///
200    /// # Related issues
201    ///
202    /// - <https://github.com/rust-embedded/riscv/issues/175>
203    /// - <https://github.com/rust-lang/rust/issues/80608>
204    /// - <https://github.com/llvm/llvm-project/issues/61991>
205    pub fn llvm_arch_patch(&self) -> String {
206        let mut patch = self.llvm_base_isa();
207        if self.extensions.contains(&Extension::M) {
208            patch.push('m');
209        }
210        if self.extensions.contains(&Extension::F) {
211            patch.push('f');
212        }
213        if self.extensions.contains(&Extension::D) {
214            patch.push('d');
215        }
216        patch
217    }
218
219    /// Returns the width of the RISC-V architecture.
220    pub fn width(&self) -> Width {
221        self.width
222    }
223
224    /// Returns the base extension of the RISC-V architecture (if any).
225    pub fn base_extension(&self) -> Option<Extension> {
226        self.extensions.base_extension()
227    }
228}
229
230impl<'a> TryFrom<TargetTriple<'a>> for RiscvTarget {
231    type Error = Error<'a>;
232
233    fn try_from(triple: TargetTriple<'a>) -> Result<Self, Self::Error> {
234        match triple.arch.strip_prefix("riscv") {
235            Some(arch) => {
236                match arch
237                    .find(|c: char| !c.is_ascii_digit())
238                    .unwrap_or(arch.len())
239                {
240                    0 => Err(Error::InvalidArch(arch)),
241                    digit_end => {
242                        let (width_str, extensions_str) = arch.split_at(digit_end);
243                        let width = width_str.parse::<u32>().unwrap().try_into()?;
244                        let extensions = extensions_str.try_into()?;
245                        Ok(Self { width, extensions })
246                    }
247                }
248            }
249            None => Err(Error::InvalidArch(triple.arch)),
250        }
251    }
252}
253
254#[cfg(test)]
255mod test {
256    #[test]
257    fn test_parse_target() {
258        let target = "riscv32imac-unknown-none-elf";
259        let cargo_flags = "target-feature=+m,-a,+f,+relax";
260        let target = super::RiscvTarget::build(target, cargo_flags).unwrap();
261        let rustc_flags = target.rustc_flags();
262        assert_eq!(rustc_flags, vec!["riscvi", "riscvm", "riscvf", "riscvc"]);
263    }
264}