Skip to main content

ab_riscv_macros_impl/
lib.rs

1//! See and use `ab-riscv-macros` crate instead, this is its implementation detail
2
3mod instruction;
4mod instruction_execution;
5
6use proc_macro::TokenStream;
7
8// TODO: Support `conflict` (`Zcmp` conflicts with `Zcd` for example)
9/// Processes `#[instruction]` attribute on both enum definitions and implementations.
10///
11/// # Enum definition
12///
13/// When applied to the enum definition, it will reorder variant fields and expose an enum with
14/// instructions as a dependency.
15///
16/// The fields are reordered as follows: `rs1` > `rs2` > others. This is helpful for
17/// high-performance execution implementation. If `rs1` or `rs2` are not present, they will be added
18/// automatically, and all implementations annotated with `#[instruction]` or
19/// `#[instruction_execution]` will be automatically updated accordingly, but tests and other usages
20/// will have to specify those fields explicitly.
21///
22/// More complex syntax is used when inheriting instructions:
23/// ```rust,ignore
24/// #[instruction(
25///     reorder = [C, Add],
26///     ignore = [E],
27///     inherit = [BaseInstruction],
28///     reorder = [D, A],
29///     if = [OptionalX]
30/// )]
31/// enum Extended<Hart>
32/// where
33///     Hart: HartConfig,
34/// {
35///     A(Hart::Reg),
36///     B(Hart::Reg),
37///     C(Hart::Reg),
38///     #[instruction(
39///         if = [OptionalY],
40///         if = [OptionalZ1, OptionalZ2],
41///     )]
42///     D(Hart::Reg),
43///     E(Hart::Reg),
44/// }
45/// ```
46///
47/// This will generate an enum with both `BaseInstruction` and `Extended` instructions, while also
48/// reordering them according to the specified order. So the eventual enum will look like this:
49/// ```rust,ignore
50/// enum Extended<Hart>
51/// where
52///     Hart: HartConfig,
53/// {
54///     C(Hart::Reg),
55///     Add { rd: Hart::Reg, rs1: Hart::Reg, rs2: Hart::Reg },
56///     // Any other instructions from `BaseInstruction` that were not mentioned explicitly
57///     D(Hart::Reg),
58///     A(Hart::Reg),
59///     B(Hart::Reg),
60/// }
61/// ```
62///
63/// Note that all attribute parameters can be specified multiple times, and reordering can reference
64/// any variant from both the `BaseInstruction` and `Extended` enums.
65///
66/// This, of course, only works when enums have compatible generics: all instruction enums are
67/// generic over hart configuration `Hart` (see `HartConfig`), and implementations have an
68/// additional `Reg` generic parameter for its register type (constrained with
69/// `Hart: HartConfig<Reg = Reg>`).
70///
71/// All instruction enums in the project must have unique names. Individual instructions can be
72/// repeated between inherited enums, but they must have the same exact variant definition and are
73/// assumed to be 100% compatible.
74///
75/// Here is how the attributes are processed:
76/// * first, all own and inherited enum variants are collected into a set
77/// * all reordered instructions are isolated from the rest to make sure they are not ignored
78/// * then each attribute is processed in order of declaration
79///   * `reorder` indicated where the corresponding variant needs to be included
80///   * `ignore` removed individual variants or the whole enum from a set mentioned earlier (but
81///     instructions that are "reordered" anywhere in the definition will remain). Ignored list may
82///     contain any known enum, including those that are not in the list of inherited enums. Ignored
83///     instructions don't satisfy `if` conditions of other instructions.
84///   * `inherit` includes all remaining variants of the corresponding enum that were not explicitly
85///     reordered or ignored anywhere in the definition
86///   * own variants that were not explicitly reordered or ignored are placed at the end of the enum
87///
88/// `reorder` is a niche feature that physically moves the variants, allowing certain variants to be
89/// next to each other for more efficient code generation (enum discriminant and execution code
90/// locality).
91///
92/// `inherit` is used both for dependencies (`Zve64x` depends on `Zicsr`) and for direct inclusion
93/// during composition (`B` contains `Zba`, `Zbb` and `Zbs`).
94///
95/// `ignore` can be used to exclude individual instructions or whole enums from a custom instruction
96/// set (like `ecall` from an instruction set that doesn't support system calls). An enum ignored as
97/// a whole, as well as enums that inherit it, are not implemented extensions of the instruction set
98/// (see `Instruction::IMPLEMENTED_EXTENSIONS`), while ignoring individual instructions doesn't
99/// change that, unless none of the enum's own instructions are left. It should not be used for
100/// modeling relationships between extensions though: an extension that is a subset of another
101/// extension according to the specification is inherited by that extension (`M` inherits `Zmmul`),
102/// while instructions shared by extensions that are not subsets of each other are extracted into a
103/// separate enum, which both extensions inherit (`Zbb` and `Zbkb` both inherit
104/// `Rv64ZbbZbkbSharedInstruction`).
105///
106/// `if` on both enum and variant levels specifies soft optional dependencies on other instructions
107/// or variants when this instruction is inherited further up the chain. Variants are always present
108/// in the enum where they are defined, such that tests can be written against them without extra
109/// effort. In this example above, all instructions require `OptionalX` enum or variant to be
110/// included alongside `Extended` itself, while variant `D` specifically requires either `OptionalY`
111/// or `OptionalZ1` + `OptionalZ2` to be present.
112///
113/// These `if` conditions allow modeling things like `Zcf` part of `C` extension only being
114/// available when `F` extension is also available or `Zcb`'s `c.sext.b` only present when `Zbb`
115/// extension is also available. An enum in `if` condition is satisfied when it is implemented, the
116/// same way as in `Instruction::IMPLEMENTED_EXTENSIONS`.
117///
118/// # Enum decoding implementation
119///
120/// For enum decoding implementation, the macro is applied to the implementation of `Instruction`
121/// trait and affects its `try_decode()` method:
122/// ```rust,ignore
123/// #[instruction]
124/// const impl<Reg, Hart> Instruction for Rv64Instruction<Hart>
125/// where
126///     Reg: [const] Register<Type = u64>,
127///     Hart: [const] HartConfig<Reg = Reg>,
128/// {
129///     // ...
130/// }
131/// ```
132///
133/// `try_decode()` implementation will end up containing decoding logic for the full extended enum
134/// as mentioned above. The two major restrictions are that `return` is not allowed in the
135/// `try_decode()` method and enum variants must be constructed using `Self::`. The implementation
136/// is quite fragile, so if you're calling internal functions, they might have to be re-exported
137/// since the macro will simply copy-paste the decoding logic as is. Similarly with missing imports,
138/// etc. Compiler should be able to guide you through errors reasonably well.
139///
140/// Construction of variants that are not present in the instruction set (like ignored ones) is
141/// replaced with `None?`. This also allows decoding to construct variants of other enums when they
142/// are present, like Zbkb decoding `packw rd, rs1, x0` as Zbb's `zext.h`.
143///
144/// The macro also generates `IMPLEMENTED_EXTENSIONS` constant (see
145/// `Instruction::IMPLEMENTED_EXTENSIONS`), while `OWN_ISA_EXTENSIONS` with ISA extensions of the
146/// enum itself must be specified explicitly, like `Ok(&[IsaExtension::new("m", 2, 0)])` for `M`.
147/// `ImplementedExtension` must be in scope for this to work.
148///
149/// # Enum display implementation
150///
151/// For enum display implementation, the macro is applied to the implementation of
152/// `core::fmt::Display` trait and affects its `fmt()` method:
153/// ```rust,ignore
154/// #[instruction]
155/// impl<Reg, Hart> fmt::Display for Rv64Instruction<Hart>
156/// where
157///     Reg: fmt::Display + Copy,
158///     Hart: HartConfig<Reg = Reg>,
159/// {
160///     // ...
161/// }
162/// ```
163/// `fmt()` implementation will end up containing decoding logic for the full extended enum as
164/// mentioned above. The two major restrictions are that field types must have `Copy` bounds on them
165/// (like `Reg` in the example above), and the method body must consist of a single `match`
166/// statement.
167///
168/// # `process_instruction_macros()`
169///
170/// What this macro "does" is impossible to do in Rust macros. So for completeness,
171/// `ab_riscv_macros::process_instruction_macros()` must be called from `build.rs` in a
172/// crate that uses `#[instruction]` macro to generate a bunch of special filed, which the macro
173/// uses to replace the original code with. This is the only way to get the desired ergonomics
174/// withing current constraints of what macros are allowed to do.
175///
176/// `process_instruction_macros()` finds annotated items anywhere in the crate's Rust files,
177/// including inline modules, and attributes may be path-qualified like
178/// `#[ab_riscv_macros::instruction]`. Attributes must be written in the source code at the start
179/// of a line (indentation is fine), ones produced by other macros (like `cfg_attr`) are not seen.
180///
181/// # [package.links]
182///
183/// `package` section of `Cargo.toml` must contain `links = "crate-name"` in order for metadata to
184/// be successfully exported to dependent crates.
185#[proc_macro_attribute]
186pub fn instruction(attr: TokenStream, item: TokenStream) -> TokenStream {
187    instruction::instruction(attr.into(), item.into())
188        .unwrap_or_else(|error| error.to_compile_error())
189        .into()
190}
191
192/// Processes `#[instruction_execution]` attribute on enum execution implementation.
193///
194/// It must be applied to implementation of traits `ExecutableInstructionOperands`,
195/// `ExecutableInstructionCsr` and `ExecutableInstruction`, whose definition is already annotated
196/// with `#[instruction]` macro.
197///
198/// Similarly to that macro, this macro will process the contents of trait implementations.
199///
200/// `ExecutableInstructionOperands::get_rs1_rs2_operands()` method will be generated from scratch.
201///
202/// `ExecutableInstruction::execute()`, `ExecutableInstructionCsr::prepare_csr_read()` and
203/// `ExecutableInstructionCsr::prepare_csr_write()` methods will end up containing both inherited
204/// and own execution logic according to the ordering set in `#[instruction]`.
205///
206/// There are constraints on the `ExecutableInstruction::execute()` method body, it must have one or
207/// both (but nothing else) of the following:
208/// * matching in the following style: `match self { Self::Variant { .. } }`
209///   * note that `Self` must be used instead of the explicit type name, such that it works when
210///     inherited
211/// * `ExecutionResult::ContinueNoWrite` expression.
212///
213/// The composed `execute()` body is not kept as one large `match`. Instead, each `match` arm
214/// (both own and inherited) is turned into its own `#[inline(always)]` free function, generated
215/// right next to `execute()`, and `execute()` itself is reduced to a lean `match` that dispatches
216/// to those functions (with the impl's generic parameters specified explicitly via turbofish,
217/// since a free function has no `Self` for them to be inferred from). This is purely an
218/// implementation detail; those functions are not meant to be used or referred to directly.
219///
220/// Alongside `execute()`, an implementation of `ThreadedExecutableInstruction` is generated for the
221/// same enum, out of the very same arms. It is done in a somewhat opaque way here with the hopes
222/// that it would become possible to move the `ThreadedExecutableInstruction::execute_threaded()`
223/// method into `ExecutableInstruction` trait at some point. Right now it is not possible to mix
224/// const and non-const methods in the same trait.
225///
226/// If `execute()` carries `#[cfg_attr(feature = "no-panic", no_panic_const::no_panic(..))]`, it
227/// is stripped from `execute()` itself (which becomes a plain dispatcher, so the attribute would
228/// no longer serve its purpose there) and placed on every one of those generated functions
229/// instead, so each variant's execution logic is checked for panics individually. Since this
230/// depends on `execute()` in the implementation currently being processed specifically carrying
231/// this attribute, an implementation that inherits from a lower-level one that has it, but does
232/// not itself repeat it will not get it applied to its own generated functions either.
233///
234/// Also requires `process_instruction_macros()` in `build.rs` to function, see `#[instruction]`
235/// macro documentation.
236///
237/// `ExecutableInstructionCsr::prepare_csr_read()` and
238/// `ExecutableInstructionCsr::prepare_csr_write()` methods can't contain `return` in them,
239/// similarly to instruction decoding implementation processed by `#[instruction]` macro.
240#[proc_macro_attribute]
241pub fn instruction_execution(attr: TokenStream, item: TokenStream) -> TokenStream {
242    instruction_execution::instruction_execution(attr.into(), item.into())
243        .unwrap_or_else(|error| error.to_compile_error())
244        .into()
245}