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