Skip to main content

ab_riscv_macros/
build.rs

1mod enum_definition;
2mod enum_impl;
3mod execution_impl;
4mod shared;
5mod state;
6
7use crate::build::enum_definition::{
8    collect_enum_definitions_from_dependencies, process_enum_definition,
9    process_pending_enum_definitions,
10};
11use crate::build::enum_impl::{
12    collect_original_enum_decoding_impls_from_dependencies, process_enum_impl,
13    process_pending_enum_impls,
14};
15use crate::build::execution_impl::{
16    collect_enum_csr_impls_from_dependencies,
17    collect_original_enum_execution_impls_from_dependencies, process_execution_impl,
18    process_pending_enum_execution_impls,
19};
20use crate::build::state::State;
21use ab_riscv_macros_common::code_utils::pre_process_rust_code;
22use anyhow::Context;
23use quote::ToTokens;
24use std::path::{Path, PathBuf};
25use std::{env, fs, io, iter};
26use syn::Item;
27
28/// Processes all instruction macros in the crate when called from `build.rs`
29pub fn process_instruction_macros() -> anyhow::Result<()> {
30    let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").context(
31        "Failed to retrieve `CARGO_MANIFEST_DIR` environment variable, make sure to call \
32        `process_instruction_macros` from `build.rs`",
33    )?;
34    let out_dir = env::var_os("OUT_DIR").context(
35        "Failed to retrieve `OUT_DIR` environment variable, make sure to call \
36        `process_instruction_macros` from `build.rs`",
37    )?;
38    let out_dir = Path::new(&out_dir);
39
40    let mut state = State::new();
41
42    for maybe_enum_definition in collect_enum_definitions_from_dependencies() {
43        let (
44            original_item_enum,
45            item_enum,
46            ignored_instructions,
47            direct_dependencies,
48            dependencies_for_enablement,
49            source,
50        ) = maybe_enum_definition?;
51
52        state.insert_known_enum_definition(
53            original_item_enum,
54            item_enum,
55            ignored_instructions,
56            direct_dependencies,
57            dependencies_for_enablement,
58            source,
59        )?;
60    }
61    for maybe_enum_impl in collect_original_enum_decoding_impls_from_dependencies() {
62        let (item_impl, source) = maybe_enum_impl?;
63        state.insert_known_original_enum_decoding_impl(item_impl, source)?;
64    }
65    for maybe_enum_csr_impl in collect_enum_csr_impls_from_dependencies() {
66        let (item_impl, source) = maybe_enum_csr_impl?;
67        state.insert_known_enum_csr_impl(item_impl, source)?;
68    }
69    for maybe_enum_execution_impl in collect_original_enum_execution_impls_from_dependencies() {
70        let (item_impl, source) = maybe_enum_execution_impl?;
71        state.insert_known_original_enum_execution_impl(item_impl, source)?;
72    }
73
74    for maybe_rust_file in rust_files_in(Path::new(&manifest_dir).join("src")) {
75        let rust_file = maybe_rust_file.context("Failed to collect Rust files")?;
76        process_rust_file(&rust_file, out_dir, &mut state)
77            .with_context(|| format!("Failed to process Rust file `{}`", rust_file.display()))?;
78    }
79
80    process_pending_enum_definitions(out_dir, &mut state)?;
81    process_pending_enum_impls(out_dir, &mut state)?;
82    process_pending_enum_execution_impls(out_dir, &mut state)
83}
84
85fn rust_files_in(dir: PathBuf) -> Box<dyn Iterator<Item = io::Result<PathBuf>>> {
86    fn walk(dir: PathBuf) -> Box<dyn Iterator<Item = io::Result<PathBuf>>> {
87        let read_dir = match fs::read_dir(dir) {
88            Ok(iter) => iter,
89            Err(error) => {
90                return Box::new(iter::once(Err(error))) as Box<_>;
91            }
92        };
93
94        Box::new(read_dir.flat_map(move |entry_res| {
95            let entry = match entry_res {
96                Ok(entry) => entry,
97                Err(error) => {
98                    return Box::new(iter::once(Err(error))) as Box<_>;
99                }
100            };
101
102            let path = entry.path();
103
104            if path.is_dir() {
105                walk(path)
106            } else if path
107                .extension()
108                .and_then(|ext| ext.to_str())
109                .is_some_and(|ext| ext == "rs")
110            {
111                Box::new(iter::once(Ok(path))) as Box<_>
112            } else {
113                Box::new(iter::empty::<io::Result<PathBuf>>()) as Box<_>
114            }
115        }))
116    }
117
118    walk(dir)
119}
120
121fn process_rust_file(source: &Path, out_dir: &Path, state: &mut State) -> anyhow::Result<()> {
122    let mut file_contents = fs::read_to_string(source).context("Failed to read Rust file")?;
123    if !file_contents.contains("\n#[instruction") {
124        // Quickly skip files without instruction macro calls. This helps to ignore the files that
125        // may use Rust nightly syntax features not supported by `syn`, which is limited to stable
126        // Rust.
127        return Ok(());
128    }
129
130    pre_process_rust_code(&mut file_contents);
131
132    let file = syn::parse_file(&file_contents).context("Failed to parse Rust file")?;
133
134    for item in file.items {
135        match item {
136            Item::Enum(item_enum) => {
137                let enum_name = item_enum.ident.clone();
138                process_enum_definition(item_enum, out_dir, state).with_context(|| {
139                    format!(
140                        "Failed to process enum `{enum_name}` in file `{}`",
141                        source.display()
142                    )
143                })?;
144            }
145            Item::Impl(item_impl) => {
146                let trait_name = item_impl.trait_.as_ref().map(|(path, _)| {
147                    path.segments
148                        .last()
149                        .expect("Path is never empty; qed")
150                        .ident
151                        .clone()
152                });
153                let type_name = item_impl.self_ty.clone();
154                if let Some(result) = process_enum_impl(item_impl.clone(), out_dir, state) {
155                    result.with_context(|| {
156                        format!(
157                            "Failed to process impl block (`{:?}` for `{}`) in file `{}`",
158                            trait_name.to_token_stream(),
159                            type_name.to_token_stream(),
160                            source.display()
161                        )
162                    })?;
163                } else if let Some(result) = process_execution_impl(item_impl, out_dir, state) {
164                    result.with_context(|| {
165                        format!(
166                            "Failed to process impl block (`{:?}` for `{}`) in file `{}`",
167                            trait_name.to_token_stream(),
168                            type_name.to_token_stream(),
169                            source.display()
170                        )
171                    })?;
172                }
173            }
174            _ => {
175                // Ignore
176            }
177        }
178    }
179
180    Ok(())
181}