Skip to main content

disc_v/
decoder.rs

1// Copyright (c) 2026 Christopher Swenson
2// Licensed under the Apache-2.0 license.
3
4//! A decoder for a chosen base ISA and set of extensions.
5
6use core::fmt;
7
8use crate::{Disassembler, Extension, Extensions, Instruction, Isa, inst_length};
9
10/// Pairs of extensions that give different meanings to the same encodings,
11/// and so cannot be decoded together.
12const CONFLICTS: &[(Extension, Extension)] = &[
13    // Zcmp and Zcmt use the encodings of c.fsdsp.
14    (Extension::Zcmp, Extension::Zcd),
15    (Extension::Zcmt, Extension::Zcd),
16    // Zclsd uses the encodings of c.flw, c.fsw, c.flwsp and c.fswsp.
17    (Extension::Zclsd, Extension::Zcf),
18    // Zfinx (and so Zdinx, Zhinx and Zhinxmin) uses F's encodings with
19    // integer registers.
20    (Extension::Zfinx, Extension::F),
21];
22
23/// Decodes instructions for a base ISA with a chosen set of extensions.
24///
25/// [`Decoder::new`] enables every supported extension that does not
26/// conflict with another ([`Extensions::DEFAULT`]), as the free functions
27/// such as [`decode`](fn@crate::decode) do. Presets such as
28/// [`Decoder::RVA23U64`], [`Decoder::with_only`] and
29/// [`Decoder::from_march`] choose the extensions of a particular target, so
30/// that instructions from other extensions are shown as `.insn` (or without
31/// aliases, such as `lpad`, that need them).
32///
33/// ```
34/// use disc_v::{Decoder, Extension, Isa};
35///
36/// // czero.eqz a0,a1,a2
37/// assert_eq!(Decoder::new(Isa::Rv64).decode(0, 0x0ec5d533).to_string(), "czero.eqz a0,a1,a2");
38/// assert_eq!(Decoder::RV64GC.decode(0, 0x0ec5d533).to_string(), ".insn 4, 0x0ec5d533");
39///
40/// let dec = Decoder::RV64GC.with(Extension::Zicond).unwrap();
41/// assert_eq!(dec.decode(0, 0x0ec5d533).to_string(), "czero.eqz a0,a1,a2");
42///
43/// let dec = Decoder::from_march("rv64gc_zicond").unwrap();
44/// assert_eq!(dec, Decoder::RV64GC.with(Extension::Zicond).unwrap());
45/// ```
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47pub struct Decoder {
48    isa: Isa,
49    extensions: Extensions,
50}
51
52impl Decoder {
53    /// RV32GC: RV32I with IMAFD, Zicsr, Zifencei and C.
54    pub const RV32GC: Decoder = Decoder::unchecked(Isa::Rv32, Extensions::GC);
55
56    /// RV64GC: RV64I with IMAFD, Zicsr, Zifencei and C.
57    pub const RV64GC: Decoder = Decoder::unchecked(Isa::Rv64, Extensions::GC);
58
59    /// The RVA23U64 profile, for user-mode code.
60    pub const RVA23U64: Decoder = Decoder::unchecked(Isa::Rv64, Extensions::RVA23U64);
61
62    /// The RVA23S64 profile, which adds the supervisor-mode and hypervisor
63    /// instructions to RVA23U64.
64    pub const RVA23S64: Decoder = Decoder::unchecked(Isa::Rv64, Extensions::RVA23S64);
65
66    /// A decoder for `isa` with every supported extension that does not
67    /// conflict with another ([`Extensions::DEFAULT`]).
68    pub const fn new(isa: Isa) -> Decoder {
69        Decoder::unchecked(isa, Extensions::DEFAULT)
70    }
71
72    const fn unchecked(isa: Isa, extensions: Extensions) -> Decoder {
73        Decoder { isa, extensions }
74    }
75
76    /// A decoder for `isa` with exactly `extensions` (and the extensions
77    /// they imply), or an error if two of them conflict.
78    ///
79    /// ```
80    /// use disc_v::{Decoder, Extension, Isa};
81    ///
82    /// let dec = Decoder::with_only(Isa::Rv32, [Extension::M, Extension::C]).unwrap();
83    /// assert_eq!(dec.decode(0, 0x02b50533).to_string(), "mul a0,a0,a1");
84    /// assert_eq!(dec.decode(0, 0x00b57553).to_string(), ".insn 4, 0x00b57553"); // fadd.s
85    /// ```
86    pub fn with_only(isa: Isa, extensions: impl Into<Extensions>) -> Result<Decoder, Conflict> {
87        let extensions = extensions.into();
88        match find_conflict(extensions) {
89            Some(conflict) => Err(conflict),
90            None => Ok(Decoder { isa, extensions }),
91        }
92    }
93
94    /// The decoder with `ext` (and the extensions it implies) enabled, or an
95    /// error if it conflicts with an extension already enabled.
96    pub fn with(self, ext: Extension) -> Result<Decoder, Conflict> {
97        Decoder::with_only(self.isa, self.extensions.with(ext))
98    }
99
100    /// The decoder with `ext` (and the extensions it implies) enabled, or
101    /// the decoder unchanged if `ext` conflicts with an extension already
102    /// enabled.
103    pub fn try_with(self, ext: Extension) -> Decoder {
104        self.with(ext).unwrap_or(self)
105    }
106
107    /// The decoder with `ext` (and the extensions that imply it) disabled.
108    pub fn without(self, ext: Extension) -> Decoder {
109        Decoder {
110            extensions: self.extensions.without(ext),
111            ..self
112        }
113    }
114
115    /// The base ISA.
116    pub fn isa(&self) -> Isa {
117        self.isa
118    }
119
120    /// The enabled extensions.
121    pub fn extensions(&self) -> Extensions {
122        self.extensions
123    }
124
125    /// Decodes the instruction `inst`, located at address `pc`. See
126    /// [`decode`](fn@crate::decode).
127    pub fn decode(&self, pc: u64, inst: u64) -> Instruction {
128        crate::decode::decode(self.isa, self.extensions, pc, inst)
129    }
130
131    /// Decodes the instruction at the start of `bytes`, located at address
132    /// `pc`. See [`decode_bytes`](crate::decode_bytes).
133    pub fn decode_bytes(&self, pc: u64, bytes: &[u8]) -> Option<Instruction> {
134        let first = u16::from_le_bytes([*bytes.first()?, *bytes.get(1)?]);
135        let len = inst_length(first).unwrap_or(2);
136        let mut word = [0; 8];
137        word[..len].copy_from_slice(bytes.get(..len)?);
138        Some(self.decode(pc, u64::from_le_bytes(word)))
139    }
140
141    /// Disassembles `bytes`, the first of which is at address `pc`. See
142    /// [`disassemble`](crate::disassemble).
143    pub fn disassemble<'a>(&self, pc: u64, bytes: &'a [u8]) -> Disassembler<'a> {
144        Disassembler {
145            decoder: *self,
146            pc,
147            bytes,
148        }
149    }
150
151    /// A decoder for an ISA string, as passed to compilers with `-march`,
152    /// such as `"rv64gc"` or `"rv32imac_zicsr_zba_zbb"`.
153    ///
154    /// Version numbers (`rv64i2p1`) are ignored, as are extensions that
155    /// define no instructions (such as Zkt, Sstc or Zvl128b). Some
156    /// extensions are decoded as a larger extension that contains them:
157    /// Zmmul as M, Zaamo and Zalrsc as A, the Zve embedded vector extensions
158    /// as V, and Sha as H. The Zce, Zk, Zkn and Zks bundles are
159    /// expanded.
160    ///
161    /// Unknown supervisor- and machine-level extensions (names starting
162    /// with `s`) are also ignored, since nearly all of them define no
163    /// instructions, while unknown `z` and `x` extensions are errors.
164    ///
165    /// ```
166    /// use disc_v::{Decoder, MarchError};
167    ///
168    /// assert_eq!(Decoder::from_march("rv64gc"), Ok(Decoder::RV64GC));
169    /// assert_eq!(
170    ///     Decoder::from_march("rv64gc_xtheadba"),
171    ///     Err(MarchError::UnsupportedExtension("xtheadba")),
172    /// );
173    /// ```
174    pub fn from_march(march: &str) -> Result<Decoder, MarchError<'_>> {
175        let (isa, rest) = [
176            ("rv32", Isa::Rv32),
177            ("rv64", Isa::Rv64),
178            ("rv128", Isa::Rv128),
179        ]
180        .into_iter()
181        .find_map(|(prefix, isa)| {
182            let (head, rest) = march.split_at_checked(prefix.len())?;
183            head.eq_ignore_ascii_case(prefix).then_some((isa, rest))
184        })
185        .ok_or(MarchError::InvalidBase)?;
186        let mut extensions = Extensions::EMPTY;
187        // Not split('_'): its searcher has panic paths that the optimizer
188        // does not always remove (see panic-check/).
189        for (i, token) in rest.split(['_']).enumerate() {
190            let first = token.chars().next().map(|c| c.to_ascii_lowercase());
191            match first {
192                Some('z' | 's' | 'x') if i > 0 => {
193                    extensions = extensions.union(multi_letter(token)?);
194                }
195                _ => {
196                    if i == 0 && !matches!(first, Some('i' | 'g' | 'e')) {
197                        return Err(MarchError::InvalidBase);
198                    }
199                    extensions = extensions.union(single_letters(token)?);
200                }
201            }
202        }
203        Decoder::with_only(isa, extensions).map_err(MarchError::Conflict)
204    }
205}
206
207/// The extensions of a run of single-letter extensions, such as `imafdc`.
208fn single_letters(token: &str) -> Result<Extensions, MarchError<'_>> {
209    use Extension::*;
210    let mut extensions = Extensions::EMPTY;
211    let mut rest = token;
212    while let Some(c) = rest.chars().next() {
213        let Some((name, after)) = rest.split_at_checked(c.len_utf8()) else {
214            break;
215        };
216        rest = strip_version(after);
217        extensions = extensions.union(match c.to_ascii_lowercase() {
218            'i' => Extensions::EMPTY,
219            // The RV32E and RV64E bases.
220            'e' => E.into(),
221            'g' => Extensions::of(&[M, A, F, D, Zicsr, Zifencei]),
222            'b' => Extensions::of(&[Zba, Zbb, Zbs]),
223            c => match Extension::from_name(name) {
224                Some(ext) if c.is_ascii_alphabetic() => ext.into(),
225                _ => return Err(MarchError::UnsupportedExtension(name)),
226            },
227        });
228    }
229    Ok(extensions)
230}
231
232/// The extensions of a multi-letter extension such as `zba2p0`.
233fn multi_letter(token: &str) -> Result<Extensions, MarchError<'_>> {
234    use Extension::*;
235    let name = token.strip_suffix(trailing_version(token)).unwrap_or(token);
236    if let Some(ext) = Extension::from_name(name) {
237        return Ok(ext.into());
238    }
239    let lower = |s: &str| name.eq_ignore_ascii_case(s);
240    let has_prefix = |p: &str| {
241        name.get(..p.len())
242            .is_some_and(|h| h.eq_ignore_ascii_case(p))
243    };
244    Ok(if lower("zmmul") {
245        M.into()
246    } else if lower("zaamo") || lower("zalrsc") {
247        A.into()
248    } else if lower("zce") {
249        Extensions::of(&[Zca, Zcb, Zcmp, Zcmt])
250    } else if has_prefix("zve") {
251        V.into()
252    } else if lower("zkn") {
253        Extensions::of(&[Zbkb, Zbkc, Zbkx, Zkne, Zknd, Zknh])
254    } else if lower("zks") {
255        Extensions::of(&[Zbkb, Zbkc, Zbkx, Zksed, Zksh])
256    } else if lower("zk") {
257        Extensions::of(&[Zbkb, Zbkc, Zbkx, Zkne, Zknd, Zknh])
258    } else if lower("sha") {
259        H.into()
260    } else if lower("smctr") {
261        Ssctr.into()
262    } else if has_prefix("zvl") || NO_INSTRUCTIONS.iter().any(|n| lower(n)) {
263        Extensions::EMPTY
264    } else if has_prefix("s") {
265        // Other supervisor- and machine-level extensions define no
266        // instructions.
267        Extensions::EMPTY
268    } else {
269        return Err(MarchError::UnsupportedExtension(name));
270    })
271}
272
273/// Unprivileged extensions that define no instructions.
274const NO_INSTRUCTIONS: &[&str] = &[
275    "zicntr", "zihpm", "zkt", "zvkt", "zvfh", "zvfhmin", "ztso", "za64rs", "za128rs", "zama16b",
276    "zic64b", "ziccamoa", "ziccif", "zicclsm", "ziccrse", "supm", "zkr",
277];
278
279/// `s` without the version number at its start, such as `2p1` in `2p1m`.
280fn strip_version(s: &str) -> &str {
281    s.strip_prefix(leading_version(s)).unwrap_or(s)
282}
283
284/// The version number at the start of `s`, in a run of single-letter
285/// extensions: digits, optionally followed by `p` and more digits.
286fn leading_version(s: &str) -> &str {
287    fn skip_digits(s: &str) -> &str {
288        s.trim_start_matches(|c: char| c.is_ascii_digit())
289    }
290    let after_major = skip_digits(s);
291    if after_major.len() == s.len() {
292        return "";
293    }
294    let after = match after_major.strip_prefix(['p', 'P']) {
295        Some(minor) if minor.starts_with(|c: char| c.is_ascii_digit()) => skip_digits(minor),
296        _ => after_major,
297    };
298    s.strip_suffix(after).unwrap_or("")
299}
300
301/// The version number at the end of a multi-letter extension name, such as
302/// `1p0` in `zba1p0`.
303fn trailing_version(s: &str) -> &str {
304    fn skip_digits(s: &str) -> &str {
305        s.trim_end_matches(|c: char| c.is_ascii_digit())
306    }
307    let before_minor = skip_digits(s);
308    if before_minor.len() == s.len() {
309        return "";
310    }
311    let before = match before_minor.strip_suffix(['p', 'P']) {
312        Some(major) if major.ends_with(|c: char| c.is_ascii_digit()) => skip_digits(major),
313        _ => before_minor,
314    };
315    s.strip_prefix(before).unwrap_or("")
316}
317
318/// Finds a pair of conflicting extensions in `extensions`.
319fn find_conflict(extensions: Extensions) -> Option<Conflict> {
320    CONFLICTS
321        .iter()
322        .find(|(a, b)| extensions.contains(*a) && extensions.contains(*b))
323        .map(|&(extension, conflicts_with)| Conflict {
324            extension,
325            conflicts_with,
326        })
327}
328
329/// Two extensions that give different meanings to the same encodings, and
330/// so cannot be decoded together.
331#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
332pub struct Conflict {
333    extension: Extension,
334    conflicts_with: Extension,
335}
336
337impl Conflict {
338    /// One of the conflicting extensions.
339    pub fn extension(&self) -> Extension {
340        self.extension
341    }
342
343    /// The extension it conflicts with.
344    pub fn conflicts_with(&self) -> Extension {
345        self.conflicts_with
346    }
347}
348
349impl fmt::Display for Conflict {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        write!(
352            f,
353            "{} conflicts with {}",
354            self.extension, self.conflicts_with
355        )
356    }
357}
358
359impl core::error::Error for Conflict {}
360
361/// An error parsing an ISA string with [`Decoder::from_march`].
362#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
363#[non_exhaustive]
364pub enum MarchError<'a> {
365    /// The string does not start with `rv32`, `rv64` or `rv128` followed by
366    /// `i`, `e` or `g`.
367    InvalidBase,
368    /// The string names an extension that disc-v does not support.
369    UnsupportedExtension(&'a str),
370    /// The string names two extensions that conflict.
371    Conflict(Conflict),
372}
373
374impl fmt::Display for MarchError<'_> {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            MarchError::InvalidBase => {
378                f.write_str("ISA string does not start with rv32, rv64 or rv128 and i, e or g")
379            }
380            MarchError::UnsupportedExtension(name) => write!(f, "unsupported extension {name}"),
381            MarchError::Conflict(conflict) => conflict.fmt(f),
382        }
383    }
384}
385
386impl core::error::Error for MarchError<'_> {}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn version_suffixes() {
394        assert_eq!(leading_version("2p1m"), "2p1");
395        assert_eq!(leading_version("2m"), "2");
396        assert_eq!(leading_version("2pm"), "2");
397        assert_eq!(leading_version("mc2"), "");
398        assert_eq!(strip_version("2p1mc"), "mc");
399        assert_eq!(strip_version("mc2"), "mc2");
400        assert_eq!(trailing_version("zba1p0"), "1p0");
401        assert_eq!(trailing_version("zba1"), "1");
402        assert_eq!(trailing_version("zba"), "");
403        assert_eq!(trailing_version("zvl128b"), "");
404    }
405}