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