machine_check_machine/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6use machine_check_common::iir::description::IMachine;
7use machine_check_common::iir::path::{IIdent, ISpan};
8use machine_check_common::iir::property::IProperty;
9use machine_check_common::iir::ty::IElementaryType;
10use machine_check_common::{PropertyMacros, Signedness};
11use mck::concr::FullMachine;
12use proc_macro2::{Ident, Span};
13use quote::{quote, ToTokens};
14use support::error_list::ErrorList;
15use syn::punctuated::Punctuated;
16use syn::spanned::Spanned;
17use syn::visit_mut::{self, VisitMut};
18use syn::{parse_quote, Attribute, Expr, Item, ItemFn, ItemMod, Meta, MetaList, PathSegment};
19use syn_path::path;
20use wir::IntoSyn;
21
22use crate::util::{create_item_mod, path_matches_global_names};
23use crate::wir::{WBasicType, WIdent, WSpan};
24
25mod abstr;
26mod concr;
27mod into_iir;
28mod into_wir;
29mod support;
30mod util;
31mod wir;
32
33pub use support::machine_error::{Error, ErrorType};
34
35pub type Errors = ErrorList<Error>;
36
37#[derive(Clone)]
38struct Description {
39    pub items: Vec<Item>,
40}
41
42pub fn process_file(mut file: syn::File) -> Result<syn::File, Errors> {
43    process_items(&mut file.items)?;
44    Ok(file)
45}
46
47pub fn process_module(mut module: ItemMod) -> Result<ItemMod, Errors> {
48    let Some((_, items)) = &mut module.content else {
49        return Err(Errors::single(Error::new(
50            ErrorType::ModuleWithoutContent,
51            WSpan::from_syn(&module),
52        )));
53    };
54    process_items(items)?;
55    Ok(module)
56}
57
58pub fn inherent_property() -> IProperty {
59    let mut global_basic_types = HashMap::new();
60    global_basic_types.insert(
61        WIdent::new(String::from("__panic"), Span::call_site()),
62        WBasicType::Bitvector(Signedness::None, 32),
63    );
64
65    let expr = parse_quote!(AG![__panic == 0]);
66
67    let (property, _panic_messages) =
68        into_wir::create_property_description(expr, &global_basic_types, &PropertyMacros::empty())
69            .expect("Inherent property should be created");
70
71    //println!("Abstract description: {:?}", description);
72
73    property
74        .into_iir()
75        .expect("Inherent property should not produce an error")
76}
77
78pub fn process_property<M: FullMachine, D>(
79    machine: &IMachine,
80    property: &str,
81    property_macros: &PropertyMacros<D>,
82) -> Result<IProperty, Errors> {
83    let expr: Expr = syn::parse_str(property).map_err(|err| {
84        Errors::single(Error::new(
85            ErrorType::ExpressionParseError(err.to_string()),
86            WSpan::from_span(err.span()),
87        ))
88    })?;
89
90    let mut global_ident_types = machine.state().fields.clone();
91
92    global_ident_types.insert(
93        IIdent::new(String::from("__panic"), ISpan::Unspecified),
94        IElementaryType::Bitvector(32),
95    );
96
97    let mut global_basic_types = HashMap::new();
98
99    // TODO: get signedness information
100    for (global_ident, elementary_type) in &global_ident_types {
101        let ty = match elementary_type {
102            IElementaryType::Bitvector(width) => WBasicType::Bitvector(Signedness::None, *width),
103            IElementaryType::Array(type_array) => WBasicType::BitvectorArray(type_array.clone()),
104            IElementaryType::Boolean => WBasicType::Boolean,
105            IElementaryType::Struct(_struct_id) => {
106                todo!("Support nested structs")
107            }
108        };
109        global_basic_types.insert(
110            WIdent::new(global_ident.name().to_string(), Span::call_site()),
111            ty,
112        );
113    }
114
115    // TODO: do something with the panic messages
116    let (property, _panic_messages) =
117        into_wir::create_property_description(expr, &global_basic_types, property_macros)?;
118
119    let property = property.into_iir();
120
121    Ok(property?)
122}
123
124pub fn default_main() -> Item {
125    let main_fn: ItemFn = parse_quote!(
126        fn main() {
127            ::machine_check_exec::run::<refin::Input, refin::State, refin::Machine>()
128        }
129    );
130    Item::Fn(main_fn)
131}
132
133fn out_dir() -> Option<PathBuf> {
134    let mut args = std::env::args();
135
136    let mut out_dir = None;
137    while let Some(arg) = args.next() {
138        if arg == "--out-dir" {
139            out_dir = args.next();
140        }
141    }
142    let out_dir = out_dir?;
143    // find target directory
144    let mut out_dir = PathBuf::from(out_dir);
145    while !out_dir.ends_with("target") {
146        if !out_dir.pop() {
147            return None;
148        }
149    }
150
151    Some(out_dir)
152}
153
154#[allow(dead_code)]
155fn unparse(items: Vec<Item>) -> String {
156    prettyplease::unparse(&syn::File {
157        shebang: None,
158        attrs: vec![],
159        items,
160    })
161}
162
163fn process_items(items: &mut Vec<Item>) -> Result<(), Errors> {
164    let out_dir: Option<PathBuf> = if cfg!(feature = "write_machine") {
165        out_dir()
166    } else {
167        None
168    };
169
170    let (description, panic_messages) = into_wir::create_description(items.clone())?;
171
172    if let Some(out_dir) = &out_dir {
173        std::fs::write(
174            out_dir.join("description.rs"),
175            unparse(wir::IntoSyn::into_syn(description.clone()).items),
176        )
177        .expect("SSA machine file should be writable");
178    }
179
180    let iir = description.clone().into_iir()?;
181
182    let (abstract_description, misc_abstract_items) =
183        abstr::create_abstract_description(description);
184
185    if let Some(out_dir) = &out_dir {
186        std::fs::write(
187            out_dir.join("description_abstr.rs"),
188            unparse(wir::IntoSyn::into_syn(abstract_description.clone()).items),
189        )
190        .expect("Abstract machine file should be writable");
191    }
192
193    let mut abstract_description = Description {
194        items: abstract_description.into_syn().items,
195    };
196
197    abstract_description.items.extend(misc_abstract_items);
198
199    support::strip_machine::strip_machine(&mut abstract_description)?;
200
201    concr::process_items(items, &panic_messages, iir)?;
202
203    let abstract_module = create_machine_module("__mck_mod_abstr", abstract_description);
204    items.push(abstract_module);
205
206    redirect_mck(items)?;
207
208    if let Some(out_dir) = &out_dir {
209        std::fs::write(out_dir.join("description_full.rs"), unparse(items.clone()))
210            .expect("Full machine file should be writable");
211    }
212
213    Ok(())
214}
215
216fn redirect_mck(items: &mut [Item]) -> Result<(), Error> {
217    let mut redirect_visitor = RedirectVisitor;
218    for item in items.iter_mut() {
219        redirect_visitor.visit_item_mut(item);
220    }
221
222    Ok(())
223}
224
225struct RedirectVisitor;
226
227impl VisitMut for RedirectVisitor {
228    fn visit_path_mut(&mut self, path: &mut syn::Path) {
229        if path.leading_colon.is_some() {
230            let first_segment = path
231                .segments
232                .first()
233                .expect("Path should have first segment");
234            if first_segment.ident == "mck" {
235                let span = first_segment.span();
236                // add machine_check before it
237                path.segments.insert(
238                    0,
239                    PathSegment {
240                        ident: Ident::new("machine_check", span),
241                        arguments: syn::PathArguments::None,
242                    },
243                );
244            }
245        }
246        visit_mut::visit_path_mut(self, path);
247    }
248
249    fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
250        // ensure paths in ::std::vec![...] are correctly redirected
251        if path_matches_global_names(&mac.path, &["std", "vec"]) {
252            let mut parsed = mac
253                .parse_body_with(Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated)
254                .expect("Macro ::std::vec! should be parseable");
255            for expr in &mut parsed {
256                visit_mut::visit_expr_mut(self, expr);
257            }
258            mac.tokens = parsed.into_token_stream();
259        }
260        visit_mut::visit_macro_mut(self, mac);
261    }
262}
263
264fn create_machine_module(name: &str, machine: Description) -> Item {
265    let mut module = create_item_mod(
266        syn::Visibility::Public(Default::default()),
267        Ident::new(name, Span::call_site()),
268        machine.items,
269    );
270    // Turn off some warnings due to the form of the rewritten modules.
271    // Note that they can still fire for the original code.
272    module.attrs.push(Attribute {
273        pound_token: Default::default(),
274        style: syn::AttrStyle::Outer,
275        bracket_token: Default::default(),
276        meta: Meta::List(MetaList {
277            path: path!(allow),
278            delimiter: syn::MacroDelimiter::Paren(Default::default()),
279            tokens: quote!(
280                non_snake_case, // _a can become __mck__a and violate snake-casing
281                clippy::all,    // turn off clippy altogether to speed up
282            ),
283        }),
284    });
285
286    Item::Mod(module)
287}