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}