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/// struct Extended<Reg> {
32///     A(Reg),
33///     B(Reg),
34///     C(Reg),
35///     #[instruction(
36///         if = [OptionalY],
37///         if = [OptionalZ1, OptionalZ2],
38///     )]
39///     D(Reg),
40///     E(Reg),
41/// }
42/// ```
43///
44/// This will generate an enum with both `BaseInstruction` and `Extended` instructions, while also
45/// reordering them according to the specified order. So the eventual enum will look like this:
46/// ```rust,ignore
47/// struct Extended<Reg> {
48///     C(Reg),
49///     Add { rd: Reg, rs1: Reg, rs2: Reg },
50///     // Any other instructions from `BaseInstruction` that were not mentioned explicitly
51///     D(Reg),
52///     A(Reg),
53///     B(Reg),
54/// }
55/// ```
56///
57/// Note that all attribute parameters can be specified multiple times, and reordering can reference
58/// any variant from both the `BaseInstruction` and `Extended` enums.
59///
60/// This, of course, only works when enums have compatible generics.
61///
62/// All instruction enums in the project must have unique names. Individual instructions can be
63/// repeated between inherited enums, but they must have the same exact variant definition and are
64/// assumed to be 100% compatible.
65///
66/// Here is how the attributes are processed:
67/// * first, all own and inherited enum variants are collected into a set
68/// * all reordered instructions are isolated from the rest to make sure they are not ignored
69/// * then each attribute is processed in order of declaration
70///   * `reorder` indicated where the corresponding variant needs to be included
71///   * `ignore` removed individual variants or the whole enum from a set mentioned earlier (but
72///     instructions that are "reordered" anywhere in the definition will remain). Ignored list may
73///     contain any known enum, including those that are not in the list of inherited enums.
74///   * `inherit` includes all remaining variants of the corresponding enum that were not explicitly
75///     reordered or ignored anywhere in the definition
76///   * own variants that were not explicitly reordered or ignored are placed at the end of the enum
77///
78/// `reorder` is a niche feature that physically moves the variants, allowing certain variants to be
79/// next to each other for more efficient code generation (enum discriminant and execution code
80/// locality).
81///
82/// `inherit` is used both for dependencies (`Zve64x` depends on `Zicsr`) and for direct inclusion
83/// during composition (`B` contains `Zba`, `Zbb` and `Zbs`).
84///
85/// `ignore` can be used to create subsets of extensions (`Zmmul` is a multiply-only subset of `M`).
86///
87/// `if` on both enum and variant levels specifies soft optional dependencies on other instructions
88/// or variants when this instruction is inherited further up the chain. Variants are always present
89/// in the enum where they are defined, such that tests can be written against them without extra
90/// effort. In this example above, all instructions require `OptionalX` enum or variant to be
91/// included alongside `Extended` itself, while variant `D` specifically requires either `OptionalY`
92/// or `OptionalZ1` + `OptionalZ2` to be present.
93///
94/// These `if` conditions allow modeling things like `Zcf` part of `C` extension only being
95/// available when `F` extension is also available or `Zcb`'s `c.sext.b` only present when `Zbb`
96/// extension is also available.
97///
98/// # Enum decoding implementation
99///
100/// For enum decoding implementation, the macro is applied to the implementation of `Instruction`
101/// trait and affects its `try_decode()` method:
102/// ```rust,ignore
103/// #[instruction]
104/// impl<Reg> const Instruction for Rv64Instruction<Reg>
105/// where
106///     Reg: [const] Register<Type = u64>,
107/// {
108///     // ...
109/// }
110/// ```
111///
112/// `try_decode()` implementation will end up containing decoding logic for the full extended enum
113/// as mentioned above. The two major restrictions are that `return` is not allowed in the
114/// `try_decode()` method and enum variants must be constructed using `Self::`. The implementation
115/// is quite fragile, so if you're calling internal functions, they might have to be re-exported
116/// since the macro will simply copy-paste the decoding logic as is. Similarly with missing imports,
117/// etc. Compiler should be able to guide you through errors reasonably well.
118///
119/// # Enum display implementation
120///
121/// For enum display implementation, the macro is applied to the implementation of
122/// `core::fmt::Display` trait and affects its `fmt()` method:
123/// ```rust,ignore
124/// #[instruction]
125/// impl<Reg> fmt::Display for Rv64Instruction<Reg>
126/// where
127///     Reg: fmt::Display + Copy,
128/// {
129///     // ...
130/// }
131/// ```
132/// `fmt()` implementation will end up containing decoding logic for the full extended enum as
133/// mentioned above. The three major restrictions are that an enum must be generic over `Reg`
134/// register type, field types must have `Copy` bounds on them (like `Reg` in the example above),
135/// and the method body must consist of a single `match` statement.
136///
137/// # `process_instruction_macros()`
138///
139/// What this macro "does" is impossible to do in Rust macros. So for completeness,
140/// `ab_riscv_macros::process_instruction_macros()` must be called from `build.rs` in a
141/// crate that uses `#[instruction]` macro to generate a bunch of special filed, which the macro
142/// uses to replace the original code with. This is the only way to get the desired ergonomics
143/// withing current constraints of what macros are allowed to do.
144///
145/// # [package.links]
146///
147/// `package` section of `Cargo.toml` must contain `links = "crate-name"` in order for metadata to
148/// be successfully exported to dependent crates.
149#[proc_macro_attribute]
150pub fn instruction(attr: TokenStream, item: TokenStream) -> TokenStream {
151    instruction::instruction(attr.into(), item.into())
152        .unwrap_or_else(|error| error.to_compile_error())
153        .into()
154}
155
156/// Processes `#[instruction_execution]` attribute on enum execution implementation.
157///
158/// It must be applied to implementation of traits `ExecutableInstructionOperands`,
159/// `ExecutableInstructionCsr` and `ExecutableInstruction`, whose definition is already annotated
160/// with `#[instruction]` macro.
161///
162/// Similarly to that macro, this macro will process the contents of trait implementations.
163///
164/// `ExecutableInstructionOperands::get_rs1_rs2_operands()` method will be generated from scratch.
165///
166/// `ExecutableInstruction::execute()`, `ExecutableInstructionCsr::prepare_csr_read()` and
167/// `ExecutableInstructionCsr::prepare_csr_write()` methods will end up containing both inherited
168/// and own execution logic according to the ordering set in `#[instruction]`.
169///
170/// There are constraints on the `ExecutableInstruction::execute()` method body, it must have one or
171/// both (but nothing else) of the following:
172/// * matching in the following style: `match self { Self::Variant { .. } }`
173///   * note that `Self` must be used instead of the explicit type name, such that it works when
174///     inherited
175/// * `Ok(ControlFlow::Continue(Default::default()))` expression.
176///
177/// Also requires `process_instruction_macros()` in `build.rs` to function, see `#[instruction]`
178/// macro documentation.
179///
180/// `ExecutableInstructionCsr::prepare_csr_read()` and
181/// `ExecutableInstructionCsr::prepare_csr_write()` methods can't contain `return` in them,
182/// similarly to instruction decoding implementation processed by `#[instruction]` macro.
183#[proc_macro_attribute]
184pub fn instruction_execution(attr: TokenStream, item: TokenStream) -> TokenStream {
185    instruction_execution::instruction_execution(attr.into(), item.into())
186        .unwrap_or_else(|error| error.to_compile_error())
187        .into()
188}