1 use cranelift_codegen_meta::{
2     generate_isle,
3     isle::{get_isle_compilations, shared_isle_lower_paths},
4 };
5 use cranelift_isle::compile::create_envs;
6 use std::env;
7 use std::path::PathBuf;
8 use strum::IntoEnumIterator;
9 use strum_macros::EnumIter;
10 use veri_engine_lib::annotations::parse_annotations;
11 use veri_engine_lib::type_inference::type_rules_with_term_and_types;
12 use veri_engine_lib::verify::verify_rules_for_term;
13 use veri_engine_lib::Config;
14 use veri_ir::{ConcreteTest, Counterexample, TermSignature, VerificationResult};
15 
16 #[derive(Debug, EnumIter, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
17 #[repr(usize)]
18 pub enum Bitwidth {
19     I8 = 8,
20     I16 = 16,
21     I32 = 32,
22     I64 = 64,
23 }
24 
25 pub enum TestResult {
26     Simple(Vec<(Bitwidth, VerificationResult)>),
27     Expect(fn(&TermSignature) -> VerificationResult),
28 }
29 
30 type TestResultBuilder = dyn Fn(Bitwidth) -> (Bitwidth, VerificationResult);
31 
32 use std::sync::Once;
33 
34 static INIT: Once = Once::new();
35 
get_isle_files(name: &str) -> Vec<std::path::PathBuf>36 pub fn get_isle_files(name: &str) -> Vec<std::path::PathBuf> {
37     let cur_dir = env::current_dir().expect("Can't access current working directory");
38     let gen_dir = cur_dir.join("test_output");
39     INIT.call_once(|| {
40         // Logger
41         env_logger::init();
42         // Test directory
43         if !gen_dir.is_dir() {
44             std::fs::create_dir(gen_dir.as_path()).unwrap();
45         }
46         // Generate ISLE files.
47         generate_isle(gen_dir.as_path()).expect("Can't generate ISLE");
48     });
49 
50     let codegen_crate_dir = cur_dir.join("../../../codegen");
51     let inst_specs_isle = codegen_crate_dir.join("src").join("inst_specs.isle");
52 
53     match name {
54         "shared_lower" => {
55             let mut shared = shared_isle_lower_paths(codegen_crate_dir.as_path());
56             shared.push(gen_dir.join("clif_lower.isle"));
57             shared
58         }
59         _ => {
60             // Lookup ISLE shared .
61             let compilations =
62                 get_isle_compilations(codegen_crate_dir.as_path(), gen_dir.as_path());
63 
64             // Return inputs from the matching compilation, if any.
65             let mut inputs = compilations.lookup(name).unwrap().inputs();
66             inputs.push(inst_specs_isle);
67             inputs
68         }
69     }
70 }
71 
72 // Some examples of functions we might need
73 #[allow(dead_code)]
just_8_result() -> TestResult74 pub fn just_8_result() -> TestResult {
75     TestResult::Simple(vec![(Bitwidth::I8, VerificationResult::Success)])
76 }
77 
78 #[allow(dead_code)]
just_16_result() -> TestResult79 pub fn just_16_result() -> TestResult {
80     TestResult::Simple(vec![(Bitwidth::I16, VerificationResult::Success)])
81 }
82 
83 #[allow(dead_code)]
just_32_result() -> TestResult84 pub fn just_32_result() -> TestResult {
85     TestResult::Simple(vec![(Bitwidth::I32, VerificationResult::Success)])
86 }
87 
88 #[allow(dead_code)]
just_64_result() -> TestResult89 pub fn just_64_result() -> TestResult {
90     TestResult::Simple(vec![(Bitwidth::I64, VerificationResult::Success)])
91 }
92 
93 /// All bitwidths verify
94 #[allow(dead_code)]
all_success_result() -> Vec<(Bitwidth, VerificationResult)>95 pub fn all_success_result() -> Vec<(Bitwidth, VerificationResult)> {
96     custom_result(&|w| (w, VerificationResult::Success))
97 }
98 
99 /// All bitwidths fail
100 #[allow(dead_code)]
all_failure_result() -> Vec<(Bitwidth, VerificationResult)>101 pub fn all_failure_result() -> Vec<(Bitwidth, VerificationResult)> {
102     custom_result(&|w| (w, VerificationResult::Failure(Counterexample {})))
103 }
104 
105 /// Specify a custom set expected result (helpful if you want to test all the bitwidths and expect
106 /// a range of different success, failure, and inapplicable outcomes)
custom_result(f: &TestResultBuilder) -> Vec<(Bitwidth, VerificationResult)>107 pub fn custom_result(f: &TestResultBuilder) -> Vec<(Bitwidth, VerificationResult)> {
108     Bitwidth::iter().map(f).collect()
109 }
110 
test_rules_with_term(inputs: Vec<PathBuf>, tr: TestResult, config: Config)111 fn test_rules_with_term(inputs: Vec<PathBuf>, tr: TestResult, config: Config) {
112     let (typeenv, termenv, defs) = create_envs(inputs).unwrap();
113     let annotation_env = parse_annotations(&defs, &termenv, &typeenv);
114 
115     let term_signatures = annotation_env
116         .get_term_signatures_by_name(&termenv, &typeenv)
117         .get(config.term.as_str())
118         .unwrap_or_else(|| panic!("Missing term type instantiation for {}", config.term))
119         .clone();
120     let instantiations = match tr {
121         TestResult::Simple(s) => {
122             let mut res = vec![];
123             for (width, result) in s {
124                 let ty = match width {
125                     Bitwidth::I8 => veri_ir::Type::BitVector(Some(8)),
126                     Bitwidth::I16 => veri_ir::Type::BitVector(Some(16)),
127                     Bitwidth::I32 => veri_ir::Type::BitVector(Some(32)),
128                     Bitwidth::I64 => veri_ir::Type::BitVector(Some(64)),
129                 };
130                 // Find the type instantiations with this as the canonical type
131                 let all_instantiations: Vec<&TermSignature> = term_signatures
132                     .iter()
133                     .filter(|sig| sig.canonical_type.unwrap() == ty)
134                     .collect();
135                 if all_instantiations.is_empty() {
136                     panic!("Missing type instantiation for width {width:?}");
137                 }
138                 for i in all_instantiations {
139                     res.push((i.clone(), result.clone()));
140                 }
141             }
142             res
143         }
144         TestResult::Expect(expect) => term_signatures
145             .iter()
146             .map(|sig| (sig.clone(), expect(sig)))
147             .collect(),
148     };
149 
150     for (type_instantiation, expected_result) in instantiations {
151         log::debug!("Expected result: {expected_result:?}");
152         let type_sols = type_rules_with_term_and_types(
153             &termenv,
154             &typeenv,
155             &annotation_env,
156             &config,
157             &type_instantiation,
158             &None,
159         );
160         let result = verify_rules_for_term(
161             &termenv,
162             &typeenv,
163             &type_sols,
164             type_instantiation,
165             &None,
166             &config,
167         );
168         assert_eq!(result, expected_result);
169     }
170 }
171 
test_from_file_with_lhs_termname_simple( file: &str, termname: String, tr: Vec<(Bitwidth, VerificationResult)>, )172 pub fn test_from_file_with_lhs_termname_simple(
173     file: &str,
174     termname: String,
175     tr: Vec<(Bitwidth, VerificationResult)>,
176 ) {
177     test_from_file_with_lhs_termname(file, termname, TestResult::Simple(tr))
178 }
179 
test_from_file_with_lhs_termname(file: &str, termname: String, tr: TestResult)180 pub fn test_from_file_with_lhs_termname(file: &str, termname: String, tr: TestResult) {
181     println!("Verifying {termname} rules in file: {file}");
182     let mut inputs = get_isle_files("shared_lower");
183     inputs.push(PathBuf::from(file));
184     let config = Config {
185         term: termname,
186         distinct_check: true,
187         custom_verification_condition: None,
188         custom_assumptions: None,
189         names: None,
190     };
191     test_rules_with_term(inputs, tr, config);
192 }
193 
test_aarch64_rule_with_lhs_termname_simple( rulename: &str, termname: &str, tr: Vec<(Bitwidth, VerificationResult)>, )194 pub fn test_aarch64_rule_with_lhs_termname_simple(
195     rulename: &str,
196     termname: &str,
197     tr: Vec<(Bitwidth, VerificationResult)>,
198 ) {
199     test_aarch64_rule_with_lhs_termname(rulename, termname, TestResult::Simple(tr))
200 }
201 
test_aarch64_rule_with_lhs_termname(rulename: &str, termname: &str, tr: TestResult)202 pub fn test_aarch64_rule_with_lhs_termname(rulename: &str, termname: &str, tr: TestResult) {
203     println!("Verifying rule `{rulename}` with termname {termname} ");
204     let inputs = get_isle_files("aarch64");
205     let config = Config {
206         term: termname.to_string(),
207         distinct_check: true,
208         custom_verification_condition: None,
209         custom_assumptions: None,
210         names: Some(vec![rulename.to_string()]),
211     };
212     test_rules_with_term(inputs, tr, config);
213 }
214 
test_x64_rule_with_lhs_termname_simple( rulename: &str, termname: &str, tr: Vec<(Bitwidth, VerificationResult)>, )215 pub fn test_x64_rule_with_lhs_termname_simple(
216     rulename: &str,
217     termname: &str,
218     tr: Vec<(Bitwidth, VerificationResult)>,
219 ) {
220     test_x64_rule_with_lhs_termname(rulename, termname, TestResult::Simple(tr))
221 }
222 
test_x64_rule_with_lhs_termname(rulename: &str, termname: &str, tr: TestResult)223 pub fn test_x64_rule_with_lhs_termname(rulename: &str, termname: &str, tr: TestResult) {
224     println!("Verifying rule `{rulename}` with termname {termname} ");
225     let inputs = get_isle_files("x64");
226     let config = Config {
227         term: termname.to_string(),
228         distinct_check: true,
229         custom_verification_condition: None,
230         custom_assumptions: None,
231         names: Some(vec![rulename.to_string()]),
232     };
233     test_rules_with_term(inputs, tr, config);
234 }
235 
test_from_file_with_config_simple( file: &str, config: Config, tr: Vec<(Bitwidth, VerificationResult)>, )236 pub fn test_from_file_with_config_simple(
237     file: &str,
238     config: Config,
239     tr: Vec<(Bitwidth, VerificationResult)>,
240 ) {
241     test_from_file_with_config(file, config, TestResult::Simple(tr))
242 }
test_from_file_with_config(file: &str, config: Config, tr: TestResult)243 pub fn test_from_file_with_config(file: &str, config: Config, tr: TestResult) {
244     println!("Verifying {} rules in file: {}", config.term, file);
245     let mut inputs = get_isle_files("shared_lower");
246     inputs.push(PathBuf::from(file));
247     test_rules_with_term(inputs, tr, config);
248 }
249 
test_aarch64_with_config_simple(config: Config, tr: Vec<(Bitwidth, VerificationResult)>)250 pub fn test_aarch64_with_config_simple(config: Config, tr: Vec<(Bitwidth, VerificationResult)>) {
251     test_aarch64_with_config(config, TestResult::Simple(tr))
252 }
253 
test_aarch64_with_config(config: Config, tr: TestResult)254 pub fn test_aarch64_with_config(config: Config, tr: TestResult) {
255     println!(
256         "Verifying rules {:?} with termname {}",
257         config.names, config.term
258     );
259     let inputs = get_isle_files("aarch64");
260     test_rules_with_term(inputs, tr, config);
261 }
262 
test_concrete_aarch64_rule_with_lhs_termname( rulename: &str, termname: &str, concrete: ConcreteTest, )263 pub fn test_concrete_aarch64_rule_with_lhs_termname(
264     rulename: &str,
265     termname: &str,
266     concrete: ConcreteTest,
267 ) {
268     println!("Verifying concrete input rule `{rulename}` with termname {termname} ");
269     let inputs = get_isle_files("aarch64");
270     let (typeenv, termenv, defs) = create_envs(inputs).unwrap();
271     let annotation_env = parse_annotations(&defs, &termenv, &typeenv);
272 
273     let config = Config {
274         term: termname.to_string(),
275         distinct_check: false,
276         custom_verification_condition: None,
277         custom_assumptions: None,
278         names: Some(vec![rulename.to_string()]),
279     };
280 
281     // Get the types/widths for this particular term
282     let args = concrete.args.iter().map(|i| i.ty).collect();
283     let ret = concrete.output.ty;
284     let t = TermSignature {
285         args,
286         ret,
287         canonical_type: None,
288     };
289 
290     let type_sols = type_rules_with_term_and_types(
291         &termenv,
292         &typeenv,
293         &annotation_env,
294         &config,
295         &t,
296         &Some(concrete.clone()),
297     );
298     let result = verify_rules_for_term(&termenv, &typeenv, &type_sols, t, &Some(concrete), &config);
299     assert_eq!(result, VerificationResult::Success);
300 }
301 
test_concrete_input_from_file_with_lhs_termname( file: &str, termname: String, concrete: ConcreteTest, )302 pub fn test_concrete_input_from_file_with_lhs_termname(
303     file: &str,
304     termname: String,
305     concrete: ConcreteTest,
306 ) {
307     println!("Verifying concrete input {termname} rule in file: {file}");
308     let mut inputs = get_isle_files("shared_lower");
309     inputs.push(PathBuf::from(file));
310 
311     let (typeenv, termenv, defs) = create_envs(inputs).unwrap();
312     let annotation_env = parse_annotations(&defs, &termenv, &typeenv);
313 
314     let config = Config {
315         term: termname.clone(),
316         distinct_check: false,
317         custom_verification_condition: None,
318         custom_assumptions: None,
319         names: None,
320     };
321 
322     // Get the types/widths for this particular term
323     let args = concrete.args.iter().map(|i| i.ty).collect();
324     let ret = concrete.output.ty;
325     let t = TermSignature {
326         args,
327         ret,
328         canonical_type: None,
329     };
330 
331     let type_sols = type_rules_with_term_and_types(
332         &termenv,
333         &typeenv,
334         &annotation_env,
335         &config,
336         &t,
337         &Some(concrete.clone()),
338     );
339     let result = verify_rules_for_term(&termenv, &typeenv, &type_sols, t, &Some(concrete), &config);
340     assert_eq!(result, VerificationResult::Success);
341 }
342