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/// Processes `#[instruction]` attribute on both enum definitions and implementations.
9///
10/// # Enum definition
11///
12/// When applied to the enum definition, it will reorder variant fields and expose an enum with
13/// instructions as a dependency.
14///
15/// The fields are reordered as follows: `rs1` > `rs2` > others. This is helpful for
16/// high-performance execution implementation. If `rs1` or `rs2` are not present, they will be added
17/// automatically, and all implementations annotated with `#[instruction]` or
18/// `#[instruction_execution]` will be automatically updated accordingly, but tests and other usages
19/// will have to specify those fields explicitly.
20///
21/// More complex syntax is used when inheriting instructions:
22/// ```rust,ignore
23/// #[instruction(
24/// reorder = [C, Add],
25/// ignore = [E],
26/// inherit = [BaseInstruction],
27/// reorder = [D, A],
28/// )]
29/// struct Extended<Reg> {
30/// A(Reg),
31/// B(Reg),
32/// C(Reg),
33/// D(Reg),
34/// E(Reg),
35/// }
36/// ```
37///
38/// This will generate an enum with both `BaseInstruction` and `Extended` instructions, while also
39/// reordering them according to the specified order. So the eventual enum will look like this:
40/// ```rust,ignore
41/// struct Extended<Reg> {
42/// C(Reg),
43/// Add { rd: Reg, rs1: Reg, rs2: Reg },
44/// // Any other instructions from `BaseInstruction` that were not mentioned explicitly
45/// D(Reg),
46/// A(Reg),
47/// B(Reg),
48/// }
49/// ```
50///
51/// Note that both `reorder`, `ignore` and `inherit` attributes can be specified multiple times, and
52/// reordering can reference any variant from both the `BaseInstruction` and `Extended` enums.
53///
54/// This, of course, only works when enums have compatible generics.
55///
56/// All instruction enums in the project must have unique names. Individual instructions can be
57/// repeated between inherited enums, but they must have the same exact variant definition and are
58/// assumed to be 100% compatible.
59///
60/// Here is how the attributes are processed:
61/// * first, all own and inherited enum variants are collected into a set
62/// * all reordered instructions are isolated from the rest to make sure they are not ignored
63/// * then each attribute is processed in order of declaration
64/// * `reorder` indicated where the corresponding variant needs to be included
65/// * `ignore` removed individual variants or the whole enum from a set mentioned earlier (but
66/// instructions that are "reordered" anywhere in the definition will remain). Ignored list may
67/// contain any known enum, including those that are not in the list of inherited enums.
68/// * `inherit` includes all remaining variants of the corresponding enum that were not explicitly
69/// reordered or ignored anywhere in the definition
70/// * own variants that were not explicitly reordered or ignored are placed at the end of the enum
71///
72/// # Enum decoding implementation
73///
74/// For enum decoding implementation, the macro is applied to the implementation of `Instruction`
75/// trait and affects its `try_decode()` method:
76/// ```rust,ignore
77/// #[instruction]
78/// impl<Reg> const Instruction for Rv64Instruction<Reg>
79/// where
80/// Reg: [const] Register<Type = u64>,
81/// {
82/// // ...
83/// }
84/// ```
85///
86/// `try_decode()` implementation will end up containing decoding logic for the full extended enum
87/// as mentioned above. The two major restrictions are that `return` is not allowed in the
88/// `try_decode()` method and enum variants must be constructed using `Self::`. The implementation
89/// is quite fragile, so if you're calling internal functions, they might have to be re-exported
90/// since the macro will simply copy-paste the decoding logic as is. Similarly with missing imports,
91/// etc. Compiler should be able to guide you through errors reasonably well.
92///
93/// # Enum display implementation
94///
95/// For enum display implementation, the macro is applied to the implementation of
96/// `core::fmt::Display` trait and affects its `fmt()` method:
97/// ```rust,ignore
98/// #[instruction]
99/// impl<Reg> fmt::Display for Rv64Instruction<Reg>
100/// where
101/// Reg: fmt::Display + Copy,
102/// {
103/// // ...
104/// }
105/// ```
106/// `fmt()` implementation will end up containing decoding logic for the full extended enum as
107/// mentioned above. The three major restrictions are that an enum must be generic over `Reg`
108/// register type, field types must have `Copy` bounds on them (like `Reg` in the example above),
109/// and the method body must consist of a single `match` statement.
110///
111/// # `process_instruction_macros()`
112///
113/// What this macro "does" is impossible to do in Rust macros. So for completeness,
114/// `ab_riscv_macros::process_instruction_macros()` must be called from `build.rs` in a
115/// crate that uses `#[instruction]` macro to generate a bunch of special filed, which the macro
116/// uses to replace the original code with. This is the only way to get the desired ergonomics
117/// withing current constraints of what macros are allowed to do.
118///
119/// # [package.links]
120///
121/// `package` section of `Cargo.toml` must contain `links = "crate-name"` in order for metadata to
122/// be successfully exported to dependent crates.
123#[proc_macro_attribute]
124pub fn instruction(attr: TokenStream, item: TokenStream) -> TokenStream {
125 instruction::instruction(attr.into(), item.into())
126 .unwrap_or_else(|error| error.to_compile_error())
127 .into()
128}
129
130/// Processes `#[instruction_execution]` attribute on enum execution implementation.
131///
132/// It must be applied to implementation of traits `ExecutableInstructionOperands`,
133/// `ExecutableInstructionCsr` and `ExecutableInstruction`, whose definition is already annotated
134/// with `#[instruction]` macro.
135///
136/// Similarly to that macro, this macro will process the contents of trait implementations.
137///
138/// `ExecutableInstructionOperands::get_rs1_rs2_operands()` method will be generated from scratch.
139///
140/// `ExecutableInstruction::execute()`, `ExecutableInstructionCsr::prepare_csr_read()` and
141/// `ExecutableInstructionCsr::prepare_csr_write()` methods will end up containing both inherited
142/// and own execution logic according to the ordering set in `#[instruction]`.
143///
144/// There are constraints on the `ExecutableInstruction::execute()` method body, it must have one or
145/// both (but nothing else) of the following:
146/// * matching in the following style: `match self { Self::Variant { .. } }`
147/// * note that `Self` must be used instead of the explicit type name, such that it works when
148/// inherited
149/// * `Ok(ControlFlow::Continue(Default::default()))` expression.
150///
151/// Also requires `process_instruction_macros()` in `build.rs` to function, see `#[instruction]`
152/// macro documentation.
153///
154/// `ExecutableInstructionCsr::prepare_csr_read()` and
155/// `ExecutableInstructionCsr::prepare_csr_write()` methods can't contain `return` in them,
156/// similarly to instruction decoding implementation processed by `#[instruction]` macro.
157#[proc_macro_attribute]
158pub fn instruction_execution(attr: TokenStream, item: TokenStream) -> TokenStream {
159 instruction_execution::instruction_execution(attr.into(), item.into())
160 .unwrap_or_else(|error| error.to_compile_error())
161 .into()
162}