Skip to main content

ab_riscv_macros/
build.rs

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