use rucc_rules::{Rule, parse};
use rucc_verify::{Model, query};
const MODEL: &str = "\
(semantics (value.i32 v) v)
(semantics (value.i64 v) v)
(semantics (iconst.i64 c) c)
(semantics (add.i64 l r) (bvadd l r))
(semantics (mul.i64 l r) (bvmul l r))
(semantics (load.i32 a) (concat (select (mem) (bvadd a 3))
(select (mem) (bvadd a 2))
(select (mem) (bvadd a 1))
(select (mem) a)))
(semantics (amode_base_index_scale base index scale) (bvadd base (bvmul index scale)))
(semantics (x64.mov_rm_32 a) (concat (select (mem) (bvadd a 3))
(select (mem) (bvadd a 2))
(select (mem) (bvadd a 1))
(select (mem) a)))";
const SCALED: &str = "\
(rule (lower (load.i32 (add.i64 (value.i64 a) (mul.i64 (value.i64 i) (iconst.i64 4)))))
(x64.mov_rm_32 (amode_base_index_scale a i 4))
(spec (= (concat (select (mem) (bvadd (bvadd a (bvmul i 4)) 3))
(select (mem) (bvadd (bvadd a (bvmul i 4)) 2))
(select (mem) (bvadd (bvadd a (bvmul i 4)) 1))
(select (mem) (bvadd a (bvmul i 4))))
(result))))";
fn model(text: &str) -> Model {
match Model::read("t.model", text) {
Ok(model) => model,
Err(errors) => panic!("{}", errors[0]),
}
}
fn rule(text: &str) -> Rule {
match parse("t.rules", text) {
Ok(rules) => rules.into_iter().next().expect("one rule"),
Err(errors) => panic!("{}", errors[0]),
}
}
fn refuse(model: &Model, text: &str) -> String {
match query("t.rules", &rule(text), model) {
Ok(_) => panic!("that was supposed to be refused"),
Err(error) => error.to_string(),
}
}
#[test]
fn a_number_given_to_a_head_is_written_at_the_width_its_body_uses_it_at() {
let asked = query("t.rules", &rule(SCALED), &model(MODEL)).expect("the model covers this");
assert!(asked.contains("(_ bv4 64)"), "{asked}");
assert!(!asked.contains("(_ bv4 32)"), "{asked}");
}
#[test]
fn the_width_the_rule_runs_at_is_not_what_a_number_in_a_head_takes() {
let text = "\
(rule (lower (load.i32 (add.i64 (value.i64 a) (mul.i64 (value.i64 i) (iconst.i64 8)))))
(x64.mov_rm_32 (amode_base_index_scale a i 8))
(spec (= (concat (select (mem) (bvadd (bvadd a (bvmul i 8)) 3))
(select (mem) (bvadd (bvadd a (bvmul i 8)) 2))
(select (mem) (bvadd (bvadd a (bvmul i 8)) 1))
(select (mem) (bvadd a (bvmul i 8))))
(result))))";
let asked = query("t.rules", &rule(text), &model(MODEL)).expect("the model covers this");
assert!(asked.contains("(_ bv8 64)"), "{asked}");
}
#[test]
fn a_number_beside_something_narrower_than_the_rule_takes_the_narrower_width() {
let text = "\
(semantics (value.i1 v) v)
(semantics (value.i8 v) v)
(semantics (trunc.i8.i1 v) (extract 0 0 v))
(semantics (x64.bit_of_8 l r) (bvand (extract 0 0 l) r))";
let rule = "\
(rule (lower (trunc.i8.i1 (value.i8 x)))
(x64.bit_of_8 x 1)
(spec (= (extract 0 0 x) (result))))";
let asked = query("t.rules", &self::rule(rule), &model(text)).expect("the model covers this");
assert!(asked.contains("(_ bv1 1)"), "{asked}");
}
#[test]
fn a_body_that_uses_one_number_at_two_widths_is_refused() {
let text = "\
(semantics (value.i8 v) v)
(semantics (value.i32 v) v)
(semantics (x64.two_ways l k) (bvadd (zero_extend 8 32 (bvadd (extract 7 0 l) k)) k))
(semantics (id.i32 v) v)";
let rule = "\
(rule (lower (id.i32 (value.i32 x)))
(x64.two_ways x 1)
(spec (= x (result))))";
let said = refuse(&model(text), rule);
assert!(said.contains("`k` is a number"), "{said}");
assert!(said.contains("8 bits"), "{said}");
assert!(said.contains("32 bits"), "{said}");
}
#[test]
fn a_complaint_about_a_body_names_the_file_the_body_is_in() {
let text = "\
(semantics (value.i32 v) v)
(semantics (id.i32 v) v)
(semantics (x64.calls_nothing v) (x64.nothing v))";
let rule = "\
(rule (lower (id.i32 (value.i32 x)))
(x64.calls_nothing x)
(spec (= x (result))))";
let said = refuse(&model(text), rule);
assert!(said.contains("t.model:3:"), "{said}");
assert!(!said.contains("t.rules"), "{said}");
}