1mod enum_definition;
2mod enum_impl;
3mod execution_impl;
4mod shared;
5mod state;
6#[cfg(test)]
7mod tests;
8
9use crate::build::enum_definition::{
10 collect_enum_definitions_from_dependencies, process_enum_definition,
11 process_pending_enum_definitions,
12};
13use crate::build::enum_impl::{
14 collect_original_enum_decoding_impls_from_dependencies, process_enum_impl,
15 process_pending_enum_impls,
16};
17use crate::build::execution_impl::{
18 collect_enum_csr_impls_from_dependencies,
19 collect_original_enum_execution_impls_from_dependencies, process_execution_impl,
20 process_pending_enum_execution_impls,
21};
22use crate::build::shared::{
23 INSTRUCTION_ATTRIBUTE, INSTRUCTION_EXECUTION_ATTRIBUTE, is_named_attribute,
24};
25use crate::build::state::State;
26use ab_riscv_macros_common::code_utils::pre_process_rust_code;
27use anyhow::Context;
28use quote::ToTokens;
29use std::path::{Path, PathBuf};
30use std::{env, fs, io, iter};
31use syn::visit::{Visit, visit_item_enum, visit_item_impl};
32use syn::{ItemEnum, ItemImpl};
33
34pub fn process_instruction_macros() -> anyhow::Result<()> {
51 let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").context(
52 "Failed to retrieve `CARGO_MANIFEST_DIR` environment variable, make sure to call \
53 `process_instruction_macros` from `build.rs`",
54 )?;
55 let out_dir = env::var_os("OUT_DIR").context(
56 "Failed to retrieve `OUT_DIR` environment variable, make sure to call \
57 `process_instruction_macros` from `build.rs`",
58 )?;
59 let out_dir = Path::new(&out_dir);
60
61 let mut state = State::new();
62
63 for maybe_enum_definition in collect_enum_definitions_from_dependencies() {
64 let (
65 original_item_enum,
66 item_enum,
67 ignored_instructions,
68 direct_dependencies,
69 dependencies_for_enablement,
70 source,
71 ) = maybe_enum_definition?;
72
73 state.insert_known_enum_definition(
74 original_item_enum,
75 item_enum,
76 ignored_instructions,
77 direct_dependencies,
78 dependencies_for_enablement,
79 source,
80 )?;
81 }
82 for maybe_enum_impl in collect_original_enum_decoding_impls_from_dependencies() {
83 let (item_impl, source) = maybe_enum_impl?;
84 state.insert_known_original_enum_decoding_impl(item_impl, source)?;
85 }
86 for maybe_enum_csr_impl in collect_enum_csr_impls_from_dependencies() {
87 let (item_impl, source) = maybe_enum_csr_impl?;
88 state.insert_known_enum_csr_impl(item_impl, source)?;
89 }
90 for maybe_enum_execution_impl in collect_original_enum_execution_impls_from_dependencies() {
91 let (item_impl, source) = maybe_enum_execution_impl?;
92 state.insert_known_original_enum_execution_impl(item_impl, source)?;
93 }
94
95 for maybe_rust_file in rust_files_in(PathBuf::from(&manifest_dir)) {
96 let rust_file = maybe_rust_file.context("Failed to collect Rust files")?;
97 process_rust_file(&rust_file, out_dir, &mut state)
98 .with_context(|| format!("Failed to process Rust file `{}`", rust_file.display()))?;
99 }
100
101 process_pending_enum_definitions(out_dir, &mut state)?;
102 process_pending_enum_impls(out_dir, &mut state)?;
103 process_pending_enum_execution_impls(out_dir, &mut state)
104}
105
106fn rust_files_in(dir: PathBuf) -> Box<dyn Iterator<Item = io::Result<PathBuf>>> {
107 fn walk(dir: PathBuf) -> Box<dyn Iterator<Item = io::Result<PathBuf>>> {
108 let read_dir = match fs::read_dir(dir) {
109 Ok(iter) => iter,
110 Err(error) => {
111 return Box::new(iter::once(Err(error))) as Box<_>;
112 }
113 };
114
115 Box::new(read_dir.flat_map(move |entry_res| {
116 let entry = match entry_res {
117 Ok(entry) => entry,
118 Err(error) => {
119 return Box::new(iter::once(Err(error))) as Box<_>;
120 }
121 };
122
123 let path = entry.path();
124
125 if path.is_dir() {
126 walk(path)
127 } else if path
128 .extension()
129 .and_then(|ext| ext.to_str())
130 .is_some_and(|ext| ext == "rs")
131 {
132 Box::new(iter::once(Ok(path))) as Box<_>
133 } else {
134 Box::new(iter::empty::<io::Result<PathBuf>>()) as Box<_>
135 }
136 }))
137 }
138
139 walk(dir)
140}
141
142fn may_contain_instruction_macros(file_contents: &str) -> bool {
148 file_contents.lines().any(|line| {
149 let Some(attribute) = line.trim_start().strip_prefix("#[") else {
150 return false;
151 };
152 let path = attribute
154 .split_once(|c: char| {
155 !(c.is_alphanumeric() || c.is_whitespace() || matches!(c, '_' | ':'))
156 })
157 .map_or(attribute, |(path, _)| path);
158 let name = path.rsplit_once("::").map_or(path, |(_, name)| name);
159
160 matches!(
161 name.trim(),
162 INSTRUCTION_ATTRIBUTE | INSTRUCTION_EXECUTION_ATTRIBUTE
163 )
164 })
165}
166
167enum InstructionItem {
169 Enum(ItemEnum),
170 Impl(ItemImpl),
171}
172
173#[derive(Default)]
176struct InstructionItemsCollector {
177 items: Vec<InstructionItem>,
178}
179
180impl Visit<'_> for InstructionItemsCollector {
181 fn visit_item_enum(&mut self, i: &ItemEnum) {
182 if i.attrs
183 .iter()
184 .any(|attribute| is_named_attribute(attribute, INSTRUCTION_ATTRIBUTE))
185 {
186 self.items.push(InstructionItem::Enum(i.clone()));
187 }
188
189 visit_item_enum(self, i);
190 }
191
192 fn visit_item_impl(&mut self, i: &ItemImpl) {
193 if i.attrs.iter().any(|attribute| {
194 is_named_attribute(attribute, INSTRUCTION_ATTRIBUTE)
195 || is_named_attribute(attribute, INSTRUCTION_EXECUTION_ATTRIBUTE)
196 }) {
197 self.items.push(InstructionItem::Impl(i.clone()));
198 }
199
200 visit_item_impl(self, i);
201 }
202}
203
204fn process_rust_file(source: &Path, out_dir: &Path, state: &mut State) -> anyhow::Result<()> {
205 let mut file_contents = fs::read_to_string(source).context("Failed to read Rust file")?;
206 if !may_contain_instruction_macros(&file_contents) {
207 return Ok(());
208 }
209
210 pre_process_rust_code(&mut file_contents);
211
212 let file = syn::parse_file(&file_contents).context("Failed to parse Rust file")?;
213
214 let mut collector = InstructionItemsCollector::default();
215 collector.visit_file(&file);
216
217 for item in collector.items {
218 match item {
219 InstructionItem::Enum(item_enum) => {
220 let enum_name = item_enum.ident.clone();
221 process_enum_definition(item_enum, out_dir, state).with_context(|| {
222 format!(
223 "Failed to process enum `{enum_name}` in file `{}`",
224 source.display()
225 )
226 })?;
227 }
228 InstructionItem::Impl(item_impl) => {
229 let trait_name = item_impl.trait_.as_ref().map(|(path, _)| {
230 path.segments
231 .last()
232 .expect("Path is never empty; qed")
233 .ident
234 .clone()
235 });
236 let type_name = item_impl.self_ty.clone();
237 if let Some(result) = process_enum_impl(item_impl.clone(), out_dir, state) {
238 result.with_context(|| {
239 format!(
240 "Failed to process impl block (`{:?}` for `{}`) in file `{}`",
241 trait_name.to_token_stream(),
242 type_name.to_token_stream(),
243 source.display()
244 )
245 })?;
246 } else if let Some(result) = process_execution_impl(item_impl, out_dir, state) {
247 result.with_context(|| {
248 format!(
249 "Failed to process impl block (`{:?}` for `{}`) in file `{}`",
250 trait_name.to_token_stream(),
251 type_name.to_token_stream(),
252 source.display()
253 )
254 })?;
255 }
256 }
257 }
258 }
259
260 Ok(())
261}