Skip to main content

ab_riscv_primitives/
instructions.rs

1//! This module defines the RISC-V instruction set instructions
2
3mod isa;
4pub mod rv32;
5pub mod rv64;
6#[cfg(test)]
7mod test_utils;
8#[cfg(test)]
9mod tests;
10pub mod utils;
11pub mod v;
12pub mod zawrs;
13pub mod zicond;
14pub mod zicsr;
15pub mod zifencei;
16pub mod zkr;
17pub mod zvbb;
18pub mod zvbc;
19
20use crate::hart::HartConfig;
21use crate::instructions::isa::isa_string;
22use crate::registers::general_purpose::Register;
23use core::any::TypeId;
24use core::cmp::Ordering;
25use core::fmt;
26use core::marker::Destruct;
27
28/// Canonical order of single-letter extensions, which is also used to order multi-letter `Z`
29/// extensions by their second letter, see "ISA Extension Naming Conventions" chapter of the
30/// RISC-V unprivileged specification
31const CANONICAL_EXTENSION_ORDER: &[u8] = b"iemafdqlcbkjtpvh";
32
33/// Key for sorting extension names in canonical order: single-letter extensions, then `Z`
34/// extensions ordered by their category (second letter), then `S` extensions and everything else,
35/// extensions with the same key are sorted by name
36const fn canonical_key(name: &[u8]) -> (u8, usize) {
37    match *name {
38        [letter] => (0, category(letter)),
39        [b'z', letter, ..] => (1, category(letter)),
40        [b's', ..] => (2, 0),
41        _ => (3, 0),
42    }
43}
44
45/// Position of a letter in canonical order, letters that are not in the list go after all others
46const fn category(letter: u8) -> usize {
47    let mut index = 0;
48    while index < CANONICAL_EXTENSION_ORDER.len() {
49        if CANONICAL_EXTENSION_ORDER[index] == letter {
50            return index;
51        }
52        index += 1;
53    }
54
55    CANONICAL_EXTENSION_ORDER.len()
56}
57
58/// Implemented extension of an instruction set, see [`Instruction::IMPLEMENTED_EXTENSIONS`]
59#[derive(Debug, Clone, Copy)]
60pub struct ImplementedExtension {
61    /// Type ID of the instruction enum
62    pub mut(self) type_id: TypeId,
63    /// ISA extensions of the instruction enum, see [`Instruction::OWN_ISA_EXTENSIONS`]
64    pub mut(self) own_isa_extensions: &'static [IsaExtension],
65}
66
67impl ImplementedExtension {
68    /// Create a new instance for an instruction enum
69    #[inline(always)]
70    pub const fn new<I>() -> Self
71    where
72        I: Instruction,
73    {
74        Self {
75            type_id: TypeId::of::<I>(),
76            own_isa_extensions: I::OWN_ISA_EXTENSIONS,
77        }
78    }
79}
80
81/// ISA extension with its version.
82///
83/// Extensions are ordered according to the canonical order of their names (see "ISA Extension
84/// Naming Conventions" chapter of the RISC-V unprivileged specification) and then by version.
85#[derive(Debug, Clone, Copy)]
86#[derive_const(PartialEq, Eq)]
87pub struct IsaExtension {
88    /// Name of the extension as used in ISA strings, for example, `zcmp`
89    pub name: &'static str,
90    /// Major version of the extension
91    pub major_version: u8,
92    /// Minor version of the extension
93    pub minor_version: u8,
94}
95
96const impl PartialOrd for IsaExtension {
97    #[inline(always)]
98    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99        Some(self.cmp(other))
100    }
101}
102
103const impl Ord for IsaExtension {
104    #[inline]
105    fn cmp(&self, other: &Self) -> Ordering {
106        canonical_key(self.name.as_bytes())
107            .cmp(&canonical_key(other.name.as_bytes()))
108            .then(self.name.as_bytes().cmp(other.name.as_bytes()))
109            .then(self.major_version.cmp(&other.major_version))
110            .then(self.minor_version.cmp(&other.minor_version))
111    }
112}
113
114impl IsaExtension {
115    /// Create a new instance
116    #[inline(always)]
117    pub const fn new(name: &'static str, major_version: u8, minor_version: u8) -> Self {
118        Self {
119            name,
120            major_version,
121            minor_version,
122        }
123    }
124}
125
126/// Generic instruction
127pub const trait Instruction:
128    fmt::Display + fmt::Debug + [const] Destruct + Copy + Send + Sync + Sized + 'static
129{
130    /// All implemented extensions, including this instruction itself, with their ISA extensions.
131    ///
132    /// An inherited extension is implemented when at least one of its own instructions is present
133    /// in the instruction set (otherwise it was ignored in one way or another) and the same is true
134    /// for all extensions it inherits. Extensions without own instructions (like `B`) are
135    /// implemented when all extensions they inherit are. So individual instructions ignored by name
136    /// (like `ecall` in an instruction set without system calls) or missing due to their conditions
137    /// (like `amocas.b` from `Zabha` without `Zacas`) don't exclude the extension.
138    ///
139    /// Note that this constant is auto-generated by the macro and usually doesn't need to be
140    /// specified explicitly (and if done, will cause conflicts at compile time).
141    const IMPLEMENTED_EXTENSIONS: &'static [ImplementedExtension];
142
143    /// ISA extensions defined by this instruction enum itself, without inherited ones.
144    ///
145    /// Empty for instruction enums that are not extensions on their own, like a collection of
146    /// instructions shared by multiple extensions. Can also contain extensions implied by this
147    /// instruction enum that are not modeled as instruction enums, like `zvl*b` for vector
148    /// extensions.
149    ///
150    /// Unsupported hart configuration results in a compilation error when this constant is used.
151    const OWN_ISA_EXTENSIONS: &'static [IsaExtension];
152
153    /// ISA string as used in the `Tag_RISCV_arch` attribute of ELF files, for example,
154    /// `rv64i2p1_m2p0_zmmul1p0`.
155    ///
156    /// Contains ISA extensions of all implemented extensions (see [`Self::IMPLEMENTED_EXTENSIONS`])
157    /// in canonical order without duplicates. Only an instruction set with a base ISA (`i` or `e`)
158    /// has the `rv32`/`rv64` prefix, for others it only lists extensions, like `m2p0_zmmul1p0`.
159    ///
160    /// Note that this constant is not supposed to be specified explicitly.
161    // TODO: Make it `final` once supported:
162    //  https://internals.rust-lang.org/t/final-associated-constants/24655?u=nazar-pc
163    const ISA_STRING: &'static str = const {
164        isa_string(
165            <Self::Hart as HartConfig>::Reg::XLEN,
166            Self::IMPLEMENTED_EXTENSIONS,
167        )
168    }
169    .unsize()
170    .as_str();
171
172    /// Instruction alignment in bytes, also known as `IALIGN`
173    const ALIGNMENT: u8;
174
175    /// Hart configuration of the implementation
176    type Hart: [const] HartConfig;
177
178    /// Try to decode a single valid instruction
179    fn try_decode(instruction: u32) -> Option<Self>;
180
181    /// Instruction size in bytes
182    fn size(&self) -> u8;
183
184    /// Checks whether this instruction implements the given extension, see
185    /// [`Self::IMPLEMENTED_EXTENSIONS`] for details
186    #[inline(always)]
187    fn implements_extension<E>() -> bool
188    where
189        E: Instruction<Hart = Self::Hart>,
190    {
191        const {
192            let mut implemented_extensions = Self::IMPLEMENTED_EXTENSIONS;
193            let mut result = false;
194            while let [implemented_extension, rest @ ..] = implemented_extensions {
195                implemented_extensions = rest;
196                if implemented_extension.type_id == TypeId::of::<E>() {
197                    result = true;
198                }
199            }
200
201            result
202        }
203    }
204}